Compare commits
No commits in common. "master" and "v3.0.0" have entirely different histories.
27 changed files with 37 additions and 1678 deletions
.gitignorechangelog.mdgo.modgo.summain.goreadme.md
modules
discord
patpet
settings
twitchat
web
youtube
songs
web
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,5 +1,4 @@
|
||||||
tokens/*token
|
tokens/*token
|
||||||
tokens/*logins
|
|
||||||
bin/*
|
bin/*
|
||||||
!./bin/.gitkeep
|
!./bin/.gitkeep
|
||||||
!./bin/*
|
!./bin/*
|
||||||
|
|
60
changelog.md
60
changelog.md
|
@ -1,65 +1,5 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 5.1.0 - 2020-03-18
|
|
||||||
### Added
|
|
||||||
- PetCat twitch chat handler
|
|
||||||
- DailyEmote twitch chat handler
|
|
||||||
|
|
||||||
## 5.0.1 - 2020-03-08
|
|
||||||
### Changed
|
|
||||||
- Updated `go-twitch-irc` lib to v2.2.2
|
|
||||||
|
|
||||||
|
|
||||||
## 5.0.0 - 2020-01-11
|
|
||||||
### Added
|
|
||||||
- Twitch point song request feature with dedicated web server and twitch
|
|
||||||
chat handler
|
|
||||||
- Mentions of `!galched` command in bot messages
|
|
||||||
- Chiki-briki song for discord bot
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Typos in the bot messages
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Updated `go-twitch-irc` lib to v2.2.1
|
|
||||||
|
|
||||||
## 4.2.0 - 2019-10-20
|
|
||||||
### Added
|
|
||||||
- Universal song handler with polka and sax commands
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Updated discordgo library up to v0.20.1
|
|
||||||
|
|
||||||
## 4.1.0 - 2019-07-28
|
|
||||||
### Added
|
|
||||||
- Song player handler
|
|
||||||
|
|
||||||
## 4.0.2 - 2019-07-28
|
|
||||||
### Changed
|
|
||||||
- Info about 7th subday
|
|
||||||
|
|
||||||
## 4.0.1 - 2019-07-22
|
|
||||||
### Changed
|
|
||||||
- Twitch chat library version from v1 to v2
|
|
||||||
|
|
||||||
## 4.0.0 - 2019-06-23
|
|
||||||
### Added
|
|
||||||
- Twitch chat module
|
|
||||||
- Twitch chat handler that calculates duplicates in chat
|
|
||||||
|
|
||||||
## 3.0.2 - 2019-06-03
|
|
||||||
### Added
|
|
||||||
- Command !subhistory
|
|
||||||
- Info about 5th and 6th subday
|
|
||||||
|
|
||||||
## 3.0.1 - 2019-05-10
|
|
||||||
### Added
|
|
||||||
- Readme file
|
|
||||||
### Removed
|
|
||||||
- Removed redundant setting parameter for subday database dumping duration
|
|
||||||
### Changed
|
|
||||||
- Changed way of sending messages to the discord channel
|
|
||||||
|
|
||||||
## 3.0.0 - 2019-05-10
|
## 3.0.0 - 2019-05-10
|
||||||
### Changed
|
### Changed
|
||||||
- Redesigned application with modular structure based on uber fx
|
- Redesigned application with modular structure based on uber fx
|
11
go.mod
11
go.mod
|
@ -1,19 +1,12 @@
|
||||||
module galched-bot
|
module galched-bot
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bwmarrin/discordgo v0.20.1
|
github.com/bwmarrin/discordgo v0.19.0
|
||||||
github.com/gempir/go-twitch-irc/v2 v2.2.2
|
|
||||||
github.com/gorilla/websocket v1.4.1 // indirect
|
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/stretchr/testify v1.3.0 // indirect
|
github.com/stretchr/testify v1.3.0 // indirect
|
||||||
go.uber.org/atomic v1.3.2
|
go.uber.org/atomic v1.3.2 // indirect
|
||||||
go.uber.org/dig v1.7.0 // indirect
|
go.uber.org/dig v1.7.0 // indirect
|
||||||
go.uber.org/fx v1.9.0
|
go.uber.org/fx v1.9.0
|
||||||
go.uber.org/goleak v0.10.0 // indirect
|
go.uber.org/goleak v0.10.0 // indirect
|
||||||
go.uber.org/multierr v1.1.0 // indirect
|
go.uber.org/multierr v1.1.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20191018095205-727590c5006e // indirect
|
|
||||||
google.golang.org/api v0.15.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
|
||||||
|
|
97
go.sum
97
go.sum
|
@ -1,39 +1,9 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/bwmarrin/discordgo v0.19.0 h1:kMED/DB0NR1QhRcalb85w0Cu3Ep2OrGAqZH1R5awQiY=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q=
|
||||||
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
|
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/bwmarrin/discordgo v0.20.1 h1:Ihh3/mVoRwy3otmaoPDUioILBJq4fdWkpsi83oj2Lmk=
|
|
||||||
github.com/bwmarrin/discordgo v0.20.1/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gempir/go-twitch-irc/v2 v2.2.1 h1:jMiEgw6zzrgiz4viG7lgj148J6enLls5aicF+zsi1bk=
|
|
||||||
github.com/gempir/go-twitch-irc/v2 v2.2.1/go.mod h1:0HXoEr9l7gNjwajosptV0w0xGpHeU6gsD7JDlfvjTYI=
|
|
||||||
github.com/gempir/go-twitch-irc/v2 v2.2.2 h1:uzinel2qApXL1UVfr3QcZ3dJsf+YU+PaUp0qJk03qNo=
|
|
||||||
github.com/gempir/go-twitch-irc/v2 v2.2.2/go.mod h1:0HXoEr9l7gNjwajosptV0w0xGpHeU6gsD7JDlfvjTYI=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
|
||||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
|
||||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
@ -41,8 +11,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
|
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
|
||||||
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/dig v1.7.0 h1:E5/L92iQTNJTjfgJF2KgU+/JpMaiuvK2DHLBj0+kSZk=
|
go.uber.org/dig v1.7.0 h1:E5/L92iQTNJTjfgJF2KgU+/JpMaiuvK2DHLBj0+kSZk=
|
||||||
|
@ -55,64 +23,3 @@ go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
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-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191018095205-727590c5006e h1:ZtoklVMHQy6BFRHkbG6JzK+S6rX82//Yeok1vMlizfQ=
|
|
||||||
golang.org/x/sys v0.0.0-20191018095205-727590c5006e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
|
||||||
google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA=
|
|
||||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
|
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
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=
|
|
||||||
|
|
42
main.go
42
main.go
|
@ -4,16 +4,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"go.uber.org/fx"
|
|
||||||
|
|
||||||
"galched-bot/modules/discord"
|
"galched-bot/modules/discord"
|
||||||
"galched-bot/modules/grace"
|
"galched-bot/modules/grace"
|
||||||
"galched-bot/modules/patpet"
|
|
||||||
"galched-bot/modules/settings"
|
"galched-bot/modules/settings"
|
||||||
"galched-bot/modules/subday"
|
"galched-bot/modules/subday"
|
||||||
"galched-bot/modules/twitchat"
|
|
||||||
"galched-bot/modules/web"
|
"go.uber.org/fx"
|
||||||
"galched-bot/modules/youtube"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -25,8 +21,6 @@ type (
|
||||||
Context context.Context
|
Context context.Context
|
||||||
Discord *discord.Discord
|
Discord *discord.Discord
|
||||||
Settings *settings.Settings
|
Settings *settings.Settings
|
||||||
Chat *twitchat.TwitchIRC
|
|
||||||
Server *web.WebServer
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,39 +35,14 @@ func start(p appParam) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print("discord: cannot start instance", err)
|
log.Print("discord: cannot start instance", err)
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
log.Printf("main: discord instance running")
|
log.Printf("main: discord instance running")
|
||||||
|
|
||||||
err = p.Chat.Start()
|
|
||||||
if err != nil {
|
|
||||||
log.Print("chat: cannot start instance", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("main: twitch chat instance running")
|
|
||||||
|
|
||||||
err = p.Server.Start()
|
|
||||||
if err != nil {
|
|
||||||
log.Print("web: cannot start instance", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("main: web server instance running")
|
|
||||||
|
|
||||||
log.Printf("main: — — —")
|
log.Printf("main: — — —")
|
||||||
|
|
||||||
<-p.Context.Done()
|
<-p.Context.Done()
|
||||||
log.Print("main: stopping galched-bot")
|
log.Print("main: stopping galched-bot")
|
||||||
|
|
||||||
err = p.Server.Stop(p.Context)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("web: cannot stop instance", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = p.Chat.Stop()
|
|
||||||
if err != nil {
|
|
||||||
log.Print("chat: cannot stop instance", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = p.Discord.Stop()
|
err = p.Discord.Stop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print("discord: cannot stop instance", err)
|
log.Print("discord: cannot stop instance", err)
|
||||||
|
@ -88,8 +57,7 @@ func main() {
|
||||||
var err error
|
var err error
|
||||||
app := fx.New(
|
app := fx.New(
|
||||||
fx.Logger(new(silentPrinter)),
|
fx.Logger(new(silentPrinter)),
|
||||||
fx.Provide(settings.New, grace.New, discord.New, subday.New,
|
fx.Provide(settings.New, grace.New, discord.New, subday.New),
|
||||||
twitchat.New, web.New, youtube.New, patpet.New),
|
|
||||||
fx.Invoke(start))
|
fx.Invoke(start))
|
||||||
|
|
||||||
err = app.Start(context.Background())
|
err = app.Start(context.Background())
|
||||||
|
|
|
@ -31,10 +31,6 @@ func New(s *settings.Settings, subday *subday.Subday) (*Discord, error) {
|
||||||
processor.AddHandler(subdayHandler)
|
processor.AddHandler(subdayHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, songHandler := range SongHandlers(s) {
|
|
||||||
processor.AddHandler(songHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("discord: added %d message handlers", len(processor.handlers))
|
log.Printf("discord: added %d message handlers", len(processor.handlers))
|
||||||
if len(processor.handlers) > 0 {
|
if len(processor.handlers) > 0 {
|
||||||
for i := range processor.handlers {
|
for i := range processor.handlers {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package discord
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
|
@ -52,8 +53,10 @@ func (h *HandlerProcessor) Process(s *discordgo.Session, m *discordgo.MessageCre
|
||||||
|
|
||||||
if strings.HasPrefix(m.Content, "!galched") {
|
if strings.HasPrefix(m.Content, "!galched") {
|
||||||
LogMessage(m)
|
LogMessage(m)
|
||||||
SendMessage(s, m, h.HelpMessage())
|
_, err := s.ChannelMessageSend(m.ChannelID, h.HelpMessage())
|
||||||
return
|
if err != nil {
|
||||||
|
log.Printf("discord: cannot send message [%s]: %v", h.HelpMessage(), err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range h.handlers {
|
for i := range h.handlers {
|
||||||
|
|
|
@ -1,191 +0,0 @@
|
||||||
package discord
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.uber.org/atomic"
|
|
||||||
|
|
||||||
"galched-bot/modules/settings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
songData [][]byte
|
|
||||||
|
|
||||||
SongHandler struct {
|
|
||||||
globalLock *atomic.Bool
|
|
||||||
songLock *atomic.Bool
|
|
||||||
|
|
||||||
song songData
|
|
||||||
signature string
|
|
||||||
description string
|
|
||||||
voiceChannel string
|
|
||||||
permissions []string
|
|
||||||
timeout time.Duration
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (h *SongHandler) Signature() string {
|
|
||||||
return h.signature
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SongHandler) Description() string {
|
|
||||||
return h.description
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SongHandler) IsValid(msg string) bool {
|
|
||||||
return msg == h.signature
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *SongHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) {
|
|
||||||
var permitted bool
|
|
||||||
for i := range h.permissions {
|
|
||||||
if m.Author.Username == h.permissions[i] {
|
|
||||||
permitted = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(h.permissions) > 0 && !permitted {
|
|
||||||
log.Printf("discord: unathorized %s message from %s",
|
|
||||||
h.signature, m.Author.Username)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the channel that the message came from.
|
|
||||||
c, err := s.State.Channel(m.ChannelID)
|
|
||||||
if err != nil {
|
|
||||||
// Could not find channel.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the guild for that channel.
|
|
||||||
g, err := s.State.Guild(c.GuildID)
|
|
||||||
if err != nil {
|
|
||||||
// Could not find guild.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for the message sender in that guild's current voice states.
|
|
||||||
LogMessage(m)
|
|
||||||
if h.globalLock.CAS(false, true) {
|
|
||||||
if h.songLock.CAS(false, true) {
|
|
||||||
|
|
||||||
err = playSound(s, g.ID, h.voiceChannel, h.song)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("discord: error playing sound:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.globalLock.Store(false)
|
|
||||||
time.Sleep(h.timeout)
|
|
||||||
defer h.songLock.Store(false)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.globalLock.Store(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SongHandlers(s *settings.Settings) []MessageHandler {
|
|
||||||
result := make([]MessageHandler, 0, len(s.Songs))
|
|
||||||
g := new(atomic.Bool)
|
|
||||||
|
|
||||||
for i := range s.Songs {
|
|
||||||
song, err := loadSong(s.Songs[i].Path)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("discord: error loading song file", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
handler := &SongHandler{
|
|
||||||
globalLock: g,
|
|
||||||
songLock: new(atomic.Bool),
|
|
||||||
song: song,
|
|
||||||
signature: s.Songs[i].Signature,
|
|
||||||
description: s.Songs[i].Description,
|
|
||||||
voiceChannel: s.DiscordVoiceChannel,
|
|
||||||
permissions: s.Songs[i].Permissions,
|
|
||||||
timeout: s.Songs[i].Timeout,
|
|
||||||
}
|
|
||||||
result = append(result, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// playSound plays the current buffer to the provided channel.
|
|
||||||
func playSound(s *discordgo.Session, guildID, channelID string, song songData) error {
|
|
||||||
|
|
||||||
// Join the provided voice channel.
|
|
||||||
vc, err := s.ChannelVoiceJoin(guildID, channelID, false, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sleep for a specified amount of time before playing the sound
|
|
||||||
time.Sleep(250 * time.Millisecond)
|
|
||||||
|
|
||||||
// Start speaking.
|
|
||||||
vc.Speaking(true)
|
|
||||||
|
|
||||||
// Send the buffer data.
|
|
||||||
for _, buff := range song {
|
|
||||||
vc.OpusSend <- buff
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop speaking
|
|
||||||
vc.Speaking(false)
|
|
||||||
|
|
||||||
// Sleep for a specified amount of time before ending.
|
|
||||||
time.Sleep(250 * time.Millisecond)
|
|
||||||
|
|
||||||
// Disconnect from the provided voice channel.
|
|
||||||
vc.Disconnect()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadSong(path string) (songData, error) {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "error opening dca file")
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
opuslen int16
|
|
||||||
buffer = make(songData, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
// Read opus frame length from dca file.
|
|
||||||
err = binary.Read(file, binary.LittleEndian, &opuslen)
|
|
||||||
|
|
||||||
// If this is the end of the file, just return.
|
|
||||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
|
||||||
err := file.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buffer, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "error reading from dca file")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read encoded pcm from dca file.
|
|
||||||
InBuf := make([]byte, opuslen)
|
|
||||||
err = binary.Read(file, binary.LittleEndian, &InBuf)
|
|
||||||
|
|
||||||
// Should not be any end of file errors
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "error reading from dca file")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append encoded pcm data to the buffer.
|
|
||||||
buffer = append(buffer, InBuf)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -38,7 +38,10 @@ func (h *SubdayListHandler) Handle(s *discordgo.Session, m *discordgo.MessageCre
|
||||||
log.Print("discord: cannot obtain guild", err)
|
log.Print("discord: cannot obtain guild", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
message := "Игры предыдущих сабдеев доступны по команде **!subhistory**\n" +
|
message := "Игры предыдущих сабдеев:\n**20.10.18**: _DmC_ -> _Fable 1_ -> _Overcooked 2_\n" +
|
||||||
|
"**17.11.18**: _The Witcher_ -> _Xenus: Белое Золото_ -> _NFS: Underground 2_\n" +
|
||||||
|
"**22.12.18**: _True Crime: Streets of LA_ -> _Serious Sam 3_ -> _Kholat_\n" +
|
||||||
|
"**26.01.19**: _Disney’s Aladdin_ -> _~~Gothic~~_ -> _Scrapland_ -> _Donut County_\n\n" +
|
||||||
"Список игр для следующего сабдея:\n"
|
"Список игр для следующего сабдея:\n"
|
||||||
for k, v := range h.subday.Database() {
|
for k, v := range h.subday.Database() {
|
||||||
nickname := " "
|
nickname := " "
|
||||||
|
@ -55,8 +58,10 @@ func (h *SubdayListHandler) Handle(s *discordgo.Session, m *discordgo.MessageCre
|
||||||
message += fmt.Sprintf(" **- %s** от _%s_\n", game, nickname)
|
message += fmt.Sprintf(" **- %s** от _%s_\n", game, nickname)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
message += "\nВсе команды бота: !galched\n"
|
_, err = s.ChannelMessageSend(m.ChannelID, strings.Trim(message, "\n"))
|
||||||
SendMessage(s, m, strings.Trim(message, "\n"))
|
if err != nil {
|
||||||
|
log.Printf("discord: cannot send message [%s]: %v", message, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubdayAddHandler struct {
|
type SubdayAddHandler struct {
|
||||||
|
@ -136,35 +141,10 @@ loop:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubdayHistoryHandler struct{}
|
|
||||||
|
|
||||||
func (h *SubdayHistoryHandler) Signature() string {
|
|
||||||
return "!subhistory"
|
|
||||||
}
|
|
||||||
func (h *SubdayHistoryHandler) Description() string {
|
|
||||||
return "история прошлых сабдеев"
|
|
||||||
}
|
|
||||||
func (h *SubdayHistoryHandler) IsValid(msg string) bool {
|
|
||||||
return strings.HasPrefix(msg, "!subhistory")
|
|
||||||
}
|
|
||||||
func (h *SubdayHistoryHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) {
|
|
||||||
LogMessage(m)
|
|
||||||
message := "Игры предыдущих сабдеев:\n**20.10.18**: _DmC_ -> _Fable 1_ -> _Overcooked 2_\n" +
|
|
||||||
"**17.11.18**: _The Witcher_ -> _Xenus: Белое Золото_ -> _NFS: Underground 2_\n" +
|
|
||||||
"**22.12.18**: _True Crime: Streets of LA_ -> _Serious Sam 3_ -> _Kholat_\n" +
|
|
||||||
"**26.01.19**: _Disney’s Aladdin_ -> _~~Gothic~~_ -> _Scrapland_ -> _Donut County_\n" +
|
|
||||||
"**24.02.19**: _Tetris 99_ -> _~~Bully~~_ -> _~~GTA: Vice City~~_\n" +
|
|
||||||
"**02.06.19**: _Spec Ops: The Line_ -> _Escape from Tarkov_\n" +
|
|
||||||
"**28.07.19**: _Crypt of the Necrodancer_ -> _My Friend Pedro_ -> _Ape Out_\n" +
|
|
||||||
"\nВсе команды бота: !galched\n"
|
|
||||||
SendMessage(s, m, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SubdayHandlers(s *subday.Subday, r []string) []MessageHandler {
|
func SubdayHandlers(s *subday.Subday, r []string) []MessageHandler {
|
||||||
var result []MessageHandler
|
var result []MessageHandler
|
||||||
|
|
||||||
addHandler := &SubdayAddHandler{s, r}
|
addHandler := &SubdayAddHandler{s, r}
|
||||||
listHandler := &SubdayListHandler{s}
|
listHandler := &SubdayListHandler{s}
|
||||||
histHandler := new(SubdayHistoryHandler)
|
return append(result, addHandler, listHandler)
|
||||||
return append(result, addHandler, listHandler, histHandler)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
package patpet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"galched-bot/modules/settings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Pet struct {
|
|
||||||
sync.RWMutex
|
|
||||||
path string
|
|
||||||
counter int
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(s *settings.Settings) (*Pet, error) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
counter int
|
|
||||||
)
|
|
||||||
|
|
||||||
petData, err := ioutil.ReadFile(s.PetDataPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("pet: cannot read data file", err)
|
|
||||||
log.Print("pet: creating new counter")
|
|
||||||
} else {
|
|
||||||
err = json.Unmarshal(petData, &counter)
|
|
||||||
if err != nil {
|
|
||||||
counter = 0
|
|
||||||
log.Print("pet: cannot unmarshal data file", err)
|
|
||||||
log.Print("pet: creating new counter")
|
|
||||||
} else {
|
|
||||||
log.Print("pet: using previously saved counter")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Pet{
|
|
||||||
RWMutex: sync.RWMutex{},
|
|
||||||
path: s.PetDataPath,
|
|
||||||
counter: counter,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pet) Pet() int {
|
|
||||||
p.Lock()
|
|
||||||
defer p.Unlock()
|
|
||||||
|
|
||||||
p.counter++
|
|
||||||
return p.counter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pet) Counter() int {
|
|
||||||
p.RUnlock()
|
|
||||||
defer p.RUnlock()
|
|
||||||
|
|
||||||
return p.counter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pet) Dump() {
|
|
||||||
p.RLock()
|
|
||||||
defer p.RUnlock()
|
|
||||||
|
|
||||||
data, err := json.Marshal(p.counter)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("pet: cannot marshal counter", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
file, err := os.Create(p.path)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("pet: cannot open counter file", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = fmt.Fprintf(file, string(data))
|
|
||||||
if err != nil {
|
|
||||||
log.Print("pet: cannot write to counter file")
|
|
||||||
}
|
|
||||||
err = file.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Print("pet: cannot close counter file")
|
|
||||||
}
|
|
||||||
log.Print("pet: counter dumped to file:", p.counter)
|
|
||||||
}
|
|
|
@ -1,56 +1,31 @@
|
||||||
package settings
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "5.1.0"
|
version = "3.0.0"
|
||||||
twitchUser = "galchedbot"
|
discordTokenPath = "./tokens/.discordtoken"
|
||||||
twitchIRCRoom = "galched"
|
subdayDataPath = "./backups/subday"
|
||||||
discordTokenPath = "./tokens/.discordtoken"
|
subdayDataDuration = 10 // in seconds
|
||||||
twitchTokenPath = "./tokens/.twitchtoken"
|
|
||||||
subdayDataPath = "./backups/subday"
|
|
||||||
petDataPath = "./backups/pets"
|
|
||||||
youtubeTokenPath = "./tokens/.youtubetoken"
|
|
||||||
webLoginsPath = "./tokens/.weblogins"
|
|
||||||
|
|
||||||
// Permitted roles in discord for subday
|
// Permitted roles in discord for subday
|
||||||
subRole1 = "433672344737677322"
|
subRole1 = "433672344737677322"
|
||||||
subRole2 = "433680494635515904"
|
subRole2 = "433680494635515904"
|
||||||
galchedRole = "301467455497175041"
|
galchedRole = "301467455497175041"
|
||||||
smorcRole = "301470784491356172"
|
smorcRole = "301470784491356172"
|
||||||
|
|
||||||
defaultQueueAddr = ":8888"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
SongInfo struct {
|
|
||||||
Path string
|
|
||||||
Signature string
|
|
||||||
Description string
|
|
||||||
Permissions []string
|
|
||||||
Timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings struct {
|
Settings struct {
|
||||||
Version string
|
Version string
|
||||||
DiscordToken string
|
DiscordToken string
|
||||||
TwitchUser string
|
SubdayDataPath string
|
||||||
TwitchIRCRoom string
|
SubdayJobDuration time.Duration
|
||||||
TwitchToken string
|
PermittedRoles []string
|
||||||
YoutubeToken string
|
|
||||||
SubdayDataPath string
|
|
||||||
PetDataPath string
|
|
||||||
PermittedRoles []string
|
|
||||||
DiscordVoiceChannel string
|
|
||||||
Songs []SongInfo
|
|
||||||
|
|
||||||
QueueAddress string
|
|
||||||
LoginUsers map[string]string
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,59 +34,12 @@ func New() (*Settings, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print("settings: cannot read discord token file", err)
|
log.Print("settings: cannot read discord token file", err)
|
||||||
}
|
}
|
||||||
twitchToken, err := ioutil.ReadFile(twitchTokenPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("settings: cannot read twitch token file", err)
|
|
||||||
}
|
|
||||||
youtubetoken, err := ioutil.ReadFile(youtubeTokenPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("settings: cannot read twitch token file", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
webLogins := make(map[string]string)
|
|
||||||
webLoginsRaw, err := ioutil.ReadFile(webLoginsPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("settings: cannot read web login file", err)
|
|
||||||
} else {
|
|
||||||
err = json.Unmarshal(webLoginsRaw, &webLogins)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("settings: cannot parse web login file", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Settings{
|
return &Settings{
|
||||||
Version: version,
|
Version: version,
|
||||||
DiscordToken: string(discordToken),
|
DiscordToken: string(discordToken),
|
||||||
TwitchToken: string(twitchToken),
|
SubdayDataPath: subdayDataPath,
|
||||||
YoutubeToken: string(youtubetoken),
|
SubdayJobDuration: subdayDataDuration * time.Second,
|
||||||
TwitchUser: twitchUser,
|
PermittedRoles: []string{subRole1, subRole2, galchedRole, smorcRole},
|
||||||
TwitchIRCRoom: twitchIRCRoom,
|
|
||||||
SubdayDataPath: subdayDataPath,
|
|
||||||
PetDataPath: petDataPath,
|
|
||||||
DiscordVoiceChannel: "301793085522706432",
|
|
||||||
PermittedRoles: []string{subRole1, subRole2, galchedRole, smorcRole},
|
|
||||||
Songs: []SongInfo{
|
|
||||||
{
|
|
||||||
Path: "songs/polka.dca",
|
|
||||||
Signature: "!song",
|
|
||||||
Description: "сыграть гимн галчед (только для избранных)",
|
|
||||||
Permissions: []string{"AlexV", "Rummy_Quamox", "Lidiya_owl"},
|
|
||||||
Timeout: 10 * time.Second,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "songs/whisper.dca",
|
|
||||||
Signature: "!sax",
|
|
||||||
Description: "kreygasm",
|
|
||||||
Timeout: 20 * time.Second,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "songs/st.dca",
|
|
||||||
Signature: "!chiki",
|
|
||||||
Description: "briki v damki",
|
|
||||||
Timeout: 20 * time.Second,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
QueueAddress: defaultQueueAddr,
|
|
||||||
LoginUsers: webLogins,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,257 +0,0 @@
|
||||||
package twitchat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"hash/fnv"
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gempir/go-twitch-irc/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const emoteMsg = "!emote"
|
|
||||||
|
|
||||||
var (
|
|
||||||
emotes = []string{
|
|
||||||
"4Head",
|
|
||||||
"ANELE",
|
|
||||||
"ArgieB8",
|
|
||||||
"ArsonNoSexy",
|
|
||||||
"AsexualPride",
|
|
||||||
"AsianGlow",
|
|
||||||
"BCWarrior",
|
|
||||||
"BOP",
|
|
||||||
"BabyRage",
|
|
||||||
"BatChest",
|
|
||||||
"BegWan",
|
|
||||||
"BibleThump",
|
|
||||||
"BigBrother",
|
|
||||||
"BigPhish",
|
|
||||||
"BisexualPride",
|
|
||||||
"BlargNaut",
|
|
||||||
"BlessRNG",
|
|
||||||
"BloodTrail",
|
|
||||||
"BrainSlug",
|
|
||||||
"BrokeBack",
|
|
||||||
"BuddhaBar",
|
|
||||||
"CarlSmile",
|
|
||||||
"ChefFrank",
|
|
||||||
"CoolCat",
|
|
||||||
"CoolStoryBob",
|
|
||||||
"CorgiDerp",
|
|
||||||
"CrreamAwk",
|
|
||||||
"CurseLit",
|
|
||||||
"DAESuppy",
|
|
||||||
"DBstyle",
|
|
||||||
"DansGame",
|
|
||||||
"DarkMode",
|
|
||||||
"DatSheffy",
|
|
||||||
"DendiFace",
|
|
||||||
"DogFace",
|
|
||||||
"DoritosChip",
|
|
||||||
"DrinkPurple",
|
|
||||||
"DxCat",
|
|
||||||
"EarthDay",
|
|
||||||
"EleGiggle",
|
|
||||||
"EntropyWins",
|
|
||||||
"FBBlock",
|
|
||||||
"FBCatch",
|
|
||||||
"FBChallenge",
|
|
||||||
"FBPass",
|
|
||||||
"FBPenalty",
|
|
||||||
"FBRun",
|
|
||||||
"FBSpiral",
|
|
||||||
"FBtouchdown",
|
|
||||||
"FUNgineer",
|
|
||||||
"FailFish",
|
|
||||||
"FrankerZ",
|
|
||||||
"FreakinStinkin",
|
|
||||||
"FutureMan",
|
|
||||||
"GayPride",
|
|
||||||
"GenderFluidPride",
|
|
||||||
"GingerPower",
|
|
||||||
"GivePLZ",
|
|
||||||
"GrammarKing",
|
|
||||||
"GreenTeam",
|
|
||||||
"GunRun",
|
|
||||||
"HSCheers",
|
|
||||||
"HSWP",
|
|
||||||
"HassaanChop",
|
|
||||||
"HassanChop",
|
|
||||||
"HeyGuys",
|
|
||||||
"HolidayCookie",
|
|
||||||
"HolidayLog",
|
|
||||||
"HolidayOrnament",
|
|
||||||
"HolidayPresent",
|
|
||||||
"HolidaySanta",
|
|
||||||
"HolidayTree",
|
|
||||||
"HotPokket",
|
|
||||||
"HumbleLife",
|
|
||||||
"IntersexPride",
|
|
||||||
"InuyoFace",
|
|
||||||
"ItsBoshyTime",
|
|
||||||
"JKanStyle",
|
|
||||||
"Jebaited",
|
|
||||||
"JonCarnage",
|
|
||||||
"KAPOW",
|
|
||||||
"Kappa",
|
|
||||||
"KappaClaus",
|
|
||||||
"KappaPride",
|
|
||||||
"KappaRoss",
|
|
||||||
"KappaWealth",
|
|
||||||
"Kappu",
|
|
||||||
"Keepo",
|
|
||||||
"KevinTurtle",
|
|
||||||
"Kippa",
|
|
||||||
"KomodoHype",
|
|
||||||
"KonCha",
|
|
||||||
"Kreygasm",
|
|
||||||
"LUL",
|
|
||||||
"LesbianPride",
|
|
||||||
"MVGame",
|
|
||||||
"Mau5",
|
|
||||||
"MaxLOL",
|
|
||||||
"MercyWing1",
|
|
||||||
"MercyWing2",
|
|
||||||
"MikeHogu",
|
|
||||||
"MingLee",
|
|
||||||
"MorphinTime",
|
|
||||||
"MrDestructoid",
|
|
||||||
"NinjaGrumpy",
|
|
||||||
"NomNom",
|
|
||||||
"NonBinaryPride",
|
|
||||||
"NotATK",
|
|
||||||
"NotLikeThis",
|
|
||||||
"OSFrog",
|
|
||||||
"OhMyDog",
|
|
||||||
"OneHand",
|
|
||||||
"OpieOP",
|
|
||||||
"OptimizePrime",
|
|
||||||
"PJSalt",
|
|
||||||
"PJSugar",
|
|
||||||
"PMSTwin",
|
|
||||||
"PRChase",
|
|
||||||
"PanicVis",
|
|
||||||
"PansexualPride",
|
|
||||||
"PartyHat",
|
|
||||||
"PartyTime",
|
|
||||||
"PeoplesChamp",
|
|
||||||
"PermaSmug",
|
|
||||||
"PicoMause",
|
|
||||||
"PinkMercy",
|
|
||||||
"PipeHype",
|
|
||||||
"PixelBob",
|
|
||||||
"PogChamp",
|
|
||||||
"Poooound",
|
|
||||||
"PopCorn",
|
|
||||||
"PorscheWIN",
|
|
||||||
"PowerUpL",
|
|
||||||
"PowerUpR",
|
|
||||||
"PraiseIt",
|
|
||||||
"PrimeMe",
|
|
||||||
"PunOko",
|
|
||||||
"PunchTrees",
|
|
||||||
"PurpleStar",
|
|
||||||
"RaccAttack",
|
|
||||||
"RalpherZ",
|
|
||||||
"RedCoat",
|
|
||||||
"RedTeam",
|
|
||||||
"ResidentSleeper",
|
|
||||||
"RitzMitz",
|
|
||||||
"RlyTho",
|
|
||||||
"RuleFive",
|
|
||||||
"SMOrc",
|
|
||||||
"SSSsss",
|
|
||||||
"SabaPing",
|
|
||||||
"SeemsGood",
|
|
||||||
"SeriousSloth",
|
|
||||||
"ShadyLulu",
|
|
||||||
"ShazBotstix",
|
|
||||||
"SingsMic",
|
|
||||||
"SingsNote",
|
|
||||||
"SmoocherZ",
|
|
||||||
"SoBayed",
|
|
||||||
"SoonerLater",
|
|
||||||
"Squid1",
|
|
||||||
"Squid2",
|
|
||||||
"Squid3",
|
|
||||||
"Squid4",
|
|
||||||
"StinkyCheese",
|
|
||||||
"StoneLightning",
|
|
||||||
"StrawBeary",
|
|
||||||
"SuperVinlin",
|
|
||||||
"SwiftRage",
|
|
||||||
"TBAngel",
|
|
||||||
"TF2John",
|
|
||||||
"TPFufun",
|
|
||||||
"TPcrunchyroll",
|
|
||||||
"TTours",
|
|
||||||
"TakeNRG",
|
|
||||||
"TearGlove",
|
|
||||||
"TehePelo",
|
|
||||||
"ThankEgg",
|
|
||||||
"TheIlluminati",
|
|
||||||
"TheRinger",
|
|
||||||
"TheTarFu",
|
|
||||||
"TheThing",
|
|
||||||
"ThunBeast",
|
|
||||||
"TinyFace",
|
|
||||||
"TombRaid",
|
|
||||||
"TooSpicy",
|
|
||||||
"TransgenderPride",
|
|
||||||
"TriHard",
|
|
||||||
"TwitchLit",
|
|
||||||
"TwitchRPG",
|
|
||||||
"TwitchSings",
|
|
||||||
"TwitchUnity",
|
|
||||||
"TwitchVotes",
|
|
||||||
"UWot",
|
|
||||||
"UnSane",
|
|
||||||
"UncleNox",
|
|
||||||
"VoHiYo",
|
|
||||||
"VoteNay",
|
|
||||||
"VoteYea",
|
|
||||||
"WTRuck",
|
|
||||||
"WholeWheat",
|
|
||||||
"WutFace",
|
|
||||||
"YouDontSay",
|
|
||||||
"YouWHY",
|
|
||||||
"bleedPurple",
|
|
||||||
"cmonBruh",
|
|
||||||
"copyThis",
|
|
||||||
"duDudu",
|
|
||||||
"imGlitch",
|
|
||||||
"mcaT",
|
|
||||||
"panicBasket",
|
|
||||||
"pastaThat",
|
|
||||||
"riPepperonis",
|
|
||||||
"twitchRaid",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
dailyEmote struct{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func DailyEmote() *dailyEmote {
|
|
||||||
return new(dailyEmote)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *dailyEmote) IsValid(m *twitch.PrivateMessage) bool {
|
|
||||||
return (m.Tags["msg-id"] == "highlighted-message") && m.Message == emoteMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *dailyEmote) Handle(m *twitch.PrivateMessage, r Responser) {
|
|
||||||
data := time.Now().Format("2006-01-02") + m.User.DisplayName
|
|
||||||
rng := rand.New(rand.NewSource(hashSeed(data)))
|
|
||||||
emote := emotes[rng.Intn(len(emotes))]
|
|
||||||
|
|
||||||
msg := fmt.Sprintf("@%s твой эмоут дня: %s", m.User.DisplayName, emote)
|
|
||||||
r.Say(m.Channel, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashSeed(s string) int64 {
|
|
||||||
h := fnv.New64()
|
|
||||||
h.Write([]byte(s))
|
|
||||||
return int64(h.Sum64())
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package twitchat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gempir/go-twitch-irc/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
dupHandler struct {
|
|
||||||
lastMessage string
|
|
||||||
counter int
|
|
||||||
dupMinimal int
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const DupMinimal = 3
|
|
||||||
|
|
||||||
func DupHandler() PrivateMessageHandler {
|
|
||||||
return &dupHandler{
|
|
||||||
lastMessage: "",
|
|
||||||
counter: 0,
|
|
||||||
dupMinimal: DupMinimal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *dupHandler) IsValid(m *twitch.PrivateMessage) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *dupHandler) Handle(m *twitch.PrivateMessage, r Responser) {
|
|
||||||
data := strings.Fields(m.Message)
|
|
||||||
for i := range data {
|
|
||||||
if data[i] == h.lastMessage {
|
|
||||||
h.counter++
|
|
||||||
} else {
|
|
||||||
if h.counter >= h.dupMinimal {
|
|
||||||
msg := fmt.Sprintf("%d %s подряд", h.counter, h.lastMessage)
|
|
||||||
r.Say(m.Channel, msg)
|
|
||||||
log.Print("chat: ", msg)
|
|
||||||
}
|
|
||||||
h.counter = 1
|
|
||||||
h.lastMessage = data[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
package twitchat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gempir/go-twitch-irc/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Responser interface {
|
|
||||||
Say(channel, message string)
|
|
||||||
}
|
|
||||||
|
|
||||||
PrivateMessageHandler interface {
|
|
||||||
IsValid(m *twitch.PrivateMessage) bool
|
|
||||||
Handle(m *twitch.PrivateMessage, r Responser)
|
|
||||||
}
|
|
||||||
)
|
|
|
@ -1,23 +0,0 @@
|
||||||
package twitchat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/gempir/go-twitch-irc/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
logCheck struct{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func LogCheck() PrivateMessageHandler {
|
|
||||||
return new(logCheck)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *logCheck) IsValid(m *twitch.PrivateMessage) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *logCheck) Handle(m *twitch.PrivateMessage, r Responser) {
|
|
||||||
log.Print("chat <", m.User.DisplayName, "> : ", m.Message)
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package twitchat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"galched-bot/modules/patpet"
|
|
||||||
"github.com/gempir/go-twitch-irc/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
petMsg1 = "!погладь"
|
|
||||||
petMsg2 = "!гладь"
|
|
||||||
petMsg3 = "!погладить"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
petCat struct {
|
|
||||||
cat *patpet.Pet
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func PetCat(pet *patpet.Pet) *petCat {
|
|
||||||
return &petCat{
|
|
||||||
cat: pet,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *petCat) IsValid(m *twitch.PrivateMessage) bool {
|
|
||||||
return (m.Tags["msg-id"] == "highlighted-message") && (strings.HasPrefix(m.Message, petMsg1) ||
|
|
||||||
strings.HasPrefix(m.Message, petMsg2) ||
|
|
||||||
strings.HasPrefix(m.Message, petMsg3))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *petCat) Handle(m *twitch.PrivateMessage, r Responser) {
|
|
||||||
msg := fmt.Sprintf("Котэ поглажен уже %d раз(а) InuyoFace", h.cat.Pet())
|
|
||||||
r.Say(m.Channel, msg)
|
|
||||||
h.cat.Dump()
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
package twitchat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gempir/go-twitch-irc/v2"
|
|
||||||
|
|
||||||
"galched-bot/modules/youtube"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
songMsg = "!song"
|
|
||||||
reqPrefix = "!req " // space in the end is important
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
songRequest struct {
|
|
||||||
r *youtube.Requester
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func SongRequest(r *youtube.Requester) PrivateMessageHandler {
|
|
||||||
return &songRequest{r: r}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *songRequest) IsValid(m *twitch.PrivateMessage) bool {
|
|
||||||
return (strings.HasPrefix(m.Message, reqPrefix) && m.Tags["msg-id"] == "highlighted-message") ||
|
|
||||||
strings.TrimSpace(m.Message) == songMsg
|
|
||||||
// return strings.HasPrefix(m.Message, reqPrefix) || strings.TrimSpace(m.Message) == songMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *songRequest) Handle(m *twitch.PrivateMessage, r Responser) {
|
|
||||||
if strings.TrimSpace(m.Message) == "!song" {
|
|
||||||
list := h.r.List()
|
|
||||||
if len(list) > 0 {
|
|
||||||
line := fmt.Sprintf("Сейчас играет: <%s>", list[0].Title)
|
|
||||||
r.Say(m.Channel, line)
|
|
||||||
} else {
|
|
||||||
r.Say(m.Channel, "Очередь видео пуста")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
query := strings.TrimPrefix(m.Message, "!req ")
|
|
||||||
if len(query) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
chatMsg, err := h.r.AddVideo(query, m.User.DisplayName)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("yt: cannot add song from msg <%s>, err: %v", m.Message, err)
|
|
||||||
if len(chatMsg) > 0 {
|
|
||||||
r.Say(m.Channel, m.User.DisplayName+" "+chatMsg)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.Say(m.Channel, m.User.DisplayName+" добавил "+chatMsg)
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
package twitchat
|
|
||||||
|
|
||||||
import (
|
|
||||||
"galched-bot/modules/patpet"
|
|
||||||
"galched-bot/modules/settings"
|
|
||||||
"galched-bot/modules/youtube"
|
|
||||||
|
|
||||||
"github.com/gempir/go-twitch-irc/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
TwitchIRC struct {
|
|
||||||
username string
|
|
||||||
chat *twitch.Client
|
|
||||||
handlers []PrivateMessageHandler
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(s *settings.Settings, r *youtube.Requester, pet *patpet.Pet) (*TwitchIRC, error) {
|
|
||||||
var irc = new(TwitchIRC)
|
|
||||||
|
|
||||||
irc.username = s.TwitchUser
|
|
||||||
|
|
||||||
irc.handlers = append(irc.handlers, DupHandler())
|
|
||||||
irc.handlers = append(irc.handlers, DailyEmote())
|
|
||||||
irc.handlers = append(irc.handlers, SongRequest(r))
|
|
||||||
irc.handlers = append(irc.handlers, PetCat(pet))
|
|
||||||
// irc.handlers = append(irc.handlers, LogCheck())
|
|
||||||
|
|
||||||
irc.chat = twitch.NewClient(s.TwitchUser, s.TwitchToken)
|
|
||||||
irc.chat.OnPrivateMessage(irc.PrivateMessageHandler)
|
|
||||||
irc.chat.Join(s.TwitchIRCRoom)
|
|
||||||
|
|
||||||
return irc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TwitchIRC) Start() error {
|
|
||||||
go func() {
|
|
||||||
err := c.chat.Connect()
|
|
||||||
_ = err // no point in error because disconnect will be called anyway
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TwitchIRC) Stop() error {
|
|
||||||
return c.chat.Disconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TwitchIRC) PrivateMessageHandler(msg twitch.PrivateMessage) {
|
|
||||||
if msg.User.Name == c.username {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := range c.handlers {
|
|
||||||
if c.handlers[i].IsValid(&msg) {
|
|
||||||
c.handlers[i].Handle(&msg, c.chat)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,159 +0,0 @@
|
||||||
package web
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"galched-bot/modules/settings"
|
|
||||||
"galched-bot/modules/youtube"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
WebServer struct {
|
|
||||||
server http.Server
|
|
||||||
r *youtube.Requester
|
|
||||||
|
|
||||||
users map[string]string
|
|
||||||
authed map[string]struct{}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(s *settings.Settings, r *youtube.Requester) *WebServer {
|
|
||||||
srv := http.Server{
|
|
||||||
Addr: s.QueueAddress,
|
|
||||||
}
|
|
||||||
|
|
||||||
webServer := &WebServer{
|
|
||||||
server: srv,
|
|
||||||
r: r,
|
|
||||||
|
|
||||||
users: s.LoginUsers,
|
|
||||||
authed: make(map[string]struct{}, 10),
|
|
||||||
}
|
|
||||||
|
|
||||||
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
if request.Method != http.MethodGet {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if webServer.IsAuthorized(request) {
|
|
||||||
http.ServeFile(writer, request, "web/index.html")
|
|
||||||
} else {
|
|
||||||
http.Redirect(writer, request, "/login", 301)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
http.HandleFunc("/login", func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
if request.Method == http.MethodGet {
|
|
||||||
http.ServeFile(writer, request, "web/login.html")
|
|
||||||
return
|
|
||||||
} else if request.Method != http.MethodPost {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
login := request.FormValue("login")
|
|
||||||
pwd := request.FormValue("password")
|
|
||||||
|
|
||||||
log.Print("web: trying to log in with user: ", login)
|
|
||||||
|
|
||||||
if webServer.IsRegistered(login, pwd) {
|
|
||||||
webServer.Authorize(writer)
|
|
||||||
} else {
|
|
||||||
log.Print("web: incorrect password attempt")
|
|
||||||
}
|
|
||||||
http.Redirect(writer, request, "/", http.StatusSeeOther)
|
|
||||||
})
|
|
||||||
|
|
||||||
http.HandleFunc("/scripts.js", func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
if request.Method != http.MethodGet {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.ServeFile(writer, request, "web/scripts.js")
|
|
||||||
})
|
|
||||||
|
|
||||||
http.HandleFunc("/style.css", func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
if request.Method != http.MethodGet {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.ServeFile(writer, request, "web/style.css")
|
|
||||||
})
|
|
||||||
|
|
||||||
http.HandleFunc("/queue", func(writer http.ResponseWriter, request *http.Request) {
|
|
||||||
if !webServer.IsAuthorized(request) {
|
|
||||||
http.Error(writer, "not authorized", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch request.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
case http.MethodPost:
|
|
||||||
body, err := ioutil.ReadAll(request.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("web: cannot read body msg, %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
id := string(body)
|
|
||||||
if len(id) != youtube.YoutubeIDLength && len(id) > 0 {
|
|
||||||
log.Printf("web: incorrect data in body, <%s>", id)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.Remove(id)
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writer.Header().Set("Content-Type", "application/json")
|
|
||||||
writer.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
json.NewEncoder(writer).Encode(webServer.r.List())
|
|
||||||
})
|
|
||||||
|
|
||||||
return webServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s WebServer) Start() error {
|
|
||||||
go func() {
|
|
||||||
s.server.ListenAndServe()
|
|
||||||
}()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s WebServer) Stop(ctx context.Context) error {
|
|
||||||
return s.server.Shutdown(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s WebServer) IsAuthorized(request *http.Request) bool {
|
|
||||||
if cookie, err := request.Cookie("session"); err == nil {
|
|
||||||
if _, ok := s.authed[cookie.Value]; ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s WebServer) IsRegistered(login, pwd string) bool {
|
|
||||||
return s.users[login] == pwd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s WebServer) Authorize(response http.ResponseWriter) {
|
|
||||||
var byteKey = make([]byte, 16)
|
|
||||||
rand.Read(byteKey[:])
|
|
||||||
stringKey := hex.EncodeToString(byteKey)
|
|
||||||
|
|
||||||
s.authed[stringKey] = struct{}{}
|
|
||||||
log.Print("web: authenticated new user")
|
|
||||||
|
|
||||||
expires := time.Now().AddDate(0, 1, 0)
|
|
||||||
http.SetCookie(response, &http.Cookie{
|
|
||||||
Name: "session",
|
|
||||||
Value: stringKey,
|
|
||||||
Path: "/",
|
|
||||||
Expires: expires,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,195 +0,0 @@
|
||||||
package youtube
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"google.golang.org/api/option"
|
|
||||||
"google.golang.org/api/youtube/v3"
|
|
||||||
|
|
||||||
"galched-bot/modules/settings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
YoutubeIDLength = 11
|
|
||||||
youtubeRegexpID = `^.*((youtu.be\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?\s]*).*`
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
urlRegex = regexp.MustCompile(youtubeRegexpID)
|
|
||||||
durationRegex = regexp.MustCompile(`P(?P<years>\d+Y)?(?P<months>\d+M)?(?P<days>\d+D)?T?(?P<hours>\d+H)?(?P<minutes>\d+M)?(?P<seconds>\d+S)?`)
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Video struct {
|
|
||||||
ID string
|
|
||||||
Title string
|
|
||||||
From string
|
|
||||||
Duration string
|
|
||||||
|
|
||||||
Upvotes uint64
|
|
||||||
Downvotes uint64
|
|
||||||
Views uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
Requester struct {
|
|
||||||
mu *sync.RWMutex
|
|
||||||
|
|
||||||
srv *youtube.Service
|
|
||||||
requests []Video
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func New(ctx context.Context, s *settings.Settings) (*Requester, error) {
|
|
||||||
srv, err := youtube.NewService(ctx, option.WithAPIKey(s.YoutubeToken))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Requester{
|
|
||||||
mu: new(sync.RWMutex),
|
|
||||||
srv: srv,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Requester) AddVideo(query, from string) (string, error) {
|
|
||||||
var (
|
|
||||||
id string
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// try parse video id from the query
|
|
||||||
id, err = videoID(query)
|
|
||||||
if err != nil {
|
|
||||||
// if we can't fo that, then search for the query
|
|
||||||
resp, err := r.srv.Search.List("snippet").Type("video").MaxResults(1).Q(query).Do()
|
|
||||||
if err != nil || len(resp.Items) == 0 || resp.Items[0].Id == nil {
|
|
||||||
return "", fmt.Errorf("cannot parse youtube id: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
id = resp.Items[0].Id.VideoId
|
|
||||||
}
|
|
||||||
|
|
||||||
// get video info from api
|
|
||||||
resp, err := r.srv.Videos.List("snippet,statistics,contentDetails").Id(id).Do()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("cannot send request to youtube api: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if response have all required fields
|
|
||||||
if len(resp.Items) == 0 {
|
|
||||||
return "", errors.New("youtube api response does not contain items")
|
|
||||||
}
|
|
||||||
if resp.Items[0].Snippet == nil {
|
|
||||||
return "", errors.New("youtube api response does not contain snippet")
|
|
||||||
}
|
|
||||||
if resp.Items[0].Statistics == nil {
|
|
||||||
return "", errors.New("youtube api response does not contain statistics")
|
|
||||||
}
|
|
||||||
if resp.Items[0].ContentDetails == nil {
|
|
||||||
return "", errors.New("youtube api response does not contain content details")
|
|
||||||
}
|
|
||||||
|
|
||||||
// check length of the video not more than 5 minutes
|
|
||||||
if parseDuration(resp.Items[0].ContentDetails.Duration) == 0 {
|
|
||||||
err = errors.New("видео не должно быть трансляцией")
|
|
||||||
return err.Error(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check video is not live
|
|
||||||
if parseDuration(resp.Items[0].ContentDetails.Duration) > time.Minute*5 {
|
|
||||||
err = errors.New("видео должно быть короче 5 минут")
|
|
||||||
return err.Error(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
|
|
||||||
// check if video already in the queue
|
|
||||||
for i := range r.requests {
|
|
||||||
if r.requests[i].ID == id {
|
|
||||||
err = errors.New("видео уже есть в очереди")
|
|
||||||
return err.Error(), err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.requests = append(r.requests, Video{
|
|
||||||
ID: id,
|
|
||||||
From: from,
|
|
||||||
Duration: strings.ToLower(resp.Items[0].ContentDetails.Duration[2:]),
|
|
||||||
Title: resp.Items[0].Snippet.Title,
|
|
||||||
Upvotes: resp.Items[0].Statistics.LikeCount,
|
|
||||||
Views: resp.Items[0].Statistics.ViewCount,
|
|
||||||
Downvotes: resp.Items[0].Statistics.DislikeCount,
|
|
||||||
})
|
|
||||||
log.Printf("yt: added video < %s > from < %s >\n", resp.Items[0].Snippet.Title, from)
|
|
||||||
|
|
||||||
return resp.Items[0].Snippet.Title, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Requester) List() []Video {
|
|
||||||
r.mu.RLock()
|
|
||||||
defer r.mu.RUnlock()
|
|
||||||
|
|
||||||
result := make([]Video, len(r.requests))
|
|
||||||
copy(result, r.requests)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Requester) Remove(id string) {
|
|
||||||
r.mu.Lock()
|
|
||||||
defer r.mu.Unlock()
|
|
||||||
|
|
||||||
for i := range r.requests {
|
|
||||||
if r.requests[i].ID == id {
|
|
||||||
r.requests = append(r.requests[:i], r.requests[i+1:]...)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func videoID(url string) (string, error) {
|
|
||||||
result := urlRegex.FindStringSubmatch(url)
|
|
||||||
|
|
||||||
ln := len(result)
|
|
||||||
if ln == 0 || len(result[ln-1]) != YoutubeIDLength {
|
|
||||||
return "", fmt.Errorf("id haven't matched in \"%s\"", url)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result[ln-1], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDuration(str string) time.Duration {
|
|
||||||
matches := durationRegex.FindStringSubmatch(str)
|
|
||||||
|
|
||||||
years := parseInt64(matches[1])
|
|
||||||
months := parseInt64(matches[2])
|
|
||||||
days := parseInt64(matches[3])
|
|
||||||
hours := parseInt64(matches[4])
|
|
||||||
minutes := parseInt64(matches[5])
|
|
||||||
seconds := parseInt64(matches[6])
|
|
||||||
|
|
||||||
hour := int64(time.Hour)
|
|
||||||
minute := int64(time.Minute)
|
|
||||||
second := int64(time.Second)
|
|
||||||
return time.Duration(years*24*365*hour + months*30*24*hour + days*24*hour + hours*hour + minutes*minute + seconds*second)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseInt64(value string) int64 {
|
|
||||||
if len(value) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
parsed, err := strconv.Atoi(value[:len(value)-1])
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int64(parsed)
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
package youtube
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPlayground(t *testing.T) {
|
|
||||||
youtubeTokenPath := "../../tokens/.youtubetoken"
|
|
||||||
youtubetoken, err := ioutil.ReadFile(youtubeTokenPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("cannot read youtube token: %v", err)
|
|
||||||
}
|
|
||||||
_ = youtubetoken
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFetchID(t *testing.T) {
|
|
||||||
positiveCases := [][]string{
|
|
||||||
{
|
|
||||||
"https://www.youtube.com/watch?v=_v5IzvVTw7A&feature=feedrec_grec_index",
|
|
||||||
"_v5IzvVTw7A",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"https://www.youtube.com/watch?v=_v5IzvVTw7A#t=0m10s",
|
|
||||||
"_v5IzvVTw7A",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"https://www.youtube.com/embed/_v5IzvVTw7A?rel=0",
|
|
||||||
"_v5IzvVTw7A",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"https://www.youtube.com/watch?v=_v5IzvVTw7A ", // multiple spaces in the end
|
|
||||||
"_v5IzvVTw7A",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"https://youtu.be/_v5IzvVTw7A",
|
|
||||||
"_v5IzvVTw7A",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"https://www.youtube.com/watch?v=PCp2iXA1uLE&list=PLvx4lPhqncyf10ymYz8Ph8EId0cafzhdZ&index=2&t=0s",
|
|
||||||
"PCp2iXA1uLE",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
negativeCases := []string{
|
|
||||||
"https://youtu.be/_vIzvVTw7A", // short video
|
|
||||||
"https://vimeo.com/_v5IzvVTw7A", // incorrect domain
|
|
||||||
"youtube prime video",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range positiveCases {
|
|
||||||
res, err := videoID(testCase[0])
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("expecting error: <nil>, got: <%v>, url: %s\n", err, testCase[0])
|
|
||||||
}
|
|
||||||
if res != testCase[1] {
|
|
||||||
t.Errorf("expecting result: %s, got: %s\n", testCase[0], res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range negativeCases {
|
|
||||||
_, err := videoID(testCase)
|
|
||||||
if err == nil {
|
|
||||||
t.Error("expecting error: <non nil>, got: <nil>")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
# Galched Bot
|
|
||||||
|
|
||||||
This bot is made for galched twitch and discord channel
|
|
|
@ -1,12 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" type="text/css" href="./style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="player"></div>
|
|
||||||
<div><button onclick="nextVideo()">Skip song</button></div>
|
|
||||||
<div id="video_queue"></div>
|
|
||||||
<script src="scripts.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,14 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<form action="./login" method="post">
|
|
||||||
<div class="container">
|
|
||||||
<input type="text" placeholder="Username" name="login" required> <br><br>
|
|
||||||
|
|
||||||
<input type="password" placeholder="Password" name="password" required> <br><br>
|
|
||||||
|
|
||||||
<button type="submit">Login</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
120
web/scripts.js
120
web/scripts.js
|
@ -1,120 +0,0 @@
|
||||||
// queue is used to play videos
|
|
||||||
var queue
|
|
||||||
// player is a iframe youtube player
|
|
||||||
var player
|
|
||||||
// vid is a current playing video id
|
|
||||||
var vid = ""
|
|
||||||
|
|
||||||
function updateTable() {
|
|
||||||
var myTableDiv = document.getElementById("video_queue")
|
|
||||||
myTableDiv.innerHTML = ""
|
|
||||||
|
|
||||||
var table = document.createElement('TABLE')
|
|
||||||
var tableBody = document.createElement('TBODY')
|
|
||||||
table.border = '1'
|
|
||||||
table.appendChild(tableBody);
|
|
||||||
|
|
||||||
for (i = 0; i < queue.length; i++) {
|
|
||||||
var tr = document.createElement('TR');
|
|
||||||
|
|
||||||
var td = document.createElement('TD');
|
|
||||||
td.appendChild(document.createTextNode(queue[i].Duration));
|
|
||||||
tr.appendChild(td);
|
|
||||||
|
|
||||||
var td = document.createElement('TD');
|
|
||||||
td.appendChild(document.createTextNode(queue[i].Title));
|
|
||||||
tr.appendChild(td)
|
|
||||||
|
|
||||||
var td = document.createElement('TD');
|
|
||||||
td.appendChild(document.createTextNode( queue[i].From));
|
|
||||||
tr.appendChild(td)
|
|
||||||
|
|
||||||
var td = document.createElement('TD');
|
|
||||||
td.appendChild(document.createTextNode( queue[i].Views));
|
|
||||||
tr.appendChild(td)
|
|
||||||
|
|
||||||
var td = document.createElement('TD');
|
|
||||||
td.appendChild(document.createTextNode(((queue[i].Upvotes/(queue[i].Upvotes + queue[i].Downvotes)) * 100).toFixed(2) + '%'));
|
|
||||||
tr.appendChild(td)
|
|
||||||
|
|
||||||
tableBody.appendChild(tr);
|
|
||||||
}
|
|
||||||
|
|
||||||
myTableDiv.appendChild(table)
|
|
||||||
}
|
|
||||||
|
|
||||||
function bgUpdateQueue() {
|
|
||||||
initQueue()
|
|
||||||
updateTable()
|
|
||||||
|
|
||||||
if (vid === "" && queue.length > 0) {
|
|
||||||
vid = queue[0].ID
|
|
||||||
player.loadVideoById(vid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function initQueue() {
|
|
||||||
var request = new XMLHttpRequest()
|
|
||||||
request.open('GET', './queue', false)
|
|
||||||
request.onload = function () {
|
|
||||||
queue = JSON.parse(this.response)
|
|
||||||
}
|
|
||||||
request.send()
|
|
||||||
updateTable()
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateQueue() {
|
|
||||||
var request = new XMLHttpRequest()
|
|
||||||
request.open('POST', './queue', false)
|
|
||||||
request.onload = function () {
|
|
||||||
queue = JSON.parse(this.response)
|
|
||||||
}
|
|
||||||
request.send(vid)
|
|
||||||
updateTable()
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextVideo() {
|
|
||||||
updateQueue()
|
|
||||||
if (queue.length > 0) {
|
|
||||||
vid = queue[0].ID
|
|
||||||
} else {
|
|
||||||
vid = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
player.loadVideoById(vid)
|
|
||||||
updateTable()
|
|
||||||
}
|
|
||||||
|
|
||||||
function onYouTubeIframeAPIReady() {
|
|
||||||
if (queue.length != 0) {
|
|
||||||
vid = queue[0].ID
|
|
||||||
}
|
|
||||||
console.log(vid)
|
|
||||||
player = new YT.Player('player', {
|
|
||||||
height: '390',
|
|
||||||
width: '640',
|
|
||||||
videoId: vid,
|
|
||||||
events: {
|
|
||||||
'onReady': onPlayerReady,
|
|
||||||
'onStateChange': onPlayerStateChange
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPlayerReady(event) {
|
|
||||||
event.target.playVideo();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPlayerStateChange(event) {
|
|
||||||
if (event.data === 0) {
|
|
||||||
nextVideo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initQueue()
|
|
||||||
setInterval(bgUpdateQueue, 4000);
|
|
||||||
var tag = document.createElement('script');
|
|
||||||
tag.src = "https://www.youtube.com/iframe_api";
|
|
||||||
var firstScriptTag = document.getElementsByTagName('script')[0];
|
|
||||||
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
|
|
@ -1,4 +0,0 @@
|
||||||
body {
|
|
||||||
background-color: #000000;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue