Initial Commit
This commit is contained in:
commit
fb17fa2650
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/lemmy-reply-bot.toml
|
||||
/lemmy-reply-bot
|
||||
/replied.bin
|
90
config.go
Normal file
90
config.go
Normal file
@ -0,0 +1,90 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
"go.arsenm.dev/logger/log"
|
||||
"go.arsenm.dev/pcre"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Lemmy struct {
|
||||
InstanceURL string `toml:"instanceURL"`
|
||||
Account struct {
|
||||
UserOrEmail string `toml:"userOrEmail"`
|
||||
Password string `toml:"password"`
|
||||
} `toml:"account"`
|
||||
} `toml:"lemmy"`
|
||||
Replies []Reply `toml:"reply"`
|
||||
}
|
||||
|
||||
type Reply struct {
|
||||
Regex string `toml:"regex"`
|
||||
Msg string `toml:"msg"`
|
||||
}
|
||||
|
||||
var (
|
||||
cfg = Config{}
|
||||
compiledRegexes = map[string]*pcre.Regexp{}
|
||||
)
|
||||
|
||||
func loadConfig(path string) error {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.Mode().Perm() != 0o600 {
|
||||
log.Fatal("Your config file's permissions are insecure. Please use chmod to set them to 600. Refusing to start.").Send()
|
||||
}
|
||||
|
||||
fl, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = toml.NewDecoder(fl).Decode(&cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = compileRegexes(cfg.Replies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
validateConfig()
|
||||
return nil
|
||||
}
|
||||
|
||||
func compileRegexes(replies []Reply) error {
|
||||
for _, reply := range replies {
|
||||
if _, ok := compiledRegexes[reply.Regex]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
re, err := pcre.Compile(reply.Regex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
compiledRegexes[reply.Regex] = re
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateConfig() {
|
||||
_, err := url.Parse(cfg.Lemmy.InstanceURL)
|
||||
if err != nil {
|
||||
log.Fatal("Lemmy instance URL is not valid").Err(err).Send()
|
||||
}
|
||||
|
||||
for i, reply := range cfg.Replies {
|
||||
re := compiledRegexes[reply.Regex]
|
||||
|
||||
if re.MatchString(reply.Msg) {
|
||||
log.Fatal("Regular expression matches message. This may create an infinite loop. Refusing to start.").Int("reply-index", i).Send()
|
||||
}
|
||||
}
|
||||
}
|
30
go.mod
Normal file
30
go.mod
Normal file
@ -0,0 +1,30 @@
|
||||
module go.arsenm.dev/lemmy-reply-bot
|
||||
|
||||
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/logger v0.0.0-20221007032343-cbffce4f4334
|
||||
go.arsenm.dev/pcre v0.0.0-20220530205550-74594f6c8b0e
|
||||
)
|
||||
|
||||
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/mattn/go-isatty v0.0.14 // 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
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac // indirect
|
||||
modernc.org/libc v1.16.8 // indirect
|
||||
modernc.org/mathutil v1.4.1 // indirect
|
||||
modernc.org/memory v1.1.1 // indirect
|
||||
)
|
104
go.sum
Normal file
104
go.sum
Normal file
@ -0,0 +1,104 @@
|
||||
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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
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/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=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
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/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=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
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=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
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/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=
|
||||
go.arsenm.dev/pcre v0.0.0-20220530205550-74594f6c8b0e/go.mod h1:c/E0D60A6rRLoDLh6mLUdFV9gxyth+CnXnqGHos2CAQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac h1:oN6lz7iLW/YC7un8pq+9bOLyXrprv2+DKfkJY+2LJJw=
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.36.0/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI=
|
||||
modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc=
|
||||
modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw=
|
||||
modernc.org/ccgo/v3 v3.16.6/go.mod h1:tGtX0gE9Jn7hdZFeU88slbTh1UtCYKusWOoCJuvkWsQ=
|
||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/libc v0.0.0-20220428101251-2d5f3daf273b/go.mod h1:p7Mg4+koNjc8jkqwcoFBJx7tXkpj00G77X7A72jXPXA=
|
||||
modernc.org/libc v1.16.0/go.mod h1:N4LD6DBE9cf+Dzf9buBlzVJndKr/iJHG97vGLHYnb5A=
|
||||
modernc.org/libc v1.16.1/go.mod h1:JjJE0eu4yeK7tab2n4S1w8tlWd9MxXLRzheaRnAKymU=
|
||||
modernc.org/libc v1.16.8 h1:Ux98PaOMvolgoFX/YwusFOHBnanXdGRmWgI8ciI2z4o=
|
||||
modernc.org/libc v1.16.8/go.mod h1:hYIV5VZczAmGZAnG15Vdngn5HSF5cSkbvfz2B7GRuVU=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.4.1 h1:ij3fYGe8zBF4Vu+g0oT7mB06r8sqGWKuJu1yXeR4by8=
|
||||
modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.1.1 h1:bDOL0DIDLQv7bWhP3gMvIrnoFw+Eo6F7a2QK9HPDiFU=
|
||||
modernc.org/memory v1.1.1/go.mod h1:/0wo5ibyrQiaoUoH7f9D8dnglAmILJ5/cxZlRECf+Nw=
|
||||
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
12
lemmy-reply-bot.example.toml
Normal file
12
lemmy-reply-bot.example.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[lemmy]
|
||||
instanceURL = "https://lemmy.ml"
|
||||
|
||||
[lemmy.account]
|
||||
userOrEmail = "user@example.com"
|
||||
password = "ExamplePassword123"
|
||||
|
||||
# Replies to any message starting with "!!BOT_TEST" with everything
|
||||
# after "!!BOT_TEST"
|
||||
[[reply]]
|
||||
regex = "!!BOT_TEST (.*)"
|
||||
msg = "$1"
|
67
logger.go
Normal file
67
logger.go
Normal file
@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"go.arsenm.dev/logger"
|
||||
"go.arsenm.dev/logger/log"
|
||||
)
|
||||
|
||||
func init() {
|
||||
l := logger.NewPretty(os.Stderr)
|
||||
|
||||
if os.Getenv("LEMMY_REPLY_BOT_DEBUG") == "1" {
|
||||
l.Level = logger.LogLevelDebug
|
||||
}
|
||||
|
||||
log.Logger = l
|
||||
}
|
||||
|
||||
type retryableLogger struct{}
|
||||
|
||||
func (retryableLogger) Error(msg string, v ...any) {
|
||||
msgs := splitMsgs(v)
|
||||
log.Error(msg).
|
||||
Str("method", msgs["method"].(string)).
|
||||
Stringer("url", msgs["url"].(fmt.Stringer)).
|
||||
Send()
|
||||
}
|
||||
|
||||
func (retryableLogger) Info(msg string, v ...any) {
|
||||
msgs := splitMsgs(v)
|
||||
log.Info(msg).
|
||||
Str("method", msgs["method"].(string)).
|
||||
Stringer("url", msgs["url"].(fmt.Stringer)).
|
||||
Send()
|
||||
}
|
||||
|
||||
func (retryableLogger) Debug(msg string, v ...any) {
|
||||
msgs := splitMsgs(v)
|
||||
log.Debug(msg).
|
||||
Str("method", msgs["method"].(string)).
|
||||
Stringer("url", msgs["url"].(fmt.Stringer)).
|
||||
Send()
|
||||
}
|
||||
|
||||
func (retryableLogger) Warn(msg string, v ...any) {
|
||||
msgs := splitMsgs(v)
|
||||
log.Warn(msg).
|
||||
Str("method", msgs["method"].(string)).
|
||||
Stringer("url", msgs["url"].(fmt.Stringer)).
|
||||
Send()
|
||||
}
|
||||
|
||||
func splitMsgs(v []any) map[string]any {
|
||||
out := map[string]any{}
|
||||
|
||||
for i, val := range v {
|
||||
if (i+1)%2 == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
out[val.(string)] = v[i+1]
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
186
main.go
Normal file
186
main.go
Normal file
@ -0,0 +1,186 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-retryablehttp"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/vmihailenco/msgpack/v5"
|
||||
"go.arsenm.dev/go-lemmy"
|
||||
"go.arsenm.dev/go-lemmy/types"
|
||||
"go.arsenm.dev/logger/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
configPath := pflag.StringP("config", "c", "./lemmy-reply-bot.toml", "Path to the config file")
|
||||
dryRun := pflag.BoolP("dry-run", "D", false, "Don't actually send comments, just check for matches")
|
||||
pflag.Parse()
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
err := loadConfig(*configPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error loading config file").Err(err).Send()
|
||||
}
|
||||
|
||||
rhc := retryablehttp.NewClient()
|
||||
rhc.Logger = retryableLogger{}
|
||||
|
||||
c, err := lemmy.NewWithClient(cfg.Lemmy.InstanceURL, rhc.StandardClient())
|
||||
if err != nil {
|
||||
log.Fatal("Error creating new Lemmy API client").Err(err).Send()
|
||||
}
|
||||
|
||||
err = c.Login(ctx, types.Login{
|
||||
UsernameOrEmail: cfg.Lemmy.Account.UserOrEmail,
|
||||
Password: cfg.Lemmy.Account.Password,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("Error logging in to Lemmy instance").Err(err).Send()
|
||||
}
|
||||
|
||||
log.Info("Successfully logged in to Lemmy instance").Send()
|
||||
|
||||
replyCh := make(chan replyJob, 200)
|
||||
|
||||
if !*dryRun {
|
||||
go commentReplyWorker(ctx, c, replyCh)
|
||||
}
|
||||
|
||||
commentWorker(ctx, c, replyCh)
|
||||
}
|
||||
|
||||
func commentWorker(ctx context.Context, c *lemmy.Client, replyCh chan<- replyJob) {
|
||||
repliedIDs := map[int]struct{}{}
|
||||
|
||||
repliedStore, err := os.Open("replied.bin")
|
||||
if err == nil {
|
||||
err = msgpack.NewDecoder(repliedStore).Decode(&repliedIDs)
|
||||
if err != nil {
|
||||
log.Warn("Error decoding reply store").Err(err).Send()
|
||||
}
|
||||
repliedStore.Close()
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
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
|
||||
}
|
||||
|
||||
for _, commentView := range cr.Comments {
|
||||
if _, ok := repliedIDs[commentView.Comment.ID]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for i, reply := range cfg.Replies {
|
||||
re := compiledRegexes[reply.Regex]
|
||||
if !re.MatchString(commentView.Comment.Content) {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Info("Matched comment body").
|
||||
Int("reply-index", i).
|
||||
Int("comment-id", commentView.Comment.ID).
|
||||
Send()
|
||||
|
||||
job := replyJob{
|
||||
CommentID: commentView.Comment.ID,
|
||||
PostID: commentView.Comment.PostID,
|
||||
}
|
||||
|
||||
matches := re.FindStringSubmatch(commentView.Comment.Content)
|
||||
job.Content = expandStr(reply.Msg, func(s string) string {
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(matches) > i+1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return matches[i]
|
||||
})
|
||||
|
||||
replyCh <- job
|
||||
|
||||
repliedIDs[commentView.Comment.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
case <-ctx.Done():
|
||||
repliedStore, err := os.Create("replied.bin")
|
||||
if err != nil {
|
||||
log.Warn("Error creating reply store file").Err(err).Send()
|
||||
return
|
||||
}
|
||||
|
||||
err = msgpack.NewEncoder(repliedStore).Encode(repliedIDs)
|
||||
if err != nil {
|
||||
log.Warn("Error encoding replies to reply store").Err(err).Send()
|
||||
}
|
||||
|
||||
repliedStore.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type replyJob struct {
|
||||
Content string
|
||||
CommentID int
|
||||
PostID int
|
||||
}
|
||||
|
||||
func commentReplyWorker(ctx context.Context, c *lemmy.Client, ch <-chan replyJob) {
|
||||
for {
|
||||
select {
|
||||
case reply := <-ch:
|
||||
cr, err := c.CreateComment(ctx, types.CreateComment{
|
||||
PostID: reply.PostID,
|
||||
ParentID: types.NewOptional(reply.CommentID),
|
||||
Content: reply.Content,
|
||||
})
|
||||
if err != nil {
|
||||
log.Warn("Error while trying to create new comment").Err(err).Send()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func expandStr(s string, mapping func(string) string) string {
|
||||
strings.ReplaceAll(s, "$$", "${_escaped_dollar_symbol}")
|
||||
return os.Expand(s, func(s string) string {
|
||||
if s == "_escaped_dollar_symbol" {
|
||||
return "$"
|
||||
}
|
||||
return mapping(s)
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user