Compare commits

...

24 Commits

Author SHA1 Message Date
stryan 40ec2610df update logging, minor fixes
continuous-integration/drone/push Build is passing Details
2023-10-10 21:01:01 -04:00
stryan 80adf6214e extra error checking 2023-03-20 11:40:07 -04:00
stryan 9c68067fc3 clarify errors 2022-11-21 18:21:01 -05:00
stryan 29ff95e43e oob check 2022-11-12 12:55:14 -05:00
stryan 6c9e3f15ae try other thing
continuous-integration/drone/push Build is passing Details
2022-09-02 19:57:34 -04:00
stryan 8fc17bd9c9 Maybe don't create an infinite loop 2022-09-01 13:36:33 -04:00
stryan 2059932ad9 bit more logging, reset on timer reset
continuous-integration/drone/push Build is passing Details
2022-08-21 16:43:52 -04:00
stryan 5ad3582a85 better backoff, use filters
continuous-integration/drone/push Build is passing Details
2022-08-10 14:14:52 -04:00
stryan 6f7e56fd36 professionals have standards pt2
continuous-integration/drone/push Build is passing Details
2022-08-07 15:29:29 -04:00
stryan fb107d0de7 professionals have standards
continuous-integration/drone/push Build is passing Details
2022-08-07 15:29:13 -04:00
stryan 529593e26d docker service file
continuous-integration/drone/push Build is passing Details
2022-08-07 15:28:28 -04:00
stryan 1a15e7ec89 add dockerignore
continuous-integration/drone/push Build is passing Details
2022-08-05 21:49:35 -04:00
stryan 9e270ed1c6 add dockerfile 2022-08-03 15:45:12 -04:00
stryan 5a940f0297 timer command 2022-08-03 15:44:59 -04:00
stryan ba048c35da swap away from lazymemstore 2022-08-03 15:44:43 -04:00
stryan 0d013bfe28 custom backoff timer 2022-08-02 13:02:12 -04:00
stryan 24c84dce0f get rid of debug
continuous-integration/drone/tag Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-07-29 16:04:18 -04:00
stryan fe28bef7b9 use account data
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2022-07-29 16:02:23 -04:00
stryan 5926c09585 reddit api worse then others, use longer wait and try again
continuous-integration/drone/push Build is passing Details
2022-07-18 11:20:29 -04:00
stryan cd2cfc71cc typo
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2022-07-17 18:26:35 -04:00
stryan be3f76171d dependency bump
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2022-07-17 18:25:01 -04:00
stryan 671b2f8a43 check /etc/nunbot
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2022-07-17 18:21:06 -04:00
stryan 81ab20e2fa Merge pull request 'switch to botlib, waitgroups' (#1) from botlib into master
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
Reviewed-on: #1
2022-07-17 18:17:22 -04:00
stryan 45b50e4733 switch to botlib, waitgroups
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2022-07-17 18:15:29 -04:00
11 changed files with 342 additions and 277 deletions

7
.dockerignore Normal file
View File

@ -0,0 +1,7 @@
.git
.gitignore
Dockerfile*
README.md
LICENSE
*.yaml
statefile

13
Containerfile Normal file
View File

@ -0,0 +1,13 @@
FROM golang:1.18 as builder
WORKDIR /go/src/app
COPY . .
RUN apt update && apt upgrade -y
RUN go build
FROM alpine:latest as final
WORKDIR /srv/
RUN mkdir /srv/nunbot
RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2
COPY --from=builder /go/src/app/nunbot /srv/nunbot/
CMD ["/srv/nunbot/nunbot"]

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)
}
}

20
go.mod
View File

