From dec4c4fcd76bcb157821c2f383a77ef7f12ada31 Mon Sep 17 00:00:00 2001 From: terminaldweller Date: Fri, 30 Aug 2024 22:29:03 -0400 Subject: added rss functionality to milla, watchlists now allow you to give them a color so they can actually show you what they matched, bunch of other changes --- .golangci.yml | 1 + README.md | 89 ++++++++++++++++++++++++----- go.mod | 7 +++ go.sum | 21 +++++++ main.go | 74 ++++++++++++++++++++++++ plugins.go | 1 + rss.go | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ types.go | 58 +++++++++++++------ 8 files changed, 398 insertions(+), 32 deletions(-) create mode 100644 rss.go diff --git a/.golangci.yml b/.golangci.yml index 220bddb..7e3e303 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -27,3 +27,4 @@ linters-settings: - github.com/yuin/gluare - gitlab.com/megalithic-llc/gluasocket - github.com/layeh/gopher-json + - github.com/mmcdole/gofeed diff --git a/README.md b/README.md index f400d8d..23c19dd 100644 --- a/README.md +++ b/README.md @@ -175,23 +175,27 @@ ircChannels = ["#channel1", "#channel2"] Please note that the bot does not have to join a channel to be usable. One can simply query the bot directly as well.
-### databaseUser +#### databaseUser Name of the database user. -### databasePassword +#### databasePassword Password for the database user. -### databaseAddress +#### databaseAddress Address of the database. -### databaseName +#### databaseName Name of the database. -### ircProxy +#### scrapeChannels + +List of channels that the bot will scrape into a database table. You can later on use these databases for the custom commands.
+ +#### ircProxy Determines which proxy to use to connect to the IRC network: @@ -199,7 +203,7 @@ Determines which proxy to use to connect to the IRC network: ircProxy = "socks5://127.0.0.1:9050" ``` -### llmProxy +#### llmProxy Determines which proxy to use to connect to the LLM endpoint: @@ -207,30 +211,38 @@ Determines which proxy to use to connect to the LLM endpoint: llmProxy = "socks5://127.0.0.1:9050" ``` -### ircdName +#### ircdName Name of the milla instance, must be unique across all instances. -### adminOnly +#### adminOnly Milla will only answer if the nick is in the admin list. -### webIRCGateway +#### webIRCGateway webirc gateway to use. -### webIRCHostname +#### webIRCHostname webirc hostname to use. -### webIRCPassword +#### webIRCPassword webirc password to use. -### webIRCAddress +#### webIRCAddress webirc address to use. +#### rssFile + +The file that contains the rss feeeds. + +#### channel + +The channel to send the rss feeds to. + ### plugins A list of plugins to load:`plugins = ["./plugins/rss.lua", "./plugins/test.lua"]` @@ -277,6 +289,32 @@ fgColor = 0 bgColor = 28 ``` +## RSS + +The rss file is self-explanatory. Here's an example: + +```json +{ + "feeds": [ + { + "name": "one", + "url": "http://feeds.feedburner.com/crunchyroll/rss", + "proxy": "socks5://172.17.0.1:9007", + "userAgent": "Mozilla/5.0 (X11; U; Linux i686; pl; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1 (Ubuntu-edgy)", + "timeout": 10 + }, + { + "name": "two", + "url": "http://feeds.feedburner.com/crunchyroll/rss/anime", + "proxy": "socks5://172.17.0.1:9007", + "userAgent": "Mozilla/5.0 (X11; U; Linux i686; pl; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1 (Ubuntu-edgy)", + "timeout": 10 + } + ], + "period": 3600 +} +``` + ### Example Config File ```toml @@ -311,10 +349,24 @@ skipTLSVerify = false useTLS = true plugins = ["/plugins/plugin1.lua", "/plugins/plugin2.lua"] adminOnly = false +plugins = ["/plugins/ip.lua", "/plugins/urban.lua", "/plugins/remind.lua"] +[ircd.devinet.watchlist.security] +watchList = ["#securityfeeds"] +watchFiles = ["/watchfiles/voidbox.list"] +alertChannel = "#milla_alerts" +[ircd.devinet.rss.manga] +rssFile = "/rssfeeds/manga.json" +channel = "#manga" +[ircd.devinet.rss.anime] +rssFile = "/rssfeeds/anime.json" +channel = "#anime" [ircd.devinet.watchlist.security] watchList = ["#securityfeeds"] watchFiles = ["/watchfiles/voidbox.list"] alertChannel = "#milla_alerts" +eventTypes = ["PRIVMSG"] +fgColor = 0 +bgColor = 28 [ircd.liberanet] ircServer = "irc.libera.chat" @@ -343,12 +395,12 @@ out = true ircProxy = "socks5://127.0.0.1:9051" llmProxy = "http://127.0.0.1:8181" adminOnly = true -[ircd.devinet_terra.customCommands.digest] +[ircd.liberanet.customCommands.digest] sql = "select log from liberanet_milla_us_market_news order by log desc;" limit = 300 context = ["you are a sentiment-analysis bot"] prompt= "i have provided to you news headlines in the form of previous conversations between you and me using the user role. please provide the digest of the news for me." -[ircd.devinet_terra.customCommands.summarize] +[ircd.liberanet.customCommands.summarize] sql= "select log from liberanet_milla_us_market_news order by log desc;" limit= 300 context = ["you are a sentiment-analysis bot"] @@ -393,6 +445,14 @@ Load a plugin: `/load /plugins/rss.lua` Unload a plugin: `/unload /plugins/rss.lua` +#### remind + +Pings the user after the given amount in seconds: `/remind 1200` + +#### roll + +Rolls a number between 1 and 6 if no arguments are given. With one argument it rolls a number between 1 and the given number. With two arguments it rolls a number between the two numbers: `/rool 10000 66666` + ## Deploy ### Docker @@ -768,6 +828,7 @@ isp: Cloudflare, Inc -- query: 1.1.1.1 -- status: success -- regionName: Queensl - Each lua plugin gets its own lua state and will run in a goroutine.
- Lua plugins will not go through a proxy if they are not instructed to do so. If you are using the provided http module, you can set the proxy value before loading the http module as provided in the examples under `plugins`. The module will read and set the following environment variables in the order given: + - `ALL_PROXY` - `HTTPS_PROXY` - `HTTP_PROXY` diff --git a/go.mod b/go.mod index caaa440..2635f9d 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/kohkimakimoto/gluayaml v0.0.0-20160815032708-6fe413d49d73 github.com/layeh/gopher-json v0.0.0-20201124131017-552bb3c4c3bf github.com/lrstanley/girc v0.0.0-20240125042120-9add3166e52e + github.com/mmcdole/gofeed v1.3.0 github.com/sashabaranov/go-openai v1.19.3 github.com/yuin/gluare v0.0.0-20170607022532-d7c94f1a80ed github.com/yuin/gopher-lua v1.1.1 @@ -27,6 +28,8 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/longrunning v0.5.6 // indirect + github.com/PuerkitoBio/goquery v1.8.0 // indirect + github.com/andybalholm/cascadia v1.3.1 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // indirect @@ -40,7 +43,11 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/kr/text v0.2.0 // indirect + github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect diff --git a/go.sum b/go.sum index 9419b46..90f87f7 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ cloud.google.com/go/longrunning v0.5.6 h1:xAe8+0YaWoCKr9t1+aWe+OeQgN/iJK1fEgZSXm cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= +github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= github.com/ailncode/gluaxmlpath v0.0.0-20161126153117-6ce478ecb4a6 h1:FM0WudTZ+xeiXPJcs+X1Zg8JXe4vlb9P2GZYqCYjZkk= github.com/ailncode/gluaxmlpath v0.0.0-20161126153117-6ce478ecb4a6/go.mod h1:Ti1AvV2KUYtHEBX7eYbdAGEfFyKz9+lHrJPcr79Vkng= github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= @@ -21,6 +23,8 @@ github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9 h1:rdWOzitWlNYeUsXmz+IQfa9NkGEq3gA/qQ3mOEqBU6o= github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9/go.mod h1:X97UjDTXp+7bayQSFZk2hPvCTmTZIicUjZQRtkwgAKY= @@ -69,6 +73,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -88,6 +93,8 @@ github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kohkimakimoto/gluayaml v0.0.0-20160815032708-6fe413d49d73 h1:e2UU76WBjv6W0QUuPHe2QfSAXLR1kouek1fcSUtnSrk= github.com/kohkimakimoto/gluayaml v0.0.0-20160815032708-6fe413d49d73/go.mod h1:I+YgUp/uc0hF+H4RuQjR+uzDJNjUz7Sm21e63UCLWWQ= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -98,6 +105,15 @@ github.com/layeh/gopher-json v0.0.0-20201124131017-552bb3c4c3bf h1:bg6J/5S/AeTz7 github.com/layeh/gopher-json v0.0.0-20201124131017-552bb3c4c3bf/go.mod h1:E/q28EyUVBgBQnONAVPIdwvEsv4Ve0vaCA9JWim4+3I= github.com/lrstanley/girc v0.0.0-20240125042120-9add3166e52e h1:Y86mAFtJjS4P0atZ6QAKH88TV0ASQYJdIGWiOmJKoNY= github.com/lrstanley/girc v0.0.0-20240125042120-9add3166e52e/go.mod h1:lgrnhcF8bg/Bd5HA5DOb4Z+uGqUqGnp4skr+J2GwVgI= +github.com/mmcdole/gofeed v1.3.0 h1:5yn+HeqlcvjMeAI4gu6T+crm7d0anY85+M+v6fIFNG4= +github.com/mmcdole/gofeed v1.3.0/go.mod h1:9TGv2LcJhdXePDzxiuMnukhV2/zb6VtnZt1mS+SjkLE= +github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 h1:Zr92CAlFhy2gL+V1F+EyIuzbQNbSgP4xhTODZtrXUtk= +github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23/go.mod h1:v+25+lT2ViuQ7mVxcncQ8ch1URund48oH+jhjiwEgS8= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -147,6 +163,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -161,10 +178,14 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= diff --git a/main.go b/main.go index e04bb85..1716025 100644 --- a/main.go +++ b/main.go @@ -10,6 +10,7 @@ import ( "fmt" "index/suffixarray" "log" + "math/rand" "net" "net/http" "net/url" @@ -222,6 +223,8 @@ func getHelpString() string { helpString += "memstats - returns the memory status currently being used\n" helpString += "load - loads a lua script\n" helpString += "unload - unloads a lua script\n" + helpString += "remind - reminds you in a given amount of seconds\n" + helpString += "roll - rolls a dice. the number is between 1 and 6. One arg sets the upper limit. Two args sets the lower and upper limit in that order\n" return helpString } @@ -546,6 +549,71 @@ func runCommand( } appConfig.deleteLstate(args[1]) + case "remind": + if len(args) < 2 { + client.Cmd.Reply(event, errNotEnoughArgs.Error()) + + break + } + + seconds, err := strconv.Atoi(args[1]) + if err != nil { + client.Cmd.Reply(event, errNotEnoughArgs.Error()) + + break + } + + client.Cmd.Reply(event, "Ok, I'll remind you in "+args[1]+" seconds.") + time.Sleep(time.Duration(seconds) * time.Second) + + client.Cmd.ReplyTo(event, " Ping!") + case "forget": + + client.Cmd.Reply(event, "I no longer even know whether you're supposed to wear or drink a camel.'") + case "roll": + lowerLimit := 1 + upperLimit := 6 + + if len(args) == 1 { + } else if len(args) == 2 { + argOne, err := strconv.Atoi(args[1]) + + if err != nil { + client.Cmd.Reply(event, errNotEnoughArgs.Error()) + + break + } + + upperLimit = argOne + } else if len(args) == 3 { + argOne, err := strconv.Atoi(args[1]) + + if err != nil { + client.Cmd.Reply(event, errNotEnoughArgs.Error()) + + break + } + + lowerLimit = argOne + + argTwo, err := strconv.Atoi(args[2]) + + if err != nil { + client.Cmd.Reply(event, errNotEnoughArgs.Error()) + + break + } + + upperLimit = argTwo + } else { + client.Cmd.Reply(event, errors.New("too many args").Error()) + + break + } + + randomNumber := lowerLimit + rand.Intn(upperLimit-lowerLimit+1) + + client.Cmd.ReplyTo(event, fmt.Sprint(randomNumber)) default: _, ok := appConfig.LuaCommands[args[0]] if !ok { @@ -1262,6 +1330,12 @@ func runIRC(appConfig TomlConfig) { go WatchListHandler(irc, appConfig) } + if len(appConfig.Rss) > 0 { + irc.Handlers.AddBg(girc.CONNECTED, func(client *girc.Client, _ girc.Event) { + go runRSS(&appConfig, irc) + }) + } + for { var dialer proxy.Dialer diff --git a/plugins.go b/plugins.go index 8e15976..2503e3d 100644 --- a/plugins.go +++ b/plugins.go @@ -343,6 +343,7 @@ func millaModuleLoaderClosure(luaState *lua.LState, client *girc.Client, appConf registerStructAsLuaMetaTable[TomlConfig](luaState, millaModule, checkStruct, TomlConfig{}, "toml_config") registerStructAsLuaMetaTable[CustomCommand](luaState, millaModule, checkStruct, CustomCommand{}, "custom_command") registerStructAsLuaMetaTable[LogModel](luaState, millaModule, checkStruct, LogModel{}, "log_model") + registerStructAsLuaMetaTable[girc.Event](luaState, millaModule, checkStruct, girc.Event{}, "girc_event") luaState.SetGlobal("milla", millaModule) diff --git a/rss.go b/rss.go new file mode 100644 index 0000000..c1d65aa --- /dev/null +++ b/rss.go @@ -0,0 +1,179 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net" + "net/http" + "net/url" + "os" + "slices" + "time" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/lrstanley/girc" + "github.com/mmcdole/gofeed" + "golang.org/x/net/proxy" +) + +func GetFeed(feed FeedConfig, + feedChanel chan<- *gofeed.Feed, + client *girc.Client, + pool *pgxpool.Pool, + channel, groupName string, +) { + rowName := groupName + "__" + feed.Name + "__" + + parsedFeed, err := feed.FeedParser.ParseURL(feed.URL) + if err != nil { + log.Print(err) + } else { + query := fmt.Sprintf("select newest_unix_time from rss where name = '%s'", rowName) + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second) + defer cancel() + + newestFromDB := int64(0) + + err := pool.QueryRow(ctx, query).Scan(&newestFromDB) + if err != nil { + pool.Exec(ctx, fmt.Sprintf("insert into rss (name, newest_unix_time) values ('%s',0)", rowName)) + } + + log.Print("Newset from DB: ", newestFromDB) + + sortFunc := func(a, b *gofeed.Item) int { + if a.PublishedParsed.Before(*b.PublishedParsed) { + return -1 + } else if a.PublishedParsed.After(*b.PublishedParsed) { + return 1 + } + + return 0 + } + + slices.SortFunc(parsedFeed.Items, sortFunc) + + for _, item := range parsedFeed.Items { + if item.PublishedParsed.Unix() >= newestFromDB { + client.Cmd.Message(channel, parsedFeed.Title+": "+item.Title) + } + } + + log.Print(parsedFeed.Items[0].PublishedParsed.Unix()) + log.Print(parsedFeed.Items[len(parsedFeed.Items)-1].PublishedParsed.Unix()) + + query = fmt.Sprintf("update rss set newest_unix_time = %d where name = '%s'", parsedFeed.Items[len(parsedFeed.Items)-1].PublishedParsed.Unix(), rowName) + + ctx2, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second) + defer cancel() + + _, err = pool.Exec(ctx2, query) + if err != nil { + log.Print(err) + } + } + + feedChanel <- parsedFeed +} + +func feedDispatcher( + config RSSConfig, + client *girc.Client, + pool *pgxpool.Pool, + channel, groupName string, +) { + feedChanel := make(chan *gofeed.Feed) + + for i := range len(config.Feeds) { + config.Feeds[i].FeedParser = gofeed.NewParser() + + config.Feeds[i].FeedParser.UserAgent = config.Feeds[i].UserAgent + + if config.Feeds[i].Proxy != "" { + proxyURL, err := url.Parse(config.Feeds[i].Proxy) + if err != nil { + log.Print(err) + continue + } + + dialer, err := proxy.FromURL(proxyURL, &net.Dialer{Timeout: time.Duration(config.Feeds[i].Timeout) * time.Second}) + if err != nil { + log.Print(err) + + continue + } + + httpClient := http.Client{ + Transport: &http.Transport{ + Dial: dialer.Dial, + }, + } + + config.Feeds[i].FeedParser.Client = &httpClient + } + } + + for _, feed := range config.Feeds { + go GetFeed(feed, feedChanel, client, pool, channel, groupName) + } + + // <-feedChanel +} + +func ParseRSSConfig(rssConfFilePath string) *RSSConfig { + file, err := os.Open(rssConfFilePath) + if err != nil { + log.Print(err) + + return nil + } + + var config *RSSConfig + + decoder := json.NewDecoder(file) + + err = decoder.Decode(&config) + if err != nil { + log.Print(err) + + return nil + } + + return config +} + +func runRSS(appConfig *TomlConfig, client *girc.Client) { + for { + query := fmt.Sprintf( + `create table if not exists rss ( + id serial primary key, + name text not null unique, + newest_unix_time bigint not null + )`) + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(appConfig.RequestTimeout)*time.Second) + defer cancel() + + _, err := appConfig.pool.Exec(ctx, query) + if err != nil { + log.Print(err) + time.Sleep(time.Duration(appConfig.MillaReconnectDelay) * time.Second) + } else { + for groupName, rss := range appConfig.Rss { + log.Print("RSS: joining ", rss.Channel) + client.Cmd.Join(rss.Channel) + rssConfig := ParseRSSConfig(rss.RssFile) + if rssConfig == nil { + log.Print("Could not parse RSS config file " + rss.RssFile + ". Exiting.") + } else { + for { + feedDispatcher(*rssConfig, client, appConfig.pool, rss.Channel, groupName) + time.Sleep(time.Duration(rssConfig.Period) * time.Second) + } + } + } + } + } +} diff --git a/types.go b/types.go index 85f820d..2206eb2 100644 --- a/types.go +++ b/types.go @@ -5,6 +5,7 @@ import ( "time" "github.com/jackc/pgx/v5/pgxpool" + "github.com/mmcdole/gofeed" lua "github.com/yuin/gopher-lua" ) @@ -43,6 +44,11 @@ type LuaCommand struct { FuncName string } +type RssFile struct { + RssFile string `toml:"rssFile"` + Channel string `toml:"channel"` +} + type TomlConfig struct { IrcServer string `toml:"ircServer"` IrcNick string `toml:"ircNick"` @@ -70,29 +76,31 @@ type TomlConfig struct { WebIRCGateway string `toml:"webIRCGateway"` WebIRCHostname string `toml:"webIRCHostname"` WebIRCAddress string `toml:"webIRCAddress"` + RSSFile string `toml:"rssFile"` Plugins []string `toml:"plugins"` CustomCommands map[string]CustomCommand `toml:"customCommands"` WatchLists map[string]WatchList `toml:"watchList"` LuaStates map[string]LuaLstates LuaCommands map[string]LuaCommand - Temp float64 `toml:"temp"` - RequestTimeout int `toml:"requestTimeout"` - MillaReconnectDelay int `toml:"millaReconnectDelay"` - IrcPort int `toml:"ircPort"` - KeepAlive int `toml:"keepAlive"` - MemoryLimit int `toml:"memoryLimit"` - PingDelay int `toml:"pingDelay"` - PingTimeout int `toml:"pingTimeout"` - TopP float32 `toml:"topP"` - TopK int32 `toml:"topK"` - EnableSasl bool `toml:"enableSasl"` - SkipTLSVerify bool `toml:"skipTLSVerify"` - UseTLS bool `toml:"useTLS"` - DisableSTSFallback bool `toml:"disableSTSFallback"` - AllowFlood bool `toml:"allowFlood"` - Debug bool `toml:"debug"` - Out bool `toml:"out"` - AdminOnly bool `toml:"adminOnly"` + Rss map[string]RssFile `toml:"rss"` + Temp float64 `toml:"temp"` + RequestTimeout int `toml:"requestTimeout"` + MillaReconnectDelay int `toml:"millaReconnectDelay"` + IrcPort int `toml:"ircPort"` + KeepAlive int `toml:"keepAlive"` + MemoryLimit int `toml:"memoryLimit"` + PingDelay int `toml:"pingDelay"` + PingTimeout int `toml:"pingTimeout"` + TopP float32 `toml:"topP"` + TopK int32 `toml:"topK"` + EnableSasl bool `toml:"enableSasl"` + SkipTLSVerify bool `toml:"skipTLSVerify"` + UseTLS bool `toml:"useTLS"` + DisableSTSFallback bool `toml:"disableSTSFallback"` + AllowFlood bool `toml:"allowFlood"` + Debug bool `toml:"debug"` + Out bool `toml:"out"` + AdminOnly bool `toml:"adminOnly"` pool *pgxpool.Pool Admins []string `toml:"admins"` IrcChannels []string `toml:"ircChannels"` @@ -169,3 +177,17 @@ type MemoryElement struct { Role string `json:"role"` Content string `json:"content"` } + +type FeedConfig struct { + Name string `json:"name"` + URL string `json:"url"` + UserAgent string `json:"userAgent"` + Proxy string `json:"proxy"` + Timeout int `json:"timeout"` + FeedParser *gofeed.Parser +} + +type RSSConfig struct { + Feeds []FeedConfig `json:"feeds"` + Period int `json:"period"` +} -- cgit v1.2.3