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