Merge pull request 'switch to botlib, waitgroups' (#1) from botlib into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing

Reviewed-on: #1
This commit is contained in:
stryan 2022-07-17 18:17:22 -04:00
commit 81ab20e2fa
8 changed files with 124 additions and 247 deletions

View File

@ -1,48 +0,0 @@
package main
import (
"fmt"
"log"
"strings"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/id"
)
func newMatrixClient(config *botConfig) *mautrix.Client {
fmt.Println("Logging into", config.Homeserver, "as", config.Username)
var client *mautrix.Client
var err error
//make sure username is lower case otherwise token login breaks
uname := strings.ToLower(config.Username)
if config.Token == "" {
client, err = mautrix.NewClient(config.Homeserver, "", "")
if err != nil {
panic(err)
}
} else {
log.Println("using token login")
client, err = mautrix.NewClient(config.Homeserver, id.NewUserID(uname, config.Domain), config.Token)
if err != nil {
panic(err)
}
}
client.Store = NewLazyMemStore(config.Statefile)
if config.Token == "" {
loginRes, err := client.Login(&mautrix.ReqLogin{
Type: "m.login.password",
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: uname},
Password: config.Password,
StoreCredentials: true,
})
if err != nil {
panic(err)
}
config.Token = loginRes.AccessToken
log.Println("Login succesful, saving access_token to config file")
writeConfig(config)
} else {
log.Println("skipping login since token provided")
}
return client
}

View File

@ -1,50 +0,0 @@
package main
import (
"io/ioutil"
"log"
"os"
"gopkg.in/yaml.v2"
)
type botConfig struct {
Homeserver string `yaml:"homeserver"`
Domain string `yaml:"domain"`
Dimension string `yaml:"dimension"`
Username string `yaml:"username"`
Password string `yaml:"password"`
Statefile string `yaml:"statefile"`
Token string `yaml:"token"`
filename string
}
func loadConfig(filename string) *botConfig {
yamlFile, err := ioutil.ReadFile(filename)
cnf := &botConfig{}
if err == nil {
err = yaml.Unmarshal(yamlFile, cnf)
} else {
panic(err)
}
if err != nil {
panic(err)
}
cnf.filename = filename
return cnf
}
func writeConfig(cnf *botConfig) {
file, err := os.OpenFile(cnf.filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("error opening/creating file: %v", err)
}
defer file.Close()
enc := yaml.NewEncoder(file)
err = enc.Encode(cnf)
if err != nil {
log.Fatalf("error encoding: %v", err)
}
}

7
go.mod
View File

@ -3,12 +3,13 @@ module git.saintnet.tech/stryan/nunbot
go 1.18 go 1.18
require ( require (
gopkg.in/yaml.v2 v2.4.0 git.saintnet.tech/stryan/matrixbotlib v0.1.2
maunium.net/go/mautrix v0.11.0 maunium.net/go/mautrix v0.11.0
) )
require ( require (
github.com/stretchr/testify v1.8.0 // indirect github.com/stretchr/testify v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 // indirect golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/net v0.0.0-20220513224357-95641704303c // indirect golang.org/x/net v0.0.0-20220708220712-1185a9018129 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
) )

12
go.sum
View File

@ -1,3 +1,7 @@
git.saintnet.tech/stryan/matrixbotlib v0.1.1 h1:8TbWEtgmt6GH2MjZZa9938DClJgvh6aEdY7Knj4Cpmk=
git.saintnet.tech/stryan/matrixbotlib v0.1.1/go.mod h1:Id9JBCt3YqOViUwSVXQomGysaWPVyoDk2fbmOf9L264=
git.saintnet.tech/stryan/matrixbotlib v0.1.2 h1:kmldNm1xyKm2t8vPUqyQ+7AvhQ0CeT/mpIhTCOvehh8=
git.saintnet.tech/stryan/matrixbotlib v0.1.2/go.mod h1:Id9JBCt3YqOViUwSVXQomGysaWPVyoDk2fbmOf9L264=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -8,10 +12,10 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20220513224357-95641704303c h1:nF9mHSvoKBLkQNQhJZNsc66z2UzAMUbLGjC95CF3pU0= golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

View File

