plugin.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. // This file is subject to a 1-clause BSD license.
  2. // Its contents can be found in the enclosed LICENSE file.
  3. // Package knmi provides commands to do current weather lookups.
  4. package knmi
  5. import (
  6. "encoding/xml"
  7. "io/ioutil"
  8. "log"
  9. "net/url"
  10. "path/filepath"
  11. "sync"
  12. "time"
  13. "notabug.org/mouz/bot/app/util"
  14. "notabug.org/mouz/bot/ftp"
  15. "notabug.org/mouz/bot/irc"
  16. "notabug.org/mouz/bot/irc/cmd"
  17. "notabug.org/mouz/bot/irc/proto"
  18. "notabug.org/mouz/bot/plugins"
  19. )
  20. func init() { plugins.Register(&plugin{}) }
  21. type plugin struct {
  22. m sync.Mutex
  23. cmd *cmd.Set
  24. config struct {
  25. ForecastURL string
  26. }
  27. }
  28. // Load initializes the module and loads any internal resources
  29. // which may be required.
  30. func (p *plugin) Load(prof irc.Profile) error {
  31. p.cmd = cmd.New(prof.CommandPrefix(), nil)
  32. p.cmd.Bind(TextForecastName, false, p.cmdForecast)
  33. file := filepath.Join(prof.Root(), "weather.cfg")
  34. return util.ReadFile(file, &p.config, false)
  35. }
  36. // Unload cleans the module and unloads any internal resources.
  37. func (p *plugin) Unload(prof irc.Profile) error {
  38. p.config.ForecastURL = ""
  39. return nil
  40. }
  41. // Dispatch sends the given, incoming IRC message to the plugin for
  42. // processing as it sees fit.
  43. func (p *plugin) Dispatch(w irc.ResponseWriter, r *irc.Request) {
  44. if len(p.config.ForecastURL) == 0 {
  45. log.Println("[knmi] ForecastURL not in weather.cfg")
  46. return
  47. }
  48. p.cmd.Dispatch(w, r)
  49. }
  50. // cmdForecast yields the weather forecast.
  51. func (p *plugin) cmdForecast(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
  52. p.m.Lock()
  53. defer p.m.Unlock()
  54. data, err := p.fetch(p.config.ForecastURL)
  55. if err != nil {
  56. log.Println("[knmi]", err)
  57. return
  58. }
  59. err = p.sendResult(w, r, data)
  60. if err != nil {
  61. log.Println("[knmi]", err)
  62. return
  63. }
  64. }
  65. // fetch fetches the given URL using the FTP protocol.
  66. func (p *plugin) fetch(furl string) ([]byte, error) {
  67. u, err := url.Parse(furl)
  68. if err != nil {
  69. return nil, err
  70. }
  71. conn, err := ftp.DialTimeout(u.Host+":21", 5*time.Second)
  72. if err != nil {
  73. return nil, err
  74. }
  75. err = conn.Login("anonymous", "anonymous")
  76. if err != nil {
  77. return nil, err
  78. }
  79. resp, err := conn.Retr(u.Path)
  80. if err != nil {
  81. return nil, err
  82. }
  83. buf, err := ioutil.ReadAll(resp)
  84. if err != nil {
  85. return nil, err
  86. }
  87. return buf, nil
  88. }
  89. // sendResult formats the result end sends it back to the user.
  90. func (p *plugin) sendResult(w irc.ResponseWriter, r *irc.Request, data []byte) error {
  91. forecast := knmiForecast{}
  92. err := xml.Unmarshal(data, &forecast)
  93. if err != nil {
  94. return err
  95. }
  96. report := forecast.Data.Location.Block[1].FieldContent
  97. return proto.PrivMsg(w, r.Target, report)
  98. }
  99. // knmiForecast represents the response when the request for the forecast was
  100. // succesful.
  101. //
  102. // The struct is generated using https://github.com/miku/zek via
  103. // https://www.onlinetool.io/xmltogo/ using as input a copy of
  104. // ftp://ftp.knmi.nl/pub_weerberichten/basisverwachting.xml
  105. type knmiForecast struct {
  106. XMLName xml.Name `xml:"report"`
  107. Text string `xml:",chardata"`
  108. Metadata struct {
  109. Text string `xml:",chardata"`
  110. ReportInfo struct {
  111. Text string `xml:",chardata"`
  112. ReportID string `xml:"report_id"`
  113. ReportLanguage string `xml:"report_language"`
  114. ReportTimeCoordinate string `xml:"report_time_coordinate"`
  115. ReportDtgIssued string `xml:"report_dtg_issued"`
  116. ReportStartValidTime string `xml:"report_start_valid_time"`
  117. ReportEndValidTime string `xml:"report_end_valid_time"`
  118. ReportIssuedBy string `xml:"report_issued_by"`
  119. ReportForecasterID string `xml:"report_forecaster_id"`
  120. ReportAdditionalInformation string `xml:"report_additional_information"`
  121. ReportDisclaimer string `xml:"report_disclaimer"`
  122. ReportCredit string `xml:"report_credit"`
  123. ReportCreditLogo string `xml:"report_credit_logo"`
  124. } `xml:"report_info"`
  125. ReportLocations struct {
  126. Text string `xml:",chardata"`
  127. ReportLocation struct {
  128. Text string `xml:",chardata"`
  129. LocationID string `xml:"location_id"`
  130. LocationDescr string `xml:"location_descr"`
  131. } `xml:"report_location"`
  132. } `xml:"report_locations"`
  133. ReportValidPeriods struct {
  134. Text string `xml:",chardata"`
  135. ReportValidPeriod []struct {
  136. Text string `xml:",chardata"`
  137. ValidID string `xml:"valid_id"`
  138. ValidStart string `xml:"valid_start"`
  139. ValidEnd string `xml:"valid_end"`
  140. ValidDescr string `xml:"valid_descr"`
  141. } `xml:"report_valid_period"`
  142. } `xml:"report_valid_periods"`
  143. ReportFields struct {
  144. Text string `xml:",chardata"`
  145. ReportField []struct {
  146. Text string `xml:",chardata"`
  147. FieldID string `xml:"field_id"`
  148. FieldDescr string `xml:"field_descr"`
  149. } `xml:"report_field"`
  150. } `xml:"report_fields"`
  151. } `xml:"metadata"`
  152. Data struct {
  153. Text string `xml:",chardata"`
  154. Location struct {
  155. Text string `xml:",chardata"`
  156. LocationID string `xml:"location_id"`
  157. Block []struct {
  158. Text string `xml:",chardata"`
  159. FieldID string `xml:"field_id"`
  160. ValidID string `xml:"valid_id"`
  161. FieldContent string `xml:"field_content"`
  162. } `xml:"block"`
  163. } `xml:"location"`
  164. } `xml:"data"`
  165. }