diff --git a/.gitignore b/.gitignore index 8d22f12..65ba3a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ config.yaml statedata config.yml +simpbot diff --git a/Containerfile b/Containerfile index e566d6e..530ba93 100644 --- a/Containerfile +++ b/Containerfile @@ -1,10 +1,10 @@ -FROM golang:1.18 as builder +FROM golang:1.20 as builder WORKDIR /go/src/app COPY . . RUN apt update && apt upgrade -y RUN go build -FROM alpine:latest as final +FROM debian:stable-slim as final WORKDIR /srv/ RUN mkdir /srv/simpbot RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 diff --git a/deploy.yml b/deploy.yml deleted file mode 100644 index 9c34a00..0000000 --- a/deploy.yml +++ /dev/null @@ -1,53 +0,0 @@ ---- - -- hosts: localhost - tasks: - - - name: check simpbot version - uri: - url: https://git.saintnet.tech/api/v1/repos/stryan/simpbot/releases?limit=1 - return_content: true - register: simpbot_latest - - - name: "downloading and installing simpbot {{ simpbot_latest.json[0].tag_name }}" - block: - - name: create temp directory - tempfile: - state: directory - suffix: dwn - register: tempfolder_1 - - - name: download simpbot - loop: "{{ simpbot_latest.json[0].assets }}" - when: "'amd64.tar.gz' in item.name" - unarchive: - remote_src: yes - src: "{{ item.browser_download_url }}" - dest: "{{ tempfolder_1.path }}" - keep_newer: yes - - - name: installing simpbot binary - copy: - remote_src: yes - src: "{{ tempfolder_1.path }}/simpbot" - dest: /usr/local/bin/ - mode: '0755' - register: new_binary - - - name: installing unit file - copy: - remote_src: yes - src: "{{ tempfolder_1.path }}/init/simpbot.service" - dest: /etc/systemd/system/simpbot.service - register: new_unit - - - name: reload systemd with new unit - systemd: - daemon_reload: yes - when: new_unit.changed or new_binary.changed - - - name: start service - systemd: - name: simpbot - state: restarted - when: new_binary.changed diff --git a/go.mod b/go.mod index 084ec9f..e703077 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,38 @@ module git.saintnet.tech/stryan/simpbot -go 1.15 +go 1.20 require ( + github.com/charmbracelet/log v0.2.1 github.com/fsnotify/fsnotify v1.5.1 github.com/spf13/viper v1.9.0 - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect - golang.org/x/net v0.0.0-20211014172544-2b766c08f1c0 // indirect - golang.org/x/sys v0.0.0-20211013075003-97ac67df715c // indirect - golang.org/x/text v0.3.7 // indirect maunium.net/go/mautrix v0.9.29 ) + +require ( + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/btcsuite/btcutil v1.0.2 // indirect + github.com/charmbracelet/lipgloss v0.7.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/magiconair/properties v1.8.5 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mitchellh/mapstructure v1.4.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.1 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/spf13/afero v1.6.0 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/net v0.0.0-20211014172544-2b766c08f1c0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.3.7 // indirect + gopkg.in/ini.v1 v1.63.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum index 2222e9e..43e9105 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,8 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= @@ -64,6 +66,10 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= +github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= +github.com/charmbracelet/log v0.2.1 h1:1z7jpkk4yKyjwlmKmKMM5qnEDSpV32E7XtWhuv0mTZE= +github.com/charmbracelet/log v0.2.1/go.mod h1:GwFfjewhcVDWLrpAbY5A0Hin9YOlEn40eWT4PNaxFT4= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -95,6 +101,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -210,6 +218,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +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/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -220,6 +230,11 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +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/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= @@ -233,6 +248,10 @@ github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +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.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -246,6 +265,9 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +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/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -269,8 +291,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= @@ -468,8 +490,8 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8= -golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -685,8 +707,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index 505de4f..15a6024 100644 --- a/main.go +++ b/main.go @@ -2,36 +2,18 @@ package main import ( "fmt" - "log" "net/url" "os" "os/signal" - "strings" - "syscall" - "time" + "sync" + "github.com/charmbracelet/log" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" - "maunium.net/go/mautrix" - "maunium.net/go/mautrix/event" - "maunium.net/go/mautrix/id" ) -var Homeserver string -var Username string -var Password string -var DimensionServer string -var HomeserverDomain string -var Token string -var GitCommit string -var GitTag string -var Statefile string -var CurrStreamCnt int -var MostStreamCnt int -var StartTime time.Time -var HolodexToken string - func main() { + simp := newSimp() viper.SetConfigName("config") viper.AddConfigPath(".") viper.AddConfigPath("/etc/simpbot") @@ -40,255 +22,65 @@ func main() { log.Fatalf("Fatal error config file: %v \n", err) } viper.SetConfigType("yaml") - Homeserver = viper.GetString("homeserver") + Homeserver := viper.GetString("homeserver") viper.SetDefault("domain", Homeserver) viper.SetDefault("statefile", "simpstate") - Username = viper.GetString("username") - Password = viper.GetString("password") - Token = viper.GetString("access_token") - HolodexToken = viper.GetString("api_token") - DimensionServer = viper.GetString("dimension") - HomeserverDomain = viper.GetString("domain") - Statefile = viper.GetString("statefile") - CurrStreamCnt = 0 - MostStreamCnt = 0 - StartTime = time.Now() - var vtubers []*Vtuber - log.Println("Logging into", Homeserver, "as", Username) - var client *mautrix.Client - uid := id.NewUserID(strings.ToLower(Username), strings.ToLower(HomeserverDomain)) - if Token == "" { - client, err = mautrix.NewClient(Homeserver, "", "") - if err != nil { - panic(err) - } - } else { - log.Println("using token login") - client, err = mautrix.NewClient(Homeserver, uid, Token) - if err != nil { - panic(err) - } - } - dataFilter := &mautrix.Filter{ - AccountData: mautrix.FilterPart{ - Limit: 20, - NotTypes: []event.Type{ - event.NewEventType("simp.batch"), - }, - }, - } - store := mautrix.NewAccountDataStore("simp.batch", client) - fID, err := client.CreateFilter(dataFilter) - store.SaveFilterID(uid, fID.FilterID) + Username := viper.GetString("username") + Password := viper.GetString("password") + Token := viper.GetString("access_token") + simp.holodexToken = viper.GetString("api_token") + HomeserverDomain := viper.GetString("domain") + DimensionServer := viper.GetString("dimension") + + log.Info("logging into matrix", "homeserver", Homeserver, "username", Username) + err = simp.SetupMatrix(Username, Password, Token, Homeserver, HomeserverDomain, DimensionServer) if err != nil { - panic(err) + log.Fatal(err) } - client.Store = store - - if Token == "" { - login_res, err := client.Login(&mautrix.ReqLogin{ - Type: "m.login.password", - Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: Username}, - Password: Password, - StoreCredentials: true, - }) - if err != nil { - panic(err) - } - Token = login_res.AccessToken - viper.Set("access_token", Token) - log.Println("Login succesful, saving access_token to config file") - err = viper.WriteConfig() - if err != nil { - panic(err) - } - } else { - log.Println("skipping login since token provided") + if simp.holodexToken == "" { + log.Warn("No holodex API token provided, unlikely to be able to get streams") } - if HolodexToken == "" { - log.Println("No holodex API token provided, unlikely to be able to get streams") - } - syncer := client.Syncer.(*mautrix.DefaultSyncer) - syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) { - if evt.Sender == client.UserID { - return //ignore events from self - } - 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 - body_s := strings.Split(body, " ") - if body_s[0] != "!simp" { - return - } - if len(body_s) < 2 { - return //nothing to parse - } - switch body_s[1] { - case "info": - // print info page - var infomsg string - vlist := []string{} - for _, vt := range vtubers { - ann := "" - if vt.AnnounceLive { - ann = "*" - } - vlist = append(vlist, fmt.Sprintf("%v%v", vt.Name, ann)) - } - infomsg = fmt.Sprintf("Currently Simping For: \n%v", strings.Join(vlist, "\n")) - client.SendText(evt.RoomID, infomsg) - case "stats": - var statmsg string - vlist := []string{} - t := 0 - for _, vt := range vtubers { - vlist = append(vlist, fmt.Sprintf("%v Total:%v", vt.Name, vt.TotalStreams)) - t = t + vt.TotalStreams - } - statmsg = fmt.Sprintf("Current Stats Since %v:\n%v\n\nTotal Streams: %v\nMost Concurrent: %v/%v\n", StartTime, strings.Join(vlist, "\n"), t, MostStreamCnt, len(vtubers)) - client.SendText(evt.RoomID, statmsg) - case "version": - // print version - if GitTag != "" { - client.SendText(evt.RoomID, "SimpBot version "+GitTag) - } else { - client.SendText(evt.RoomID, "SimpBot version "+GitCommit) - } - case "reload": - //reload config - client.SendText(evt.RoomID, "Reloading config") - fmt.Println("Reload requested,reloading vtubers") - vtubers = LoadVtubers() - case "subscribe": - if len(body_s) < 3 { - client.SendText(evt.RoomID, "Need a member to subscribe to") - } - vt := body_s[2] - var subbed bool - for _, v := range vtubers { - if strings.ToUpper(v.Name) == strings.ToUpper(vt) { - v.Subs[evt.Sender] = true - subbed = true - } - } - if subbed { - client.SendText(evt.RoomID, "subbed") - } else { - client.SendText(evt.RoomID, "could not identify talent to subscribe to") - } - - case "help": - client.SendText(evt.RoomID, "Supported commands: info,version,stats,reload,subscribe") - default: - //command not found - client.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 = client.JoinRoomByID(evt.RoomID) - if err != nil { - fmt.Printf("error joining room %v", evt.RoomID) - } else { - fmt.Printf("joined room %v", evt.RoomID) - } - } - }) - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGUSR1) - vtubers = LoadVtubers() + simp.vtubers = loadVtubers() viper.WatchConfig() viper.OnConfigChange(func(e fsnotify.Event) { fmt.Println("Config file changed,reloading vtubers:", e.Name) - vtubers = LoadVtubers() + simp.vtubers = loadVtubers() }) - + var wg sync.WaitGroup + stop := make(chan bool) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) go func() { - for { - time.Sleep(30 * time.Second) - roomResp, err := client.JoinedRooms() - if err != nil { - log.Println("error getting joined rooms") - log.Println(err) - log.Println("Skipping iteration") - continue - } - rooms := roomResp.JoinedRooms - // We're going to assume they're only stream one video at a time - for _, v := range vtubers { - err = v.Update(HolodexToken) - if err != nil { - log.Println(err) - } - if v.IsLive() { - - for _, room := range rooms { - //check to see if already embeded - var content YoutubeWidget - err = client.StateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, &content) - if err != nil { - log.Printf("error getting state event in room %v: %v", room, err) - continue - } - if content.ID == "" { - if v.AnnounceLive { - client.SendText(room, v.LiveMsg) - } else { - if isValidUrl(v.LiveMsg) { - client.SendNotice(room, fmt.Sprintf("%v has gone live", v.Name)) - } else { - client.SendNotice(room, v.LiveMsg) - } - } - client.SendNotice(room, fmt.Sprintf("%v's Title: %v", v.Name, v.CurrentStreamTitle)) - var subs string - for k := range v.Subs { - subs += k.String() + " " - } - if len(v.Subs) > 0 { - client.SendText(room, fmt.Sprintf("Pinging %v", subs)) - } - resp, err := client.SendStateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, NewYT(v.Name+"'s stream", v.CurrentStream, string(room))) - if err != nil { - log.Println("error embeding video") - log.Println(err) - } - v.TotalStreams = v.TotalStreams + 1 - CurrStreamCnt = CurrStreamCnt + 1 - if CurrStreamCnt > MostStreamCnt { - MostStreamCnt = CurrStreamCnt - } - log.Printf("Embed stream %v for %v ", resp, v.Name) - } - } - } else { - //Not live, check to see if there's any embeds and remove them - for _, room := range rooms { - var content YoutubeWidget - err = client.StateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, &content) - if err == nil && content.ID != "" { - //event found, kill it - resp, err := client.SendStateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, struct{}{}) - if err != nil { - log.Println("error removing video embed") - log.Println(err) - } - CurrStreamCnt = CurrStreamCnt - 1 - log.Printf("Embed stream %v removed %v", resp, v.Name) - } - } - } - } - } + <-c + log.Info("trying to shutdown cleanly") + simp.Stop() + stop <- true }() - err = client.Sync() - if err != nil { - panic(err) - } + wg.Add(1) + go func() { + + err = simp.client.Sync() + if err != nil { + log.Warn(err) + } + wg.Done() + + }() + + wg.Add(1) + go func() { + simp.Run() + wg.Done() + log.Info("simpbot shutdown") + }() + log.Info("simpbot running") + wg.Wait() + log.Info("shutting down") + } -func isValidUrl(toTest string) bool { +func isValidURL(toTest string) bool { _, err := url.ParseRequestURI(toTest) if err != nil { return false diff --git a/simp.go b/simp.go new file mode 100644 index 0000000..546c2aa --- /dev/null +++ b/simp.go @@ -0,0 +1,281 @@ +package main + +import ( + "fmt" + "net/url" + "strings" + "time" + + "github.com/charmbracelet/log" + "github.com/spf13/viper" + "maunium.net/go/mautrix" + "maunium.net/go/mautrix/event" + "maunium.net/go/mautrix/id" +) + +type simp struct { + client *mautrix.Client + dimensionServer string + holodexToken string + startTime time.Time + currStream int + maxStream int + vtubers []*Vtuber + stop chan bool +} + +func newSimp() *simp { + return &simp{ + startTime: time.Now(), + stop: make(chan bool), + } +} + +func (s *simp) Run() { + ticker := time.NewTicker(time.Second * 3) + for { + select { + case <-s.stop: + return + case <-ticker.C: + + roomResp, err := s.client.JoinedRooms() + if err != nil { + log.Warnf("error getting joined rooms: %v, skipping iteration", err) + continue + } + rooms := roomResp.JoinedRooms + // We're going to assume they're only stream one video at a time + for _, v := range s.vtubers { + err = v.Update(s.holodexToken) + if err != nil { + log.Warn(err) + } + if v.IsLive() { + for _, room := range rooms { + //check to see if already embeded + var content youtubeWidget + err = s.client.StateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, &content) + if err != nil { + log.Printf("error getting state event in room %v: %v", room, err) + } + if content.ID == "" { + if v.AnnounceLive { + s.client.SendText(room, v.LiveMsg) + } else { + if isValidURL(v.LiveMsg) { + s.client.SendNotice(room, fmt.Sprintf("%v has gone live", v.Name)) + } else { + s.client.SendNotice(room, v.LiveMsg) + } + } + s.client.SendNotice(room, fmt.Sprintf("%v's Title: %v", v.Name, v.CurrentStreamTitle)) + var subs string + for k := range v.Subs { + subs += k.String() + " " + } + if len(v.Subs) > 0 { + s.client.SendText(room, fmt.Sprintf("Pinging %v", subs)) + } + resp, err := s.client.SendStateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, s.NewYT(v.Name+"'s stream", v.CurrentStream, string(room))) + if err != nil { + log.Warnf("error embeding video: %v", err) + } + v.TotalStreams = v.TotalStreams + 1 + s.currStream++ + if s.currStream > s.maxStream { + s.maxStream = s.currStream + } + log.Info("Embed stream added", "event", resp, "vtuber", v.Name) + } + } + } else { + //Not live, check to see if there's any embeds and remove them + for _, room := range rooms { + var content youtubeWidget + err = s.client.StateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, &content) + if err == nil && content.ID != "" { + //event found, kill it + resp, err := s.client.SendStateEvent(room, event.NewEventType("im.vector.modular.widgets"), "dimension-m.video-simp-"+v.Name, struct{}{}) + if err != nil { + log.Warnf("error removing embed: %v", err) + } + s.currStream-- + log.Info("Embed stream removed", "event", resp, "vtuber", v.Name) + } + } + } + } + } + } + +} + +func (s *simp) SetupMatrix(uname, pass, token, hs, domain, dserver string) error { + uid := id.NewUserID(strings.ToLower(uname), strings.ToLower(domain)) + if token == "" { + client, err := mautrix.NewClient(hs, "", "") + if err != nil { + return err + } + s.client = client + } else { + log.Info("using token login") + client, err := mautrix.NewClient(hs, uid, token) + if err != nil { + return err + } + s.client = client + } + dataFilter := &mautrix.Filter{ + AccountData: mautrix.FilterPart{ + Limit: 20, + NotTypes: []event.Type{ + event.NewEventType("s.batch"), + }, + }, + } + store := mautrix.NewAccountDataStore("simp.batch", s.client) + fID, err := s.client.CreateFilter(dataFilter) + if err != nil { + return err + } + store.SaveFilterID(uid, fID.FilterID) + s.client.Store = store + + if token == "" { + loginRes, err := s.client.Login(&mautrix.ReqLogin{ + Type: "m.login.password", + Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: uname}, + Password: pass, + StoreCredentials: true, + }) + if err != nil { + return err + } + token = loginRes.AccessToken + viper.Set("access_token", token) + log.Info("Login succesful, saving access_token to config file") + err = viper.WriteConfig() + if err != nil { + return err + } + } else { + log.Info("skipping login since token provided") + } + syncer := s.client.Syncer.(*mautrix.DefaultSyncer) + syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) { + if evt.Sender == s.client.UserID { + return //ignore events from self + } + log.Debugf("<%[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] != "!simp" { + return + } + if len(bodyS) < 2 { + return //nothing to parse + } + switch bodyS[1] { + case "info": + // print info page + var infomsg string + vlist := []string{} + for _, vt := range s.vtubers { + ann := "" + if vt.AnnounceLive { + ann = "*" + } + vlist = append(vlist, fmt.Sprintf("%v%v", vt.Name, ann)) + } + infomsg = fmt.Sprintf("Currently Simping For: \n%v", strings.Join(vlist, "\n")) + s.client.SendText(evt.RoomID, infomsg) + case "stats": + var statmsg string + vlist := []string{} + t := 0 + for _, vt := range s.vtubers { + vlist = append(vlist, fmt.Sprintf("%v Total:%v", vt.Name, vt.TotalStreams)) + t = t + vt.TotalStreams + } + statmsg = fmt.Sprintf("Current Stats Since %v:\n%v\n\nTotal Streams: %v\nMost Concurrent: %v/%v\n", s.startTime, strings.Join(vlist, "\n"), t, s.maxStream, len(s.vtubers)) + s.client.SendText(evt.RoomID, statmsg) + case "version": + s.client.SendText(evt.RoomID, "not implemented") + + case "reload": + //reload config + s.client.SendText(evt.RoomID, "Reloading config") + log.Info("Reload requested,reloading vtubers") + s.vtubers = loadVtubers() + case "subscribe": + if len(bodyS) < 3 { + s.client.SendText(evt.RoomID, "Need a member to subscribe to") + } + vt := bodyS[2] + var subbed bool + for _, v := range s.vtubers { + if strings.ToUpper(v.Name) == strings.ToUpper(vt) { + v.Subs[evt.Sender] = true + subbed = true + } + } + if subbed { + s.client.SendText(evt.RoomID, "subbed") + } else { + s.client.SendText(evt.RoomID, "could not identify talent to subscribe to") + } + + case "help": + s.client.SendText(evt.RoomID, "Supported commands: info,version,stats,reload,subscribe") + default: + //command not found + s.client.SendText(evt.RoomID, "command not recognized") + } + }) + syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) { + log.Infof("<%[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 = s.client.JoinRoomByID(evt.RoomID) + if err != nil { + log.Warnf("error joining room %v", evt.RoomID) + } else { + log.Infof("joined room %v", evt.RoomID) + } + } + }) + + return nil +} + +func (s *simp) Stop() { + s.stop <- true + s.client.StopSync() +} + +func (s *simp) NewYT(videoName, videoID, roomID string) *youtubeWidget { + encodedVod := url.QueryEscape("https://youtube.com/embed/" + videoID) + return &youtubeWidget{ + Type: "im.vector.modular.widgets", + URL: "https://" + s.dimensionServer + "/widgets/video?url=" + encodedVod, + Name: videoName, + Data: videoData{ + VideoURL: "https://www.youtube.com/watch?v=" + videoID, + URL: "https://youtube.com/embed/" + videoID, + DimensionAppMetadata: dimensionAppMetadata{ + InRoomID: roomID, + WrapperURLBase: "https://" + s.dimensionServer + "/widgets/video?url=", + WrapperID: "video", + ScalarWrapperID: "youtube", + Integration: integration{ + Category: "widget", + Type: "youtube", + }, + LastUpdatedTs: time.Now().UnixNano() / int64(time.Millisecond), + }, + }, + CreatorUserID: string(s.client.UserID), + ID: "dimension-m.video-simp", + } +} diff --git a/vtuber.go b/vtuber.go index 7d8fdd2..09ab11a 100644 --- a/vtuber.go +++ b/vtuber.go @@ -4,14 +4,14 @@ import ( "encoding/json" "fmt" "io/ioutil" - "log" "net/http" + "github.com/charmbracelet/log" "github.com/spf13/viper" "maunium.net/go/mautrix/id" ) -type VtuberConfig struct { +type vtuberConfig struct { Name string `mapstructure:"name"` ChannelID string `mapstructure:"channelid"` LiveMsg string `mapstructure:"msg"` @@ -29,7 +29,7 @@ type Vtuber struct { Subs map[id.UserID]bool } -func NewVtuber(name, channelID, liveMsg string, announce bool) *Vtuber { +func newVtuber(name, channelID, liveMsg string, announce bool) *Vtuber { return &Vtuber{ Name: name, ChannelID: channelID, @@ -42,28 +42,30 @@ func NewVtuber(name, channelID, liveMsg string, announce bool) *Vtuber { } } -func LoadVtubers() []*Vtuber { - var vtubersRaw []VtuberConfig +func loadVtubers() []*Vtuber { + var vtubersRaw []vtuberConfig var vtubers []*Vtuber err := viper.UnmarshalKey("vtubers", &vtubersRaw) if err != nil { panic(err) } for _, vt := range vtubersRaw { - log.Printf("adding vtuber %v", vt) - vtubers = append(vtubers, NewVtuber(vt.Name, vt.ChannelID, vt.LiveMsg, vt.Announce)) + log.Infof("adding vtuber %v", vt) + vtubers = append(vtubers, newVtuber(vt.Name, vt.ChannelID, vt.LiveMsg, vt.Announce)) } return vtubers } +//IsLive returns whether the specified Vtuber is live func (v *Vtuber) IsLive() bool { return v.CurrentStream != "" } -func (v *Vtuber) Update(api_key string) error { +//Update takes an apiKey and updates the vtuber struct +func (v *Vtuber) Update(apiKey string) error { url := fmt.Sprintf("https://holodex.net/api/v2/live?channel_id=%s&lang=all&sort=available_at&order=desc&limit=25&offset=0&paginated=%%3Cempty%%3E", v.ChannelID) req, err := http.NewRequest("GET", url, nil) - req.Header.Set("X-APIKEY", api_key) + req.Header.Set("X-APIKEY", apiKey) if err != nil { return err } @@ -76,7 +78,6 @@ func (v *Vtuber) Update(api_key string) error { var sl StreamList err = json.Unmarshal(jsonBody, &sl) if err != nil { - log.Printf("error parsing json for vtuber %v:%v", v.Name, err) return err } found := false diff --git a/youtube.go b/youtube.go index c4a8088..0c6dba1 100644 --- a/youtube.go +++ b/youtube.go @@ -1,63 +1,32 @@ package main -import ( - "net/url" - "time" -) - -type Integration struct { +type integration struct { Category string `json:"category"` Type string `json:"type"` } -type DimensionAppMetadata struct { +type dimensionAppMetadata struct { InRoomID string `json:"inRoomId"` WrapperURLBase string `json:"wrapperUrlBase"` WrapperID string `json:"wrapperId"` ScalarWrapperID string `json:"scalarWrapperId"` - Integration Integration `json:"integration"` + Integration integration `json:"integration"` LastUpdatedTs int64 `json:"lastUpdatedTs"` } -type Data struct { +type videoData struct { VideoURL string `json:"videoUrl"` URL string `json:"url"` - DimensionAppMetadata DimensionAppMetadata `json:"dimension:app:metadata"` + DimensionAppMetadata dimensionAppMetadata `json:"dimension:app:metadata"` } -type YoutubeWidget struct { - Type string `json:"type"` - URL string `json:"url"` - Name string `json:"name"` - Data Data `json:"data"` - CreatorUserID string `json:"creatorUserId"` - ID string `json:"id"` - RoomID string `json:"roomId"` - EventID string `json:"eventId"` +type youtubeWidget struct { + Type string `json:"type"` + URL string `json:"url"` + Name string `json:"name"` + Data videoData `json:"data"` + CreatorUserID string `json:"creatorUserId"` + ID string `json:"id"` + RoomID string `json:"roomId"` + EventID string `json:"eventId"` } -type Unsigned struct { +type unsignedAge struct { Age int `json:"age"` } - -func NewYT(videoName, videoID, roomID string) *YoutubeWidget { - encodedVod := url.QueryEscape("https://youtube.com/embed/" + videoID) - return &YoutubeWidget{ - Type: "im.vector.modular.widgets", - URL: "https://" + DimensionServer + "/widgets/video?url=" + encodedVod, - Name: videoName, - Data: Data{ - VideoURL: "https://www.youtube.com/watch?v=" + videoID, - URL: "https://youtube.com/embed/" + videoID, - DimensionAppMetadata: DimensionAppMetadata{ - InRoomID: roomID, - WrapperURLBase: "https://" + DimensionServer + "/widgets/video?url=", - WrapperID: "video", - ScalarWrapperID: "youtube", - Integration: Integration{ - Category: "widget", - Type: "youtube", - }, - LastUpdatedTs: time.Now().UnixNano() / int64(time.Millisecond), - }, - }, - CreatorUserID: "@" + Username + ":" + HomeserverDomain, - ID: "dimension-m.video-simp", - } -}