@ -1,85 +0,0 @@
package main
import (
"bytes"
"encoding/gob"
"os"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/id"
)
type LazyMemStore struct {
mem *mautrix.InMemoryStore
NextBatch map[id.UserID]string
saveFile string
}
func NewLazyMemStore(fileloc string) *LazyMemStore {
return &LazyMemStore{
mem: mautrix.NewInMemoryStore(),
NextBatch: make(map[id.UserID]string),
saveFile: fileloc,
}
}
func (l *LazyMemStore) SaveFilterID(userID id.UserID, filterID string) {
l.mem.SaveFilterID(userID, filterID)
}
func (l *LazyMemStore) LoadFilterID(userID id.UserID) string {
return l.mem.LoadFilterID(userID)
}
func (l *LazyMemStore) SaveNextBatch(userID id.UserID, nextBatchToken string) {
b := new(bytes.Buffer)
l.NextBatch[userID] = nextBatchToken
e := gob.NewEncoder(b)
err := e.Encode(l.NextBatch)
if err != nil {
panic(err)
}
if err := os.WriteFile(l.saveFile, b.Bytes(), 0666); err != nil {
panic(err)
}
}
func (l *LazyMemStore) LoadNextBatch(userID id.UserID) string {
dat, err := os.ReadFile(l.saveFile)
if err != nil {
if os.IsNotExist(err) {
b := new(bytes.Buffer)
e := gob.NewEncoder(b)
err := e.Encode(l.NextBatch)
if err != nil {
panic(err)
}
if err := os.WriteFile(l.saveFile, b.Bytes(), 0666); err != nil {
panic(err)
}
dat, err = os.ReadFile(l.saveFile)
if err != nil {
panic(err)
}
} else {
panic(err)
}
}
d := gob.NewDecoder(bytes.NewBuffer(dat))
err = d.Decode(&l.NextBatch)
if err != nil {
panic(err)
}
return l.NextBatch[userID]
}
func (l *LazyMemStore) SaveRoom(room *mautrix.Room) {
l.mem.SaveRoom(room)
}
func (l *LazyMemStore) LoadRoom(roomID id.RoomID) *mautrix.Room {
return l.mem.LoadRoom(roomID)
}

99
main.go
View File

@ -1,11 +1,12 @@
package main package main
import ( import (
"fmt"
"log" "log"
"strings" "os"
"time" "os/signal"
"sync"
mbl "git.saintnet.tech/stryan/matrixbotlib"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
) )
@ -17,24 +18,29 @@ var GitTag string
var GitCommit string var GitCommit string
func main() { func main() {
conf := loadConfig("config.yaml") conf, err := mbl.LoadMatrixClientConfig("config.yaml")
matrixClient := newMatrixClient(conf) if err != nil {
//redditClient := newRedditClient(conf) panic(err)
}
store := mbl.NewLazyMemStore(conf.Statefile)
matrixClient, err := mbl.NewMatrixClient(conf, store)
if err != nil {
panic(err)
}
syncer := matrixClient.Syncer.(*mautrix.DefaultSyncer) syncer := matrixClient.Syncer.(*mautrix.DefaultSyncer)
mbl.AcceptAllRoomInvites(matrixClient)
syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) { syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) {
if evt.Sender == matrixClient.UserID { if evt.Sender == matrixClient.UserID {
return //ignore events from self return //ignore events from self
} }
fmt.Printf("<%[1]s> %[4]s (%[2]s/%[3]s)\n", evt.Sender, evt.Type.String(), evt.ID, evt.Content.AsMessage().Body) cmd, err := mbl.ParseCommand(evt, "nun")
body := evt.Content.AsMessage().Body if err != nil {
bodyS := strings.Split(body, " ") if err != mbl.ErrCmdParseNoPrefix {
if bodyS[0] != "!nun" { log.Printf("invalid command: %v", err)
}
return return
} }
if len(bodyS) < 2 { switch cmd[1] {
return //nothing to parse
}
switch bodyS[1] {
case "version": case "version":
// print version // print version
if GitTag != "" { if GitTag != "" {
@ -43,48 +49,43 @@ func main() {
matrixClient.SendText(evt.RoomID, "NunBot version "+GitCommit) matrixClient.SendText(evt.RoomID, "NunBot version "+GitCommit)
} }
case "help": case "help":
matrixClient.SendText(evt.RoomID, "Supported commands: version,stats") matrixClient.SendText(evt.RoomID, "Supported commands: version, help")
default: default:
//command not found //command not found
matrixClient.SendText(evt.RoomID, "command not recognized") matrixClient.SendText(evt.RoomID, "command not recognized")
} }
}) })
syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) { var wg sync.WaitGroup
fmt.Printf("<%[1]s> %[4]s (%[2]s/%[3]s)\n", evt.Sender, evt.Type.String(), evt.ID, evt.Content.AsMessage().Body) stop := make(chan bool)
if evt.Content.AsMember().Membership.IsInviteOrJoin() { c := make(chan os.Signal, 1)
_, err := matrixClient.JoinRoomByID(evt.RoomID) signal.Notify(c, os.Interrupt)
if err != nil {
fmt.Printf("error joining room %v", evt.RoomID)
} else {
fmt.Printf("joined room %v", evt.RoomID)
}
}
})
var curPost post
go func() { go func() {
for { <-c
time.Sleep(30 * time.Second) log.Println("trying to shutdown cleanly")
newPost := getNewestPost("LittleNuns") matrixClient.StopSync()
if curPost.Title != newPost.Title { stop <- true
curPost = newPost
roomResp, err := matrixClient.JoinedRooms()
if err != nil {
log.Printf("error getting joined rooms: %v", err)
continue
}
rooms := roomResp.JoinedRooms
for _, room := range rooms {
matrixClient.SendText(room, fmt.Sprintf("%v\n%v", curPost.Title, curPost.Link))
}
}
}
}() }()
err := matrixClient.Sync() nun := newNunWatch(stop, matrixClient)
if err != nil { wg.Add(1)
panic(err) go func() {
}
err = matrixClient.Sync()
if err != nil {
log.Println(err)
}
wg.Done()
log.Println("matrix client shutdown")
}()
wg.Add(1)
go func() {
nun.Main()
wg.Done()
log.Println("nun watch shutdown")
}()
log.Println("nunbot running")
wg.Wait()
log.Println("shutting down")
} }