@ -3,12 +3,24 @@ module git.saintnet.tech/stryan/nunbot
go 1.18
require (
gopkg.in/yaml.v2 v2.4.0
git.saintnet.tech/stryan/matrixbotlib v0.1.3
maunium.net/go/mautrix v0.11.0
)
require (
github.com/stretchr/testify v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 // indirect
golang.org/x/net v0.0.0-20220513224357-95641704303c // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v0.8.0 // indirect
github.com/charmbracelet/log v0.2.5 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
golang.org/x/net v0.0.0-20220708220712-1185a9018129 // indirect
golang.org/x/sys v0.7.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

40
go.sum
View File

@ -1,17 +1,49 @@
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=
git.saintnet.tech/stryan/matrixbotlib v0.1.3 h1:SJ80s6J3/wBwHCj78mXmFxHhLc/EQ9Tt6O/Y8hsRs2Q=
git.saintnet.tech/stryan/matrixbotlib v0.1.3/go.mod h1:Id9JBCt3YqOViUwSVXQomGysaWPVyoDk2fbmOf9L264=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU=
github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU=
github.com/charmbracelet/log v0.2.5 h1:1yVvyKCKVV639RR4LIq1iy1Cs1AKxuNO+Hx2LJtk7Wc=
github.com/charmbracelet/log v0.2.5/go.mod h1:nQGK8tvc4pS9cvVEH/pWJiZ50eUq1aoXUOjGpXvdD0k=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
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/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-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20220513224357-95641704303c h1:nF9mHSvoKBLkQNQhJZNsc66z2UzAMUbLGjC95CF3pU0=
golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

View File

@ -0,0 +1,14 @@
[Unit]
Description=NunBot: Bringing nuns, ducks and love to Matrix
Requires=docker.service
After=docker.service
[Service]
Restart=always
ExecStart=/usr/bin/docker run --name nunbot --mount=type=bind,source=/etc/nunbot/config.yaml,destination=/etc/nunbot/config.yaml git.saintnet.tech/stryan/nunbot
ExecStop=/usr/bin/docker rm -f nunbot
[Install]
WantedBy=default.target

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)
}

197
main.go
View File

