Use WebSocket API
This commit is contained in:
parent
65a05a3b4b
commit
27c94b7d21
@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
|
7
go.mod
7
go.mod
@ -5,11 +5,10 @@ go 1.19
|
||||
//replace go.arsenm.dev/go-lemmy => /home/arsen/Code/go-lemmy
|
||||
|
||||
require (
|
||||
github.com/hashicorp/go-retryablehttp v0.7.1
|
||||
github.com/pelletier/go-toml/v2 v2.0.6
|
||||
github.com/spf13/pflag v1.0.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/pcre v0.0.0-20220530205550-74594f6c8b0e
|
||||
)
|
||||
@ -18,8 +17,10 @@ require (
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // 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/recws-org/recws v1.4.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
|
17
go.sum
17
go.sum
@ -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/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
|
||||
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/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
|
||||
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
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/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
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.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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
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-20221210234052-7fc04591ba51/go.mod h1:bDUHw1QFZtjygM5DdsvVnBY1xP3YmLiAzlBvZOdfe8A=
|
||||
go.arsenm.dev/go-lemmy v0.0.0-20221213021222-a4a015a4cc2a h1:iBeRt2sU8Y3pwKPKTFnkIcTwFTvifyCLUtjjsj7CaWM=
|
||||
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/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw=
|
||||
go.arsenm.dev/pcre v0.0.0-20220530205550-74594f6c8b0e h1:4XwLmFDvAKt7ZvS3E3hD2R++0wr75fBUEvXkK9dLXzk=
|
||||
|
62
main.go
62
main.go
@ -9,7 +9,6 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
"go.arsenm.dev/go-lemmy"
|
||||
@ -31,10 +30,7 @@ func main() {
|
||||
log.Fatal("Error loading config file").Err(err).Send()
|
||||
}
|
||||
|
||||
rhc := retryablehttp.NewClient()
|
||||
rhc.Logger = retryableLogger{}
|
||||
|
||||
c, err := lemmy.NewWithClient(cfg.Lemmy.InstanceURL, rhc.StandardClient())
|
||||
c, err := lemmy.NewWebSocket(cfg.Lemmy.InstanceURL)
|
||||
if err != nil {
|
||||
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()
|
||||
|
||||
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)
|
||||
|
||||
if !*dryRun {
|
||||
@ -58,7 +66,7 @@ func main() {
|
||||
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{}{}
|
||||
|
||||
repliedStore, err := os.Open("replied.bin")
|
||||
@ -74,38 +82,38 @@ func commentWorker(ctx context.Context, c *lemmy.Client, replyCh chan<- replyJob
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
cr, err := c.Comments(ctx, types.GetComments{
|
||||
Sort: types.NewOptional(types.CommentSortNew),
|
||||
Limit: types.NewOptional(200),
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("Error while trying to get comments").Err(err).Send()
|
||||
continue
|
||||
}
|
||||
case res := <-c.Responses():
|
||||
// Check which operation has been sent from the server
|
||||
switch res.Op {
|
||||
case types.UserOpCreateComment, types.UserOpEditComment:
|
||||
var cr types.CommentResponse
|
||||
err = lemmy.DecodeResponse(res.Data, &cr)
|
||||
if err != nil {
|
||||
log.Warn("Error while trying to decode comment").Err(err).Send()
|
||||
continue
|
||||
}
|
||||
|
||||
for _, commentView := range cr.Comments {
|
||||
if _, ok := repliedIDs[commentView.Comment.ID]; ok {
|
||||
if _, ok := repliedIDs[cr.CommentView.Comment.ID]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for i, reply := range cfg.Replies {
|
||||
re := compiledRegexes[reply.Regex]
|
||||
if !re.MatchString(commentView.Comment.Content) {
|
||||
if !re.MatchString(cr.CommentView.Comment.Content) {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Info("Matched comment body").
|
||||
Int("reply-index", i).
|
||||
Int("comment-id", commentView.Comment.ID).
|
||||
Int("comment-id", cr.CommentView.Comment.ID).
|
||||
Send()
|
||||
|
||||
job := replyJob{
|
||||
CommentID: commentView.Comment.ID,
|
||||
PostID: commentView.Comment.PostID,
|
||||
CommentID: cr.CommentView.Comment.ID,
|
||||
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 {
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
@ -124,7 +132,7 @@ func commentWorker(ctx context.Context, c *lemmy.Client, replyCh chan<- replyJob
|
||||
|
||||
replyCh <- job
|
||||
|
||||
repliedIDs[commentView.Comment.ID] = struct{}{}
|
||||
repliedIDs[cr.CommentView.Comment.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
case <-ctx.Done():
|
||||
@ -151,11 +159,11 @@ type replyJob struct {
|
||||
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 {
|
||||
select {
|
||||
case reply := <-ch:
|
||||
cr, err := c.CreateComment(ctx, types.CreateComment{
|
||||
err := c.Request(types.UserOpCreateComment, types.CreateComment{
|
||||
PostID: reply.PostID,
|
||||
ParentID: types.NewOptional(reply.CommentID),
|
||||
Content: reply.Content,
|
||||
@ -167,11 +175,7 @@ func commentReplyWorker(ctx context.Context, c *lemmy.Client, ch <-chan replyJob
|
||||
log.Info("Created new comment").
|
||||
Int("post-id", reply.PostID).
|
||||
Int("parent-id", reply.CommentID).
|
||||
Int("comment-id", cr.CommentView.Comment.ID).
|
||||
Send()
|
||||
|
||||
// Make sure requests don't happen too quickly
|
||||
time.Sleep(1 * time.Second)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user