57
nun.go Normal file
View File

@ -0,0 +1,57 @@
package main
import (
"fmt"
"log"
"time"
"maunium.net/go/mautrix"
)
type nunWatch struct {
fail int
curPost post
stop chan bool
client *mautrix.Client
}
func newNunWatch(stop chan bool, c *mautrix.Client) *nunWatch {
return &nunWatch{0, post{}, stop, c}
}
func (n *nunWatch) Main() {
ticker := time.NewTicker(30 * time.Second)
for {
select {
case <-n.stop:
return
case <-ticker.C:
if n.fail > 5 {
log.Fatal("fail count too high; ending loop")
return
}
newPost, err := getNewestPost("LittleNuns")
if err != nil {
log.Printf("error getting newest post: %v", err)
log.Println("skipping this cycle, incrementing fail count")
n.fail++
continue
}
n.fail = 0
if n.curPost.Title != newPost.Title {
n.curPost = newPost
roomResp, err := n.client.JoinedRooms()
if err != nil {
log.Printf("error getting joined rooms: %v", err)
continue
}
rooms := roomResp.JoinedRooms
for _, room := range rooms {
n.client.SendText(room, fmt.Sprintf("%v\n%v", n.curPost.Title, n.curPost.Link))
}
}
}
}
}

View File

@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"time" "time"
) )
@ -15,7 +14,7 @@ type post struct {
Link string Link string
} }
func getNewestPost(subreddit string) post { func getNewestPost(subreddit string) (post, error) {
var resp redditResp var resp redditResp
//building request from scratch because reddit api is weird //building request from scratch because reddit api is weird
url := fmt.Sprintf("https://www.reddit.com/r/%v/new.json?sort=new&limit=1", subreddit) url := fmt.Sprintf("https://www.reddit.com/r/%v/new.json?sort=new&limit=1", subreddit)
@ -29,26 +28,24 @@ func getNewestPost(subreddit string) post {
} }
res, err := defaultClient.Do(req) res, err := defaultClient.Do(req)
if err != nil { if err != nil {
log.Printf("Got %v, retrying in 5s", err)
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
res, err = http.Get(url) res, err = http.Get(url)
if err != nil { if err != nil {
log.Printf("Got %v, not trying again", err) return post{}, err
return post{}
} }
} }
defer res.Body.Close() defer res.Body.Close()
body, readErr := ioutil.ReadAll(res.Body) body, readErr := ioutil.ReadAll(res.Body)
if readErr != nil { if readErr != nil {
log.Fatalf("error reading reddit resp: %v", readErr) return post{}, err
} }
err = json.Unmarshal(body, &resp) err = json.Unmarshal(body, &resp)
if err != nil { if err != nil {
log.Fatalf("unmarshal error: %v", err) return post{}, err
} }
return post{ return post{
Title: resp.Data.Children[0].Data.Title, Title: resp.Data.Children[0].Data.Title,
Link: resp.Data.Children[0].Data.URL, Link: resp.Data.Children[0].Data.URL,
} }, nil
} }