@ -1,90 +1,149 @@
package main
import (
"fmt"
"log"
"os"
"os/signal"
"strconv"
"strings"
"time"
"sync"
"github.com/charmbracelet/log"
mbl "git.saintnet.tech/stryan/matrixbotlib"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
//GitTag is current git tag
// GitTag is current git tag
var GitTag string
//GitCommit is current git commit
// GitCommit is current git commit
var GitCommit string
func main() {
conf := loadConfig("config.yaml")
matrixClient := newMatrixClient(conf)
//redditClient := newRedditClient(conf)
syncer := matrixClient.Syncer.(*mautrix.DefaultSyncer)
syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) {
if evt.Sender == matrixClient.UserID {
return //ignore events from self
conf, err := mbl.LoadMatrixClientConfig("config.yaml")
if err != nil {
log.Info("no local config found, checking /etc/nunbot")
conf, err = mbl.LoadMatrixClientConfig("/etc/nunbot/config.yaml")
if err != nil {
panic(err)
}
fmt.Printf("<%[1]s> %[4]s (%[2]s/%[3]s)\n", evt.Sender, evt.Type.String(), evt.ID, evt.Content.AsMessage().Body)
body := evt.Content.AsMessage().Body
bodyS := strings.Split(body, " ")
if bodyS[0] != "!nun" {
return
}
if len(bodyS) < 2 {
return //nothing to parse
}
switch bodyS[1] {
case "version":
// print version
if GitTag != "" {
matrixClient.SendText(evt.RoomID, "NunBot version "+GitTag)
} else {
matrixClient.SendText(evt.RoomID, "NunBot version "+GitCommit)
}
case "help":
matrixClient.SendText(evt.RoomID, "Supported commands: version,stats")
default:
//command not found
matrixClient.SendText(evt.RoomID, "command not recognized")
}
})
syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) {
fmt.Printf("<%[1]s> %[4]s (%[2]s/%[3]s)\n", evt.Sender, evt.Type.String(), evt.ID, evt.Content.AsMessage().Body)
if evt.Content.AsMember().Membership.IsInviteOrJoin() {
_, err := matrixClient.JoinRoomByID(evt.RoomID)
if err != nil {
fmt.Printf("error joining room %v", evt.RoomID)
} else {
fmt.Printf("joined room %v", evt.RoomID)
}
}
})
}
dataFilter := &mautrix.Filter{
AccountData: mautrix.FilterPart{
Limit: 20,
NotTypes: []event.Type{
event.NewEventType("nun.batch"),
},
},
}
var curPost post
go func() {
for {
time.Sleep(30 * time.Second)
newPost := getNewestPost("LittleNuns")
if curPost.Title != newPost.Title {
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()
matrixClient, err := mbl.NewMatrixClient(conf, nil)
if err != nil {
panic(err)
}
store := mautrix.NewAccountDataStore("nun.batch", matrixClient)
fID, err := matrixClient.CreateFilter(dataFilter)
if err != nil {
// don't want to continue if we can't keep state
panic(err)
}
var nun *nunWatch
uid := id.NewUserID(strings.ToLower(conf.Username), strings.ToLower(conf.Domain))
store.SaveFilterID(uid, fID.FilterID)
matrixClient.Store = store
syncer := matrixClient.Syncer.(*mautrix.DefaultSyncer)
mbl.AcceptAllRoomInvites(matrixClient)
syncer.OnEventType(event.EventMessage, func(_ mautrix.EventSource, evt *event.Event) {
if evt.Sender == matrixClient.UserID {
return // ignore events from self
}
cmd, err := mbl.ParseCommand(evt, "nun")
if err != nil {
if err != mbl.ErrCmdParseNoPrefix {
log.Printf("invalid command: %v", err)
}
return
}
switch cmd[0] {
case "version":
// print version
if GitTag != "" {
_, err := matrixClient.SendText(evt.RoomID, "NunBot version "+GitTag)
if err != nil {
log.Warn(err)
}
} else {
_, err := matrixClient.SendText(evt.RoomID, "NunBot version "+GitCommit)
if err != nil {
log.Warn(err)
}
}
case "timer":
if len(cmd) <= 1 {
_, err := matrixClient.SendText(evt.RoomID, "!nun timer [time in minutes to wait between tries]")
if err != nil {
log.Warn(err)
}
return
}
time, err := strconv.Atoi(cmd[1])
if err != nil {
_, err = matrixClient.SendText(evt.RoomID, "!nun timer needs integer timer")
if err != nil {
log.Warn(err)
}
return
}
nun.SetTimer(time)
_, err = matrixClient.SendText(evt.RoomID, "timer set")
if err != nil {
log.Warn(err)
}
case "help":
_, err := matrixClient.SendText(evt.RoomID, "Supported commands: version, help,timer")
if err != nil {
log.Warn(err)
}
default:
// command not found
_, err := matrixClient.SendText(evt.RoomID, "command not recognized")
if err != nil {
log.Warn(err)
}
}
})
var wg sync.WaitGroup
stop := make(chan bool)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
<-c
log.Info("trying to shutdown cleanly")
matrixClient.StopSync()
stop <- true
}()
nun = newNunWatch(stop, matrixClient, 2)
wg.Add(1)
go func() {
err = matrixClient.Sync()
if err != nil {
log.Warn(err)
}
wg.Done()
log.Info("matrix client shutdown")
}()
wg.Add(1)
go func() {
nun.Main()
wg.Done()
log.Info("nun watch shutdown")
}()
log.Info("nunbot running")
wg.Wait()
log.Info("shutting down")
}

97
nun.go Normal file
View File

@ -0,0 +1,97 @@
package main
import (
"fmt"
"time"
"github.com/charmbracelet/log"
"maunium.net/go/mautrix"
)
type nunWatch struct {
fail int
curPost post
stop chan bool
client *mautrix.Client
timer int
}
func newNunWatch(stop chan bool, c *mautrix.Client, t int) *nunWatch {
return &nunWatch{0, post{}, stop, c, t}
}
func (n *nunWatch) SetTimer(t int) {
n.timer = t
n.fail = 0
}
func (n *nunWatch) Main() {
ticker := time.NewTicker(time.Duration(n.timer) * time.Minute)
curT := n.timer
for {
select {
case <-n.stop:
return
case <-ticker.C:
newPost, err := getNewestPost("LittleNuns")
if err != nil {
log.Warn("error getting newest post:", "error", err)
log.Warn("skipping this cycle, incrementing fail count")
n.fail++
if n.fail > 5 {
log.Fatal("failgithub.com/charmbracelet/log count too high; ending loop")
return
} else if n.fail > 3 {
log.Warnf("over three failures, increasing tick time to %v minutes", 3*n.timer)
ticker.Reset(3 * time.Duration(n.timer) * time.Minute)
break
} else {
log.Warnf("fail count %v", n.fail)
break
}
}
n.fail = 0
ticker.Reset(time.Duration(n.timer) * time.Minute)
if curT != n.timer {
log.Info("updating nunwatch timer")
ticker.Reset(time.Duration(n.timer) * time.Minute)
n.fail = 0
curT = n.timer
}
if n.curPost.Title != newPost.Title {
n.curPost = newPost
var cur post
err := n.client.GetAccountData("nun.newest", &cur)
if err != nil {
log.Warnf("tried to get saved account data but failed %v", err)
cur.Number = 0
}
roomResp, err := n.client.JoinedRooms()
if err != nil {
log.Warnf("error getting joined rooms: %v", err)
break
}
rooms := roomResp.JoinedRooms
for _, room := range rooms {
if n.curPost.Number != cur.Number {
log.Infof("posting %v", n.curPost.Number)
_, err := n.client.SendText(room, fmt.Sprintf("%v\n%v", n.curPost.Title, n.curPost.Link))
if err != nil {
log.Printf("err sending to room: %v", err)
}
if n.curPost.Number > 0 {
err := n.client.SetAccountData("nun.newest", n.curPost)
if err != nil {
log.Warnf("error saving current post: %v", err)
}
}
}
}
}
}
}
}

View File

@ -3,52 +3,66 @@ package main
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"io"
"net/http"
"regexp"
"strconv"
"time"
)
type post struct {
Title string
Link string
Title string
Link string
Number int
}
func getNewestPost(subreddit string) post {
func getNewestPost(subreddit string) (post, error) {
var resp redditResp
//building request from scratch because reddit api is weird
re := regexp.MustCompile("([0-9]+)")
// 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)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("User-Agent", "Custom Agent")
req.Header.Set("Host", "reddit.com")
var defaultClient = http.Client{
defaultClient := http.Client{
Transport: &http.Transport{
TLSNextProto: map[string]func(authority string, c *tls.Conn) http.RoundTripper{},
},
}
res, err := defaultClient.Do(req)
if err != nil {
log.Printf("Got %v, retrying in 5s", err)
time.Sleep(5 * time.Second)
res, err = http.Get(url)
if err != nil {
log.Printf("Got %v, not trying again", err)
return post{}
return post{}, err
}
}
defer res.Body.Close()
body, readErr := ioutil.ReadAll(res.Body)
body, readErr := io.ReadAll(res.Body)
if readErr != nil {
log.Fatalf("error reading reddit resp: %v", readErr)
return post{}, err
}
err = json.Unmarshal(body, &resp)
if err != nil {
log.Fatalf("unmarshal error: %v", err)
return post{}, err
}
if len(resp.Data.Children) < 1 {
return post{}, errors.New("bad data from reddit")
}
reSearch := re.FindStringSubmatch(resp.Data.Children[0].Data.Title)
if len(reSearch) == 0 {
return post{}, fmt.Errorf("something strange in data from reddit")
}
numS := reSearch[0]
num, err := strconv.Atoi(numS)
if err != nil || numS == "" {
num = -1
}
return post{
Title: resp.Data.Children[0].Data.Title,
Link: resp.Data.Children[0].Data.URL,
}
Title: resp.Data.Children[0].Data.Title,
Link: resp.Data.Children[0].Data.URL,
Number: num,
}, nil
}