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 }