Use WebSocket API

This commit is contained in:
Elara 2022-12-12 18:23:29 -08:00
parent 65a05a3b4b
commit 27c94b7d21
4 changed files with 46 additions and 42 deletions

View File

@ -1,6 +1,6 @@
# Lemmy Reply Bot # Lemmy Reply Bot
This project is a simple bot that replies to comments on Lemmy. Every 10 seconds, it fetches the 200 newest comments from your configured Lemmy instance, and sees if they match any regex configured in the config file. If it finds one that does, it replies with the message corresponding to that regex. This project is a simple bot that replies to comments on Lemmy. It uses Lemmy's WebSocket to get notified of any new comments, and sees if they match any regex configured in the config file. If it finds one that does, it replies with the message corresponding to that regex.
### Configuration ### Configuration

7
go.mod
View File

@ -5,11 +5,10 @@ go 1.19
//replace go.arsenm.dev/go-lemmy => /home/arsen/Code/go-lemmy //replace go.arsenm.dev/go-lemmy => /home/arsen/Code/go-lemmy
require ( require (
github.com/hashicorp/go-retryablehttp v0.7.1
github.com/pelletier/go-toml/v2 v2.0.6 github.com/pelletier/go-toml/v2 v2.0.6
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/vmihailenco/msgpack/v5 v5.3.5 github.com/vmihailenco/msgpack/v5 v5.3.5
go.arsenm.dev/go-lemmy v0.0.0-20221210234052-7fc04591ba51 go.arsenm.dev/go-lemmy v0.0.0-20221213021222-a4a015a4cc2a
go.arsenm.dev/logger v0.0.0-20221007032343-cbffce4f4334 go.arsenm.dev/logger v0.0.0-20221007032343-cbffce4f4334
go.arsenm.dev/pcre v0.0.0-20220530205550-74594f6c8b0e go.arsenm.dev/pcre v0.0.0-20220530205550-74594f6c8b0e
) )
@ -18,8 +17,10 @@ require (
github.com/google/go-querystring v1.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.1 // indirect github.com/gookit/color v1.5.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect github.com/gorilla/websocket v1.4.2 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-isatty v0.0.14 // indirect
github.com/recws-org/recws v1.4.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect

17
go.sum
View File

@ -11,12 +11,10 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ= github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM= github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
@ -25,6 +23,8 @@ github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvI
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/recws-org/recws v1.4.0 h1:y9LLddtAicjejikNZXiaY9DQjIwcAQ82acd1XU6n0lU=
github.com/recws-org/recws v1.4.0/go.mod h1:7+NQkTmBdU98VSzkzq9/P7+X0xExioUVBx9OeRKQIkk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@ -32,7 +32,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
@ -46,8 +45,8 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.arsenm.dev/go-lemmy v0.0.0-20221210234052-7fc04591ba51 h1:RoQ4KR1kmm0jOosJpPjXUnAQShMMsjHsS+QnvKy8D5c= go.arsenm.dev/go-lemmy v0.0.0-20221213021222-a4a015a4cc2a h1:iBeRt2sU8Y3pwKPKTFnkIcTwFTvifyCLUtjjsj7CaWM=
go.arsenm.dev/go-lemmy v0.0.0-20221210234052-7fc04591ba51/go.mod h1:bDUHw1QFZtjygM5DdsvVnBY1xP3YmLiAzlBvZOdfe8A= go.arsenm.dev/go-lemmy v0.0.0-20221213021222-a4a015a4cc2a/go.mod h1:/XgX+cQqs9a2VJWwZ67wArjK2CVXWKdEEpHdAFbFRNQ=
go.arsenm.dev/logger v0.0.0-20221007032343-cbffce4f4334 h1:S98LJOBmj1pAKSw94spJk6+n8ERlBNTxi4lt5B67nQo= go.arsenm.dev/logger v0.0.0-20221007032343-cbffce4f4334 h1:S98LJOBmj1pAKSw94spJk6+n8ERlBNTxi4lt5B67nQo=
go.arsenm.dev/logger v0.0.0-20221007032343-cbffce4f4334/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw= go.arsenm.dev/logger v0.0.0-20221007032343-cbffce4f4334/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw=
go.arsenm.dev/pcre v0.0.0-20220530205550-74594f6c8b0e h1:4XwLmFDvAKt7ZvS3E3hD2R++0wr75fBUEvXkK9dLXzk= go.arsenm.dev/pcre v0.0.0-20220530205550-74594f6c8b0e h1:4XwLmFDvAKt7ZvS3E3hD2R++0wr75fBUEvXkK9dLXzk=

62
main.go
View File

@ -9,7 +9,6 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/hashicorp/go-retryablehttp"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/vmihailenco/msgpack/v5" "github.com/vmihailenco/msgpack/v5"
"go.arsenm.dev/go-lemmy" "go.arsenm.dev/go-lemmy"
@ -31,10 +30,7 @@ func main() {
log.Fatal("Error loading config file").Err(err).Send() log.Fatal("Error loading config file").Err(err).Send()
} }
rhc := retryablehttp.NewClient() c, err := lemmy.NewWebSocket(cfg.Lemmy.InstanceURL)
rhc.Logger = retryableLogger{}
c, err := lemmy.NewWithClient(cfg.Lemmy.InstanceURL, rhc.StandardClient())
if err != nil { if err != nil {
log.Fatal("Error creating new Lemmy API client").Err(err).Send() log.Fatal("Error creating new Lemmy API client").Err(err).Send()
} }
@ -49,6 +45,18 @@ func main() {
log.Info("Successfully logged in to Lemmy instance").Send() log.Info("Successfully logged in to Lemmy instance").Send()
err = c.Request(types.UserOpUserJoin, nil)
if err != nil {
log.Fatal("Error joining WebSocket user context").Err(err).Send()
}
err = c.Request(types.UserOpCommunityJoin, types.CommunityJoin{
CommunityID: 0,
})
if err != nil {
log.Fatal("Error joining WebSocket community context").Err(err).Send()
}
replyCh := make(chan replyJob, 200) replyCh := make(chan replyJob, 200)
if !*dryRun { if !*dryRun {
@ -58,7 +66,7 @@ func main() {
commentWorker(ctx, c, replyCh) commentWorker(ctx, c, replyCh)
} }
func commentWorker(ctx context.Context, c *lemmy.Client, replyCh chan<- replyJob) { func commentWorker(ctx context.Context, c *lemmy.WSClient, replyCh chan<- replyJob) {
repliedIDs := map[int]struct{}{} repliedIDs := map[int]struct{}{}
repliedStore, err := os.Open("replied.bin") repliedStore, err := os.Open("replied.bin")
@ -74,38 +82,38 @@ func commentWorker(ctx context.Context, c *lemmy.Client, replyCh chan<- replyJob
defer ticker.Stop() defer ticker.Stop()
for { for {
select { select {
case <-ticker.C: case res := <-c.Responses():
cr, err := c.Comments(ctx, types.GetComments{ // Check which operation has been sent from the server
Sort: types.NewOptional(types.CommentSortNew), switch res.Op {
Limit: types.NewOptional(200), case types.UserOpCreateComment, types.UserOpEditComment:
}) var cr types.CommentResponse
if err != nil { err = lemmy.DecodeResponse(res.Data, &cr)
log.Warn("Error while trying to get comments").Err(err).Send() if err != nil {
continue log.Warn("Error while trying to decode comment").Err(err).Send()
} continue
}
for _, commentView := range cr.Comments { if _, ok := repliedIDs[cr.CommentView.Comment.ID]; ok {
if _, ok := repliedIDs[commentView.Comment.ID]; ok {
continue continue
} }
for i, reply := range cfg.Replies { for i, reply := range cfg.Replies {
re := compiledRegexes[reply.Regex] re := compiledRegexes[reply.Regex]
if !re.MatchString(commentView.Comment.Content) { if !re.MatchString(cr.CommentView.Comment.Content) {
continue continue
} }
log.Info("Matched comment body"). log.Info("Matched comment body").
Int("reply-index", i). Int("reply-index", i).
Int("comment-id", commentView.Comment.ID). Int("comment-id", cr.CommentView.Comment.ID).
Send() Send()
job := replyJob{ job := replyJob{
CommentID: commentView.Comment.ID, CommentID: cr.CommentView.Comment.ID,
PostID: commentView.Comment.PostID, PostID: cr.CommentView.Comment.PostID,
} }
matches := re.FindStringSubmatch(commentView.Comment.Content) matches := re.FindStringSubmatch(cr.CommentView.Comment.Content)
job.Content = expandStr(reply.Msg, func(s string) string { job.Content = expandStr(reply.Msg, func(s string) string {
i, err := strconv.Atoi(s) i, err := strconv.Atoi(s)
if err != nil { if err != nil {
@ -124,7 +132,7 @@ func commentWorker(ctx context.Context, c *lemmy.Client, replyCh chan<- replyJob
replyCh <- job replyCh <- job
repliedIDs[commentView.Comment.ID] = struct{}{} repliedIDs[cr.CommentView.Comment.ID] = struct{}{}
} }
} }
case <-ctx.Done(): case <-ctx.Done():
@ -151,11 +159,11 @@ type replyJob struct {
PostID int PostID int
} }
func commentReplyWorker(ctx context.Context, c *lemmy.Client, ch <-chan replyJob) { func commentReplyWorker(ctx context.Context, c *lemmy.WSClient, ch <-chan replyJob) {
for { for {
select { select {
case reply := <-ch: case reply := <-ch:
cr, err := c.CreateComment(ctx, types.CreateComment{ err := c.Request(types.UserOpCreateComment, types.CreateComment{
PostID: reply.PostID, PostID: reply.PostID,
ParentID: types.NewOptional(reply.CommentID), ParentID: types.NewOptional(reply.CommentID),
Content: reply.Content, Content: reply.Content,
@ -167,11 +175,7 @@ func commentReplyWorker(ctx context.Context, c *lemmy.Client, ch <-chan replyJob
log.Info("Created new comment"). log.Info("Created new comment").
Int("post-id", reply.PostID). Int("post-id", reply.PostID).
Int("parent-id", reply.CommentID). Int("parent-id", reply.CommentID).
Int("comment-id", cr.CommentView.Comment.ID).
Send() Send()
// Make sure requests don't happen too quickly
time.Sleep(1 * time.Second)
case <-ctx.Done(): case <-ctx.Done():
return return
} }