warawara

XMPP bot for cerca public RSS feed
git clone http://git.permacomputing.net/repos/warawara.git # read-only access
Log | Files | Refs | README

warawara.go (4001B)


      1 /*
      2   warawara
      3 
      4   The Warawara are unborn human souls who reside in the Sea World. Once they
      5   mature, they fly up into the sky to be born as humans. Sometimes they
      6   bring news from questionnable RSS feeds.
      7 
      8 */
      9 
     10 package main
     11 
     12 import (
     13   "context"
     14   "crypto/tls"
     15   "encoding/xml"
     16   "flag"
     17   "fmt"
     18   "mellium.im/sasl"
     19   "mellium.im/xmpp"
     20   "mellium.im/xmpp/dial"
     21   "mellium.im/xmpp/jid"
     22   "mellium.im/xmpp/muc"
     23   "mellium.im/xmpp/stanza"
     24   "net/http"
     25   "os"
     26   "time"
     27 ) 
     28 
     29 var(
     30   feedURL       string
     31   ctx           context.Context
     32   cancel        context.CancelFunc
     33   botJID        string
     34   botServer     string
     35   botPassword   string
     36   session       *xmpp.Session
     37   MUCJID        string
     38 )
     39 
     40 type itemXML struct {
     41   Title         string `xml:"title"`
     42   Link          string `xml:"link"`
     43   Description   string `xml:"description"`
     44   Date          string `xml:"pubDate"`
     45 }
     46 
     47 type channelXML struct {
     48   Items []itemXML `xml:"channel>item"`
     49 }
     50 
     51 type textMessage struct {
     52   stanza.Message
     53   Body string `xml:"body"`
     54 }
     55 
     56 func sendMUCMessage(text string) {
     57   to := jid.MustParse(MUCJID)
     58   var msg textMessage = textMessage{
     59     Message: stanza.Message{
     60       From: jid.MustParse(botJID),
     61       To: to,
     62       Type:"groupchat"},
     63     Body: text}
     64     session.Encode(ctx, msg)
     65 }
     66 
     67 func joinXMPP() {
     68   ctx, cancel = context.WithCancel(context.Background())
     69   //defer cancel()
     70   var err error
     71   // TLS disabled to avoid a 3min timeout before connecting - whyyyyyyy?
     72   dialer := dial.Dialer{NoTLS: true,}
     73   conn, err := dialer.Dial(ctx, "tcp", jid.MustParse(botJID))
     74   if err != nil {
     75     fmt.Println("Error:", botServer, err)
     76   }
     77   negotiator := xmpp.NewNegotiator(func(*xmpp.Session, *xmpp.StreamConfig) xmpp.StreamConfig {
     78     return xmpp.StreamConfig{
     79       Features: []xmpp.StreamFeature{
     80         xmpp.StartTLS(&tls.Config{
     81           ServerName: botServer,
     82 	}),
     83 	xmpp.SASL("", botPassword, sasl.ScramSha256Plus, sasl.ScramSha1Plus, sasl.ScramSha256, sasl.ScramSha1, sasl.Plain),
     84 	xmpp.BindResource(),
     85       },
     86     }
     87   })
     88  session, err = xmpp.NewSession(context.TODO(), jid.MustParse(botServer), jid.MustParse(botJID), conn, 0, negotiator)
     89   if err != nil {
     90     fmt.Println("Error:", botJID, err)
     91     os.Exit(1)
     92   }
     93   fmt.Println("Connected to", botServer, "as", botJID)
     94   err = session.Send(ctx, stanza.Presence{Type: stanza.AvailablePresence}.Wrap(nil))
     95   if err != nil {
     96     fmt.Println("Error:", err)
     97   }
     98 }
     99 
    100 // TODO: this is in a goroutine so this won't display errors
    101 // and won't exit, need to use channels or something...
    102 func joinMUC() {
    103   MUC := jid.MustParse(MUCJID + "/warawara")
    104   mucClient := muc.Client{}
    105   _, err := mucClient.Join(ctx, MUC, session)
    106     if err != nil {
    107       fmt.Println("Error:", err)
    108       os.Exit(1)
    109     }
    110 }
    111 
    112 func fetchFeed() {
    113   pDateOld := ""
    114   for {
    115     fmt.Println("trying to fetch feed")
    116     if feed, err := http.Get(feedURL); err != nil {
    117       fmt.Println("Error:", err)
    118     } else {
    119       channel := channelXML{}
    120       if err := xml.NewDecoder(feed.Body).Decode(&channel); err != nil {
    121         fmt.Println("Error:", err)
    122       } else if len(channel.Items) != 0 {
    123         item := channel.Items[0]
    124         pTitle := item.Title
    125         pLink := item.Link
    126         pDescription := item.Description
    127         pDate := item.Date
    128         if pDate != pDateOld {
    129           breakingNews := "🗨️ " + pTitle + " " + pDescription + "\n" + pLink
    130           fmt.Println(breakingNews)
    131           sendMUCMessage(breakingNews)
    132           pDateOld = pDate
    133         }
    134       }
    135     }
    136     time.Sleep(16 * time.Minute) // cerca's limiter kicks in at 15min
    137   }
    138 }
    139 
    140 func main() {
    141   flag.StringVar(&feedURL, "url", "", "RSS feed URL")
    142   flag.StringVar(&botJID, "bot", "", "bot JID")
    143   flag.StringVar(&botServer, "server", "", "server")
    144   flag.StringVar(&botPassword, "password", "", "bot JID password")
    145   flag.StringVar(&MUCJID, "muc", "", "MUC JID")
    146   flag.Parse()
    147 
    148   fmt.Println("🫧 w a r a w a r a 🫧\n")
    149   fmt.Println("ctrl-c for emergency shutdown\n")
    150 
    151   joinXMPP()
    152   go joinMUC()
    153 
    154   fetchFeed()
    155 }