plugin.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. // This file is subject to a 1-clause BSD license.
  2. // Its contents can be found in the enclosed LICENSE file.
  3. // Package owm provides commands to do current weather lookups.
  4. package owm
  5. import (
  6. "fmt"
  7. "io/ioutil"
  8. "net/http"
  9. "path/filepath"
  10. "sync"
  11. "time"
  12. "notabug.org/mouz/bot/app/util"
  13. "notabug.org/mouz/bot/irc"
  14. "notabug.org/mouz/bot/irc/cmd"
  15. "notabug.org/mouz/bot/plugins"
  16. )
  17. func init() { plugins.Register(&plugin{}) }
  18. // CacheTimeout defines the time after which a cache entry is
  19. // considered stale and it must be re-fetched.
  20. const CacheTimeout = time.Minute * 5
  21. // LookupTimeout defines the timeout after which a service request
  22. // is considered failed.
  23. const LookupTimeout = time.Second * 5
  24. // MaxFetchFrequency defines the maximum number of GET requests per
  25. // minute to the weather server
  26. const MaxFetchFrequency = 60
  27. type plugin struct {
  28. m sync.Mutex
  29. cmd *cmd.Set
  30. currentWeatherCache map[string]*owmCurrent
  31. fetchTimeCache map[time.Time]bool
  32. config struct {
  33. OwmAPIKey string
  34. }
  35. }
  36. // Load initializes the module and loads any internal resources
  37. // which may be required.
  38. func (p *plugin) Load(prof irc.Profile) error {
  39. p.currentWeatherCache = make(map[string]*owmCurrent)
  40. p.fetchTimeCache = make(map[time.Time]bool)
  41. p.cmd = cmd.New(prof.CommandPrefix(), nil)
  42. p.cmd.Bind(TextCurrentWeatherName, false, p.cmdCurrentWeather).
  43. Add(TextLocation, true, cmd.RegAny)
  44. file := filepath.Join(prof.Root(), "weather.cfg")
  45. return util.ReadFile(file, &p.config, false)
  46. }
  47. // Unload cleans the module up and unloads any internal resources.
  48. func (p *plugin) Unload(prof irc.Profile) error {
  49. p.config.OwmAPIKey = ""
  50. return nil
  51. }
  52. // Dispatch sends the given, incoming IRC message to the plugin for
  53. // processing as it sees fit.
  54. func (p *plugin) Dispatch(w irc.ResponseWriter, r *irc.Request) {
  55. if len(p.config.OwmAPIKey) > 0 {
  56. p.cmd.Dispatch(w, r)
  57. }
  58. }
  59. // fetch fetches the given URL. It returns the content of the
  60. // response, or an error if there were too many fetches in the last
  61. // minute.
  62. func (p *plugin) fetch(serviceURL, query string) ([]byte, error) {
  63. if p.fetchOverload() {
  64. return nil, fmt.Errorf("Number of API requests exceeds %d per minute", MaxFetchFrequency)
  65. }
  66. p.fetchTimeCache[time.Now()] = true
  67. url := fmt.Sprintf(serviceURL, TextUnits, TextLanguage, p.config.OwmAPIKey, query)
  68. resp, err := http.Get(url)
  69. if err != nil {
  70. return nil, err
  71. }
  72. defer resp.Body.Close()
  73. data, err := ioutil.ReadAll(resp.Body)
  74. if err != nil {
  75. return nil, err
  76. }
  77. return data, nil
  78. }
  79. // fetchOverload returns true iff too many fetches are done recently
  80. // (in the last minute).
  81. func (p *plugin) fetchOverload() bool {
  82. // remove old entries from time cache
  83. isold := make(map[time.Time]bool)
  84. for t := range p.fetchTimeCache {
  85. if t.Add(time.Minute).Before(time.Now()) {
  86. isold[t] = true
  87. }
  88. }
  89. for t := range isold {
  90. delete(p.fetchTimeCache, t)
  91. }
  92. // check remaining length of time cache
  93. if len(p.fetchTimeCache) >= MaxFetchFrequency {
  94. return true
  95. }
  96. return false
  97. }