warawara

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

commit 8030a5ee2aa2ec6c8d442f84dd6f885c379547b2
Author: ugrnm <ultrageranium@bleu255.com>
Date:   Thu, 14 Nov 2024 23:12:36 +0100

init

Diffstat:
A.gitignore | 2++
Ago.mod | 18++++++++++++++++++
Awarawara.go | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 175 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +go.sum +warawara diff --git a/go.mod b/go.mod @@ -0,0 +1,18 @@ +module television + +go 1.22.0 + +toolchain go1.23.2 + +require ( + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/net v0.29.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/tools v0.25.0 // indirect + mellium.im/reader v0.1.0 // indirect + mellium.im/sasl v0.3.2 // indirect + mellium.im/xmlstream v0.15.4 // indirect + mellium.im/xmpp v0.22.0 // indirect +) diff --git a/warawara.go b/warawara.go @@ -0,0 +1,155 @@ +/* + warawara + + The Warawara are unborn human souls who reside in the Sea World. Once they + mature, they fly up into the sky to be born as humans. Sometimes they + bring news from questionnable RSS feeds. + +*/ + +package main + +import ( + "context" + "crypto/tls" + "encoding/xml" + "flag" + "fmt" + "mellium.im/sasl" + "mellium.im/xmpp" + "mellium.im/xmpp/dial" + "mellium.im/xmpp/jid" + "mellium.im/xmpp/muc" + "mellium.im/xmpp/stanza" + "net/http" + "os" + "time" +) + +var( + feedURL string + ctx context.Context + cancel context.CancelFunc + botJID string + botServer string + botPassword string + session *xmpp.Session + MUCJID string +) + +type itemXML struct { + Title string `xml:"title"` + Link string `xml:"link"` + Description string `xml:"description"` + Date string `xml:"pubDate"` +} + +type channelXML struct { + Items []itemXML `xml:"channel>item"` +} + +type textMessage struct { + stanza.Message + Body string `xml:"body"` +} + +func sendMUCMessage(text string) { + to := jid.MustParse(MUCJID) + var msg textMessage = textMessage{ + Message: stanza.Message{ + From: jid.MustParse(botJID), + To: to, + Type:"groupchat"}, + Body: text} + session.Encode(ctx, msg) +} + +func joinXMPP() { + ctx, cancel = context.WithCancel(context.Background()) + //defer cancel() + var err error + // TLS disabled to avoid a 3min timeout before connecting - whyyyyyyy? + dialer := dial.Dialer{NoTLS: true,} + conn, err := dialer.Dial(ctx, "tcp", jid.MustParse(botJID)) + if err != nil { + fmt.Println("Error:", botServer, err) + } + negotiator := xmpp.NewNegotiator(func(*xmpp.Session, *xmpp.StreamConfig) xmpp.StreamConfig { + return xmpp.StreamConfig{ + Features: []xmpp.StreamFeature{ + xmpp.StartTLS(&tls.Config{ + ServerName: botServer, + }), + xmpp.SASL("", botPassword, sasl.ScramSha256Plus, sasl.ScramSha1Plus, sasl.ScramSha256, sasl.ScramSha1, sasl.Plain), + xmpp.BindResource(), + }, + } + }) + session, err = xmpp.NewSession(context.TODO(), jid.MustParse(botServer), jid.MustParse(botJID), conn, 0, negotiator) + if err != nil { + fmt.Println("Error:", botJID, err) + os.Exit(1) + } + fmt.Println("Connected to", botServer, "as", botJID) + err = session.Send(ctx, stanza.Presence{Type: stanza.AvailablePresence}.Wrap(nil)) + if err != nil { + fmt.Println("Error:", err) + } +} + +// TODO: this is in a goroutine so this won't display errors +// and won't exit, need to use channels or something... +func joinMUC() { + MUC := jid.MustParse(MUCJID + "/warawara") + mucClient := muc.Client{} + _, err := mucClient.Join(ctx, MUC, session) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } +} + +func fetchFeed() { + pDateOld := "" + for { + fmt.Println("trying to fetch feed") + if feed, err := http.Get(feedURL); err != nil { + fmt.Println("Error:", err) + } else { + channel := channelXML{} + if err := xml.NewDecoder(feed.Body).Decode(&channel); err != nil { + fmt.Println("Error:", err) + } else if len(channel.Items) != 0 { + item := channel.Items[0] + pTitle := item.Title + pLink := item.Link + pDescription := item.Description + pDate := item.Date + if pDate != pDateOld { + breakingNews := "🗨️ " + pTitle + " " + pDescription + "\n" + pLink + fmt.Println(breakingNews) + sendMUCMessage(breakingNews) + pDateOld = pDate + } + } + } + time.Sleep(16 * time.Minute) // cerca's limiter kicks in at 15min + } +} + +func main() { + flag.StringVar(&feedURL, "url", "", "RSS feed URL") + flag.StringVar(&botJID, "bot", "", "bot JID") + flag.StringVar(&botServer, "server", "", "server") + flag.StringVar(&botPassword, "password", "", "bot JID password") + flag.StringVar(&MUCJID, "muc", "", "MUC JID") + flag.Parse() + + fmt.Println("🫧 w a r a w a r a 🫧\n") + fmt.Println("ctrl-c for emergency shutdown\n") + + joinXMPP() + go joinMUC() + + fetchFeed() +}