From 8a439c81a72bd5794fcabde4bf13d5f09f652adf Mon Sep 17 00:00:00 2001 From: alexvanin Date: Fri, 10 May 2019 16:02:08 +0300 Subject: [PATCH 01/11] Added readme file --- readme.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 readme.md diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..42a196d --- /dev/null +++ b/readme.md @@ -0,0 +1,3 @@ +# Galched Bot + +This bot is made for galched twitch and discord channel \ No newline at end of file From 22d0e095e3a92052fc1c0d68f009d074c7c04b5f Mon Sep 17 00:00:00 2001 From: alexvanin Date: Sat, 1 Jun 2019 00:07:12 +0300 Subject: [PATCH 02/11] Added new subhistory command in subday handler --- changelog.md | 5 +++++ modules/discord/handlers.go | 1 + modules/discord/subdayhandlers.go | 29 ++++++++++++++++++++++++----- modules/settings/settings.go | 2 +- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/changelog.md b/changelog.md index 368fc5d..4d4538d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,10 @@ # Changelog +## 3.0.2 - 2019-05-31 +### Added +- Command !subhistory +- Info about 5th short subday + ## 3.0.1 - 2019-05-10 ### Added - Readme file diff --git a/modules/discord/handlers.go b/modules/discord/handlers.go index 37b2dde..05a241c 100644 --- a/modules/discord/handlers.go +++ b/modules/discord/handlers.go @@ -53,6 +53,7 @@ func (h *HandlerProcessor) Process(s *discordgo.Session, m *discordgo.MessageCre if strings.HasPrefix(m.Content, "!galched") { LogMessage(m) SendMessage(s, m, h.HelpMessage()) + return } for i := range h.handlers { diff --git a/modules/discord/subdayhandlers.go b/modules/discord/subdayhandlers.go index 91329da..d5a239c 100644 --- a/modules/discord/subdayhandlers.go +++ b/modules/discord/subdayhandlers.go @@ -38,10 +38,7 @@ func (h *SubdayListHandler) Handle(s *discordgo.Session, m *discordgo.MessageCre log.Print("discord: cannot obtain guild", err) return } - 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" + + message := "Игры предыдущих сабдеев доступны по команде **!subhistory**\n" + "Список игр для следующего сабдея:\n" for k, v := range h.subday.Database() { nickname := " " @@ -138,10 +135,32 @@ 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~~_" + SendMessage(s, m, message) +} + func SubdayHandlers(s *subday.Subday, r []string) []MessageHandler { var result []MessageHandler addHandler := &SubdayAddHandler{s, r} listHandler := &SubdayListHandler{s} - return append(result, addHandler, listHandler) + histHandler := new(SubdayHistoryHandler) + return append(result, addHandler, listHandler, histHandler) } diff --git a/modules/settings/settings.go b/modules/settings/settings.go index 6a3c9af..a30095b 100644 --- a/modules/settings/settings.go +++ b/modules/settings/settings.go @@ -6,7 +6,7 @@ import ( ) const ( - version = "3.0.1" + version = "3.0.2" discordTokenPath = "./tokens/.discordtoken" subdayDataPath = "./backups/subday" From 5352ef8434406e70d5691d0dcc1d371ce1e72122 Mon Sep 17 00:00:00 2001 From: alexvanin Date: Mon, 3 Jun 2019 23:27:34 +0300 Subject: [PATCH 03/11] Added new subhistory command in subday handler --- changelog.md | 4 ++-- modules/discord/subdayhandlers.go | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 4d4538d..b2730f6 100644 --- a/changelog.md +++ b/changelog.md @@ -1,9 +1,9 @@ # Changelog -## 3.0.2 - 2019-05-31 +## 3.0.2 - 2019-06-03 ### Added - Command !subhistory -- Info about 5th short subday +- Info about 5th and 6th subday ## 3.0.1 - 2019-05-10 ### Added diff --git a/modules/discord/subdayhandlers.go b/modules/discord/subdayhandlers.go index d5a239c..94bb89d 100644 --- a/modules/discord/subdayhandlers.go +++ b/modules/discord/subdayhandlers.go @@ -152,7 +152,8 @@ func (h *SubdayHistoryHandler) Handle(s *discordgo.Session, m *discordgo.Message "**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~~_" + "**24.02.19**: _Tetris 99_ -> _~~Bully~~_ -> _~~GTA: Vice City~~_\n" + + "**02.06.19**: _Spec Ops: The Line_ -> _Escape from Tarkov_\n" SendMessage(s, m, message) } From 601747352af6388d94a1c432dd13597f1259b5e2 Mon Sep 17 00:00:00 2001 From: alexvanin Date: Sun, 23 Jun 2019 16:12:35 +0300 Subject: [PATCH 04/11] Add twitchat module and dup finder handler --- changelog.md | 5 ++++ go.mod | 1 + go.sum | 2 ++ main.go | 20 +++++++++++-- modules/settings/settings.go | 15 +++++++++- modules/twitchat/duphandler.go | 48 ++++++++++++++++++++++++++++++ modules/twitchat/handlers.go | 12 ++++++++ modules/twitchat/twitchat.go | 53 ++++++++++++++++++++++++++++++++++ 8 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 modules/twitchat/duphandler.go create mode 100644 modules/twitchat/handlers.go create mode 100644 modules/twitchat/twitchat.go diff --git a/changelog.md b/changelog.md index b2730f6..28361cf 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,10 @@ # Changelog +## 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 diff --git a/go.mod b/go.mod index 2008330..14d0b5a 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,7 @@ module galched-bot require ( github.com/bwmarrin/discordgo v0.19.0 + github.com/gempir/go-twitch-irc v1.1.0 github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.3.0 // indirect go.uber.org/atomic v1.3.2 // indirect diff --git a/go.sum b/go.sum index 31b3e85..28ae0a3 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/bwmarrin/discordgo v0.19.0 h1:kMED/DB0NR1QhRcalb85w0Cu3Ep2OrGAqZH1R5a github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q= 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/gempir/go-twitch-irc v1.1.0 h1:Q9gQGI/3yJzYwlYDlFsGJzWfpaqubMExfmBXNpOC6W0= +github.com/gempir/go-twitch-irc v1.1.0/go.mod h1:Pc661rsUSmkQXvI9W2bNyLt4ZrMAgHZPnVwMQEJ0fdo= 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/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= diff --git a/main.go b/main.go index 5b7b8e0..2107535 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "galched-bot/modules/grace" "galched-bot/modules/settings" "galched-bot/modules/subday" + "galched-bot/modules/twitchat" "go.uber.org/fx" ) @@ -21,6 +22,7 @@ type ( Context context.Context Discord *discord.Discord Settings *settings.Settings + Chat *twitchat.TwitchIRC } ) @@ -35,14 +37,26 @@ func start(p appParam) error { if err != nil { log.Print("discord: cannot start instance", err) return err - } log.Printf("main: discord instance running") - log.Printf("main: — — —") + err = p.Chat.Start() + if err != nil { + log.Print("chat: cannot start instance", err) + return err + } + log.Printf("main: twitch chat instance running") + + log.Printf("main: — — —") <-p.Context.Done() log.Print("main: stopping galched-bot") + err = p.Chat.Stop() + if err != nil { + log.Print("chat: cannot stop instance", err) + return err + } + err = p.Discord.Stop() if err != nil { log.Print("discord: cannot stop instance", err) @@ -57,7 +71,7 @@ func main() { var err error app := fx.New( 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), fx.Invoke(start)) err = app.Start(context.Background()) diff --git a/modules/settings/settings.go b/modules/settings/settings.go index a30095b..0cf6c80 100644 --- a/modules/settings/settings.go +++ b/modules/settings/settings.go @@ -6,8 +6,11 @@ import ( ) const ( - version = "3.0.2" + version = "4.0.0" + twitchUser = "galchedbot" + twitchIRCRoom = "galched" discordTokenPath = "./tokens/.discordtoken" + twitchTokenPath = "./tokens/.twitchtoken" subdayDataPath = "./backups/subday" // Permitted roles in discord for subday @@ -21,6 +24,9 @@ type ( Settings struct { Version string DiscordToken string + TwitchUser string + TwitchIRCRoom string + TwitchToken string SubdayDataPath string PermittedRoles []string } @@ -31,10 +37,17 @@ func New() (*Settings, error) { if err != nil { 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) + } return &Settings{ Version: version, DiscordToken: string(discordToken), + TwitchToken: string(twitchToken), + TwitchUser: twitchUser, + TwitchIRCRoom: twitchIRCRoom, SubdayDataPath: subdayDataPath, PermittedRoles: []string{subRole1, subRole2, galchedRole, smorcRole}, }, nil diff --git a/modules/twitchat/duphandler.go b/modules/twitchat/duphandler.go new file mode 100644 index 0000000..01b0b74 --- /dev/null +++ b/modules/twitchat/duphandler.go @@ -0,0 +1,48 @@ +package twitchat + +import ( + "fmt" + "log" + "strings" + + "github.com/gempir/go-twitch-irc" +) + +type ( + dupHandler struct { + lastMessage string + counter int + dupMinimal int + } +) + +const DupMinimal = 3 + +func DupHandler() MessageHandler { + return &dupHandler{ + lastMessage: "", + counter: 0, + dupMinimal: DupMinimal, + } +} + +func (h *dupHandler) IsValid(m string) bool { + return true +} + +func (h *dupHandler) Handle(ch string, u *twitch.User, m *twitch.Message, client *twitch.Client) { + data := strings.Fields(m.Text) + 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) + client.Say(ch, msg) + log.Print("chat: ", msg) + } + h.counter = 1 + h.lastMessage = data[i] + } + } +} diff --git a/modules/twitchat/handlers.go b/modules/twitchat/handlers.go new file mode 100644 index 0000000..a2fdc50 --- /dev/null +++ b/modules/twitchat/handlers.go @@ -0,0 +1,12 @@ +package twitchat + +import ( + "github.com/gempir/go-twitch-irc" +) + +type ( + MessageHandler interface { + IsValid(string) bool + Handle(ch string, u *twitch.User, m *twitch.Message, client *twitch.Client) + } +) diff --git a/modules/twitchat/twitchat.go b/modules/twitchat/twitchat.go new file mode 100644 index 0000000..d70c376 --- /dev/null +++ b/modules/twitchat/twitchat.go @@ -0,0 +1,53 @@ +package twitchat + +import ( + "galched-bot/modules/settings" + + "github.com/gempir/go-twitch-irc" + _ "github.com/gempir/go-twitch-irc" +) + +type ( + TwitchIRC struct { + username string + chat *twitch.Client + handlers []MessageHandler + } +) + +func New(s *settings.Settings) (*TwitchIRC, error) { + var irc = new(TwitchIRC) + + irc.username = s.TwitchUser + + irc.handlers = append(irc.handlers, DupHandler()) + + irc.chat = twitch.NewClient(s.TwitchUser, s.TwitchToken) + irc.chat.OnNewMessage(irc.MessageHandler) + 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) MessageHandler(ch string, u twitch.User, m twitch.Message) { + if u.Username == c.username { + return + } + for i := range c.handlers { + if c.handlers[i].IsValid(m.Text) { + c.handlers[i].Handle(ch, &u, &m, c.chat) + } + } +} From 17db60dea13a6bd25f8279b3efa8fefcd1904584 Mon Sep 17 00:00:00 2001 From: alexvanin Date: Sat, 20 Jul 2019 22:14:07 +0300 Subject: [PATCH 05/11] Update go-twitch-irc library revision from v1 to v2 --- changelog.md | 4 ++++ go.mod | 2 +- go.sum | 2 ++ modules/settings/settings.go | 2 +- modules/twitchat/duphandler.go | 10 +++++----- modules/twitchat/handlers.go | 10 +++++++--- modules/twitchat/twitchat.go | 15 +++++++-------- 7 files changed, 27 insertions(+), 18 deletions(-) diff --git a/changelog.md b/changelog.md index 28361cf..1da0e85 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,9 @@ # Changelog +## 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 diff --git a/go.mod b/go.mod index 14d0b5a..ca01272 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module galched-bot require ( github.com/bwmarrin/discordgo v0.19.0 - github.com/gempir/go-twitch-irc v1.1.0 + github.com/gempir/go-twitch-irc/v2 v2.2.0 github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.3.0 // indirect go.uber.org/atomic v1.3.2 // indirect diff --git a/go.sum b/go.sum index 28ae0a3..bcabf38 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ 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/gempir/go-twitch-irc v1.1.0 h1:Q9gQGI/3yJzYwlYDlFsGJzWfpaqubMExfmBXNpOC6W0= github.com/gempir/go-twitch-irc v1.1.0/go.mod h1:Pc661rsUSmkQXvI9W2bNyLt4ZrMAgHZPnVwMQEJ0fdo= +github.com/gempir/go-twitch-irc/v2 v2.2.0 h1:9iYRr/PkT5tqnD9J0awBXtwS4R4DatA5cMQbsua6OvM= +github.com/gempir/go-twitch-irc/v2 v2.2.0/go.mod h1:0HXoEr9l7gNjwajosptV0w0xGpHeU6gsD7JDlfvjTYI= 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/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= diff --git a/modules/settings/settings.go b/modules/settings/settings.go index 0cf6c80..922afc8 100644 --- a/modules/settings/settings.go +++ b/modules/settings/settings.go @@ -6,7 +6,7 @@ import ( ) const ( - version = "4.0.0" + version = "4.0.1" twitchUser = "galchedbot" twitchIRCRoom = "galched" discordTokenPath = "./tokens/.discordtoken" diff --git a/modules/twitchat/duphandler.go b/modules/twitchat/duphandler.go index 01b0b74..b9a1433 100644 --- a/modules/twitchat/duphandler.go +++ b/modules/twitchat/duphandler.go @@ -5,7 +5,7 @@ import ( "log" "strings" - "github.com/gempir/go-twitch-irc" + "github.com/gempir/go-twitch-irc/v2" ) type ( @@ -18,7 +18,7 @@ type ( const DupMinimal = 3 -func DupHandler() MessageHandler { +func DupHandler() PrivateMessageHandler { return &dupHandler{ lastMessage: "", counter: 0, @@ -30,15 +30,15 @@ func (h *dupHandler) IsValid(m string) bool { return true } -func (h *dupHandler) Handle(ch string, u *twitch.User, m *twitch.Message, client *twitch.Client) { - data := strings.Fields(m.Text) +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) - client.Say(ch, msg) + r.Say(m.Channel, msg) log.Print("chat: ", msg) } h.counter = 1 diff --git a/modules/twitchat/handlers.go b/modules/twitchat/handlers.go index a2fdc50..d6c6134 100644 --- a/modules/twitchat/handlers.go +++ b/modules/twitchat/handlers.go @@ -1,12 +1,16 @@ package twitchat import ( - "github.com/gempir/go-twitch-irc" + "github.com/gempir/go-twitch-irc/v2" ) type ( - MessageHandler interface { + Responser interface { + Say(channel, message string) + } + + PrivateMessageHandler interface { IsValid(string) bool - Handle(ch string, u *twitch.User, m *twitch.Message, client *twitch.Client) + Handle(m *twitch.PrivateMessage, r Responser) } ) diff --git a/modules/twitchat/twitchat.go b/modules/twitchat/twitchat.go index d70c376..ef70f7c 100644 --- a/modules/twitchat/twitchat.go +++ b/modules/twitchat/twitchat.go @@ -3,15 +3,14 @@ package twitchat import ( "galched-bot/modules/settings" - "github.com/gempir/go-twitch-irc" - _ "github.com/gempir/go-twitch-irc" + "github.com/gempir/go-twitch-irc/v2" ) type ( TwitchIRC struct { username string chat *twitch.Client - handlers []MessageHandler + handlers []PrivateMessageHandler } ) @@ -23,7 +22,7 @@ func New(s *settings.Settings) (*TwitchIRC, error) { irc.handlers = append(irc.handlers, DupHandler()) irc.chat = twitch.NewClient(s.TwitchUser, s.TwitchToken) - irc.chat.OnNewMessage(irc.MessageHandler) + irc.chat.OnPrivateMessage(irc.PrivateMessageHandler) irc.chat.Join(s.TwitchIRCRoom) return irc, nil @@ -41,13 +40,13 @@ func (c *TwitchIRC) Stop() error { return c.chat.Disconnect() } -func (c *TwitchIRC) MessageHandler(ch string, u twitch.User, m twitch.Message) { - if u.Username == c.username { +func (c *TwitchIRC) PrivateMessageHandler(msg twitch.PrivateMessage) { + if msg.User.Name == c.username { return } for i := range c.handlers { - if c.handlers[i].IsValid(m.Text) { - c.handlers[i].Handle(ch, &u, &m, c.chat) + if c.handlers[i].IsValid(msg.Message) { + c.handlers[i].Handle(&msg, c.chat) } } } From 8b5673434a08e9c4ae810810847836c16e3321f0 Mon Sep 17 00:00:00 2001 From: alexvanin Date: Sun, 28 Jul 2019 23:10:24 +0300 Subject: [PATCH 06/11] Add song player handler --- changelog.md | 8 +++ go.mod | 2 +- modules/discord/discord.go | 57 +++++++++++++++++++ modules/discord/polkahandler.go | 92 +++++++++++++++++++++++++++++++ modules/discord/subdayhandlers.go | 3 +- modules/settings/settings.go | 34 +++++++----- songs/.gitkeep | 0 7 files changed, 179 insertions(+), 17 deletions(-) create mode 100644 modules/discord/polkahandler.go create mode 100644 songs/.gitkeep diff --git a/changelog.md b/changelog.md index 1da0e85..756a7c5 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 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 diff --git a/go.mod b/go.mod index ca01272..8372b61 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ require ( github.com/gempir/go-twitch-irc/v2 v2.2.0 github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.3.0 // indirect - go.uber.org/atomic v1.3.2 // indirect + go.uber.org/atomic v1.3.2 go.uber.org/dig v1.7.0 // indirect go.uber.org/fx v1.9.0 go.uber.org/goleak v0.10.0 // indirect diff --git a/modules/discord/discord.go b/modules/discord/discord.go index 37a40e0..11e47c6 100644 --- a/modules/discord/discord.go +++ b/modules/discord/discord.go @@ -1,12 +1,17 @@ package discord import ( + "encoding/binary" "fmt" + "io" "log" + "os" "galched-bot/modules/settings" "galched-bot/modules/subday" + "go.uber.org/atomic" + "github.com/bwmarrin/discordgo" "github.com/pkg/errors" ) @@ -31,6 +36,16 @@ func New(s *settings.Settings, subday *subday.Subday) (*Discord, error) { processor.AddHandler(subdayHandler) } + polka, err := loadSong(s.PolkaPath) + if err != nil { + return nil, errors.Wrap(err, "cannot read polka song") + } + processor.AddHandler(&polkaHandler{ + polka: polka, + voiceChannel: s.DiscordVoiceChannel, + lock: atomic.NewBool(false), + }) + log.Printf("discord: added %d message handlers", len(processor.handlers)) if len(processor.handlers) > 0 { for i := range processor.handlers { @@ -45,6 +60,48 @@ func New(s *settings.Settings, subday *subday.Subday) (*Discord, error) { }, nil } +func loadSong(path string) ([][]byte, error) { + file, err := os.Open(path) + if err != nil { + return nil, errors.Wrap(err, "error opening dca file") + } + + var ( + opuslen int16 + buffer = make([][]byte, 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) + } +} + func LogMessage(m *discordgo.MessageCreate) { log.Printf("discord: msg [%s]: %s", m.Author.Username, m.Content) } diff --git a/modules/discord/polkahandler.go b/modules/discord/polkahandler.go new file mode 100644 index 0000000..5f3dc3d --- /dev/null +++ b/modules/discord/polkahandler.go @@ -0,0 +1,92 @@ +package discord + +import ( + "log" + "time" + + "github.com/bwmarrin/discordgo" + "go.uber.org/atomic" +) + +type polkaHandler struct { + polka [][]byte + voiceChannel string + lock *atomic.Bool +} + +func (h *polkaHandler) Signature() string { + return "!song" +} + +func (h *polkaHandler) Description() string { + return "сыграть гимн галчед (только для избранных)" +} + +func (h *polkaHandler) IsValid(msg string) bool { + return msg == "!song" +} + +func (h *polkaHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { + if m.Author.Username != "YoMedved" && m.Author.Username != "Rummy_Quamox" && m.Author.Username != "Lidiya_owl" { + log.Printf("discord: unathorized polka message from %s", 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.lock.CAS(false, true) { + defer h.lock.Store(false) + err = h.playSound(s, g.ID, h.voiceChannel) + if err != nil { + log.Println("discord: error playing sound:", err) + } + time.Sleep(10 * time.Second) + return + } +} + +// playSound plays the current buffer to the provided channel. +func (h *polkaHandler) playSound(s *discordgo.Session, guildID, channelID string) (err 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 h.polka { + 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 +} diff --git a/modules/discord/subdayhandlers.go b/modules/discord/subdayhandlers.go index 94bb89d..477cece 100644 --- a/modules/discord/subdayhandlers.go +++ b/modules/discord/subdayhandlers.go @@ -153,7 +153,8 @@ func (h *SubdayHistoryHandler) Handle(s *discordgo.Session, m *discordgo.Message "**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" + "**02.06.19**: _Spec Ops: The Line_ -> _Escape from Tarkov_\n" + + "**28.07.19**: _Crypt of the Necrodancer_ -> _My Friend Pedro_ -> _Ape Out_\n" SendMessage(s, m, message) } diff --git a/modules/settings/settings.go b/modules/settings/settings.go index 922afc8..b6bfa6c 100644 --- a/modules/settings/settings.go +++ b/modules/settings/settings.go @@ -6,7 +6,7 @@ import ( ) const ( - version = "4.0.1" + version = "4.1.0" twitchUser = "galchedbot" twitchIRCRoom = "galched" discordTokenPath = "./tokens/.discordtoken" @@ -22,13 +22,15 @@ const ( type ( Settings struct { - Version string - DiscordToken string - TwitchUser string - TwitchIRCRoom string - TwitchToken string - SubdayDataPath string - PermittedRoles []string + Version string + DiscordToken string + TwitchUser string + TwitchIRCRoom string + TwitchToken string + SubdayDataPath string + PermittedRoles []string + PolkaPath string + DiscordVoiceChannel string } ) @@ -43,12 +45,14 @@ func New() (*Settings, error) { } return &Settings{ - Version: version, - DiscordToken: string(discordToken), - TwitchToken: string(twitchToken), - TwitchUser: twitchUser, - TwitchIRCRoom: twitchIRCRoom, - SubdayDataPath: subdayDataPath, - PermittedRoles: []string{subRole1, subRole2, galchedRole, smorcRole}, + Version: version, + DiscordToken: string(discordToken), + TwitchToken: string(twitchToken), + TwitchUser: twitchUser, + TwitchIRCRoom: twitchIRCRoom, + SubdayDataPath: subdayDataPath, + PolkaPath: "songs/polka.dca", + DiscordVoiceChannel: "301793085522706432", + PermittedRoles: []string{subRole1, subRole2, galchedRole, smorcRole}, }, nil } diff --git a/songs/.gitkeep b/songs/.gitkeep new file mode 100644 index 0000000..e69de29 From 65fc1ccad4cc6efa348e1217ffe805d13cd739d6 Mon Sep 17 00:00:00 2001 From: alexvanin Date: Sun, 20 Oct 2019 18:22:52 +0300 Subject: [PATCH 07/11] Add universal music handler and update discordgo version --- changelog.md | 7 ++ go.mod | 7 +- go.sum | 18 ++- modules/discord/discord.go | 57 +--------- modules/discord/polkahandler.go | 92 --------------- modules/discord/songhandlers.go | 191 ++++++++++++++++++++++++++++++++ modules/settings/settings.go | 29 ++++- 7 files changed, 246 insertions(+), 155 deletions(-) delete mode 100644 modules/discord/polkahandler.go create mode 100644 modules/discord/songhandlers.go diff --git a/changelog.md b/changelog.md index 756a7c5..a9a9260 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 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 diff --git a/go.mod b/go.mod index 8372b61..7449d4d 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,9 @@ module galched-bot require ( - github.com/bwmarrin/discordgo v0.19.0 + github.com/bwmarrin/discordgo v0.20.1 github.com/gempir/go-twitch-irc/v2 v2.2.0 + github.com/gorilla/websocket v1.4.1 // indirect github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.3.0 // indirect go.uber.org/atomic v1.3.2 @@ -10,4 +11,8 @@ require ( go.uber.org/fx v1.9.0 go.uber.org/goleak v0.10.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 ) + +go 1.13 diff --git a/go.sum b/go.sum index bcabf38..5faca35 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ -github.com/bwmarrin/discordgo v0.19.0 h1:kMED/DB0NR1QhRcalb85w0Cu3Ep2OrGAqZH1R5awQiY= -github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q= +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/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/gempir/go-twitch-irc v1.1.0 h1:Q9gQGI/3yJzYwlYDlFsGJzWfpaqubMExfmBXNpOC6W0= -github.com/gempir/go-twitch-irc v1.1.0/go.mod h1:Pc661rsUSmkQXvI9W2bNyLt4ZrMAgHZPnVwMQEJ0fdo= github.com/gempir/go-twitch-irc/v2 v2.2.0 h1:9iYRr/PkT5tqnD9J0awBXtwS4R4DatA5cMQbsua6OvM= github.com/gempir/go-twitch-irc/v2 v2.2.0/go.mod h1:0HXoEr9l7gNjwajosptV0w0xGpHeU6gsD7JDlfvjTYI= 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.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -27,3 +27,13 @@ go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 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/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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +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-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= diff --git a/modules/discord/discord.go b/modules/discord/discord.go index 11e47c6..62c266e 100644 --- a/modules/discord/discord.go +++ b/modules/discord/discord.go @@ -1,17 +1,12 @@ package discord import ( - "encoding/binary" "fmt" - "io" "log" - "os" "galched-bot/modules/settings" "galched-bot/modules/subday" - "go.uber.org/atomic" - "github.com/bwmarrin/discordgo" "github.com/pkg/errors" ) @@ -36,15 +31,9 @@ func New(s *settings.Settings, subday *subday.Subday) (*Discord, error) { processor.AddHandler(subdayHandler) } - polka, err := loadSong(s.PolkaPath) - if err != nil { - return nil, errors.Wrap(err, "cannot read polka song") + for _, songHandler := range SongHandlers(s) { + processor.AddHandler(songHandler) } - processor.AddHandler(&polkaHandler{ - polka: polka, - voiceChannel: s.DiscordVoiceChannel, - lock: atomic.NewBool(false), - }) log.Printf("discord: added %d message handlers", len(processor.handlers)) if len(processor.handlers) > 0 { @@ -60,48 +49,6 @@ func New(s *settings.Settings, subday *subday.Subday) (*Discord, error) { }, nil } -func loadSong(path string) ([][]byte, error) { - file, err := os.Open(path) - if err != nil { - return nil, errors.Wrap(err, "error opening dca file") - } - - var ( - opuslen int16 - buffer = make([][]byte, 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) - } -} - func LogMessage(m *discordgo.MessageCreate) { log.Printf("discord: msg [%s]: %s", m.Author.Username, m.Content) } diff --git a/modules/discord/polkahandler.go b/modules/discord/polkahandler.go deleted file mode 100644 index 5f3dc3d..0000000 --- a/modules/discord/polkahandler.go +++ /dev/null @@ -1,92 +0,0 @@ -package discord - -import ( - "log" - "time" - - "github.com/bwmarrin/discordgo" - "go.uber.org/atomic" -) - -type polkaHandler struct { - polka [][]byte - voiceChannel string - lock *atomic.Bool -} - -func (h *polkaHandler) Signature() string { - return "!song" -} - -func (h *polkaHandler) Description() string { - return "сыграть гимн галчед (только для избранных)" -} - -func (h *polkaHandler) IsValid(msg string) bool { - return msg == "!song" -} - -func (h *polkaHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { - if m.Author.Username != "YoMedved" && m.Author.Username != "Rummy_Quamox" && m.Author.Username != "Lidiya_owl" { - log.Printf("discord: unathorized polka message from %s", 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.lock.CAS(false, true) { - defer h.lock.Store(false) - err = h.playSound(s, g.ID, h.voiceChannel) - if err != nil { - log.Println("discord: error playing sound:", err) - } - time.Sleep(10 * time.Second) - return - } -} - -// playSound plays the current buffer to the provided channel. -func (h *polkaHandler) playSound(s *discordgo.Session, guildID, channelID string) (err 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 h.polka { - 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 -} diff --git a/modules/discord/songhandlers.go b/modules/discord/songhandlers.go new file mode 100644 index 0000000..cc76886 --- /dev/null +++ b/modules/discord/songhandlers.go @@ -0,0 +1,191 @@ +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) + } +} diff --git a/modules/settings/settings.go b/modules/settings/settings.go index b6bfa6c..f174a2d 100644 --- a/modules/settings/settings.go +++ b/modules/settings/settings.go @@ -3,10 +3,11 @@ package settings import ( "io/ioutil" "log" + "time" ) const ( - version = "4.1.0" + version = "4.2.0" twitchUser = "galchedbot" twitchIRCRoom = "galched" discordTokenPath = "./tokens/.discordtoken" @@ -21,6 +22,14 @@ const ( ) type ( + SongInfo struct { + Path string + Signature string + Description string + Permissions []string + Timeout time.Duration + } + Settings struct { Version string DiscordToken string @@ -29,8 +38,8 @@ type ( TwitchToken string SubdayDataPath string PermittedRoles []string - PolkaPath string DiscordVoiceChannel string + Songs []SongInfo } ) @@ -51,8 +60,22 @@ func New() (*Settings, error) { TwitchUser: twitchUser, TwitchIRCRoom: twitchIRCRoom, SubdayDataPath: subdayDataPath, - PolkaPath: "songs/polka.dca", 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, + }, + }, }, nil } From f0f31a8415220eb568a9f276ba3d5f75f49bf179 Mon Sep 17 00:00:00 2001 From: alexvanin Date: Sat, 11 Jan 2020 22:38:51 +0300 Subject: [PATCH 08/11] Add twitch point song requests to the bot This commits adds new feature for galchedbot: video requests in the twitch chat via highlighted chat messages. This messages parsed by the bot and added to the video queue, that can be accessed by the dedicated web server. Video queue requires authorization based on random token added to the cookies. --- changelog.md | 13 ++ go.mod | 3 +- go.sum | 81 ++++++++++++- main.go | 22 +++- modules/discord/subdayhandlers.go | 4 +- modules/settings/settings.go | 37 +++++- modules/twitchat/duphandler.go | 2 +- modules/twitchat/handlers.go | 2 +- modules/twitchat/logcheck.go | 23 ++++ modules/twitchat/songrequest.go | 61 ++++++++++ modules/twitchat/twitchat.go | 7 +- modules/web/server.go | 159 ++++++++++++++++++++++++ modules/youtube/requester.go | 195 ++++++++++++++++++++++++++++++ modules/youtube/requester_test.go | 67 ++++++++++ web/index.html | 12 ++ web/login.html | 14 +++ web/scripts.js | 120 ++++++++++++++++++ web/style.css | 4 + 18 files changed, 813 insertions(+), 13 deletions(-) create mode 100644 modules/twitchat/logcheck.go create mode 100644 modules/twitchat/songrequest.go create mode 100644 modules/web/server.go create mode 100644 modules/youtube/requester.go create mode 100644 modules/youtube/requester_test.go create mode 100644 web/index.html create mode 100644 web/login.html create mode 100644 web/scripts.js create mode 100644 web/style.css diff --git a/changelog.md b/changelog.md index a9a9260..b781298 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,18 @@ # Changelog +## 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 diff --git a/go.mod b/go.mod index 7449d4d..7482188 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module galched-bot require ( github.com/bwmarrin/discordgo v0.20.1 - github.com/gempir/go-twitch-irc/v2 v2.2.0 + github.com/gempir/go-twitch-irc/v2 v2.2.1 github.com/gorilla/websocket v1.4.1 // indirect github.com/pkg/errors v0.8.1 github.com/stretchr/testify v1.3.0 // indirect @@ -13,6 +13,7 @@ require ( 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 diff --git a/go.sum b/go.sum index 5faca35..a906d49 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,37 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gempir/go-twitch-irc/v2 v2.2.0 h1:9iYRr/PkT5tqnD9J0awBXtwS4R4DatA5cMQbsua6OvM= -github.com/gempir/go-twitch-irc/v2 v2.2.0/go.mod h1:0HXoEr9l7gNjwajosptV0w0xGpHeU6gsD7JDlfvjTYI= +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/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/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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -15,6 +39,8 @@ 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/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 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/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/dig v1.7.0 h1:E5/L92iQTNJTjfgJF2KgU+/JpMaiuvK2DHLBj0+kSZk= @@ -30,10 +56,61 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf 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= diff --git a/main.go b/main.go index 2107535..df7009d 100644 --- a/main.go +++ b/main.go @@ -4,13 +4,15 @@ import ( "context" "log" + "go.uber.org/fx" + "galched-bot/modules/discord" "galched-bot/modules/grace" "galched-bot/modules/settings" "galched-bot/modules/subday" "galched-bot/modules/twitchat" - - "go.uber.org/fx" + "galched-bot/modules/web" + "galched-bot/modules/youtube" ) type ( @@ -23,6 +25,7 @@ type ( Discord *discord.Discord Settings *settings.Settings Chat *twitchat.TwitchIRC + Server *web.WebServer } ) @@ -47,10 +50,23 @@ func start(p appParam) error { } 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: — — —") <-p.Context.Done() 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) @@ -71,7 +87,7 @@ func main() { var err error app := fx.New( fx.Logger(new(silentPrinter)), - fx.Provide(settings.New, grace.New, discord.New, subday.New, twitchat.New), + fx.Provide(settings.New, grace.New, discord.New, subday.New, twitchat.New, web.New, youtube.New), fx.Invoke(start)) err = app.Start(context.Background()) diff --git a/modules/discord/subdayhandlers.go b/modules/discord/subdayhandlers.go index 477cece..70c5821 100644 --- a/modules/discord/subdayhandlers.go +++ b/modules/discord/subdayhandlers.go @@ -55,6 +55,7 @@ func (h *SubdayListHandler) Handle(s *discordgo.Session, m *discordgo.MessageCre message += fmt.Sprintf(" **- %s** от _%s_\n", game, nickname) } } + message += "\nВсе команды бота: !galched\n" SendMessage(s, m, strings.Trim(message, "\n")) } @@ -154,7 +155,8 @@ func (h *SubdayHistoryHandler) Handle(s *discordgo.Session, m *discordgo.Message "**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" + "**28.07.19**: _Crypt of the Necrodancer_ -> _My Friend Pedro_ -> _Ape Out_\n" + + "\nВсе команды бота: !galched\n" SendMessage(s, m, message) } diff --git a/modules/settings/settings.go b/modules/settings/settings.go index f174a2d..b38af64 100644 --- a/modules/settings/settings.go +++ b/modules/settings/settings.go @@ -1,24 +1,29 @@ package settings import ( + "encoding/json" "io/ioutil" "log" "time" ) const ( - version = "4.2.0" + version = "5.0.0" twitchUser = "galchedbot" twitchIRCRoom = "galched" discordTokenPath = "./tokens/.discordtoken" twitchTokenPath = "./tokens/.twitchtoken" subdayDataPath = "./backups/subday" + youtubeTokenPath = "./tokens/.youtubetoken" + webLoginsPath = "./tokens/.weblogins" // Permitted roles in discord for subday subRole1 = "433672344737677322" subRole2 = "433680494635515904" galchedRole = "301467455497175041" smorcRole = "301470784491356172" + + defaultQueueAddr = ":8888" ) type ( @@ -36,10 +41,14 @@ type ( TwitchUser string TwitchIRCRoom string TwitchToken string + YoutubeToken string SubdayDataPath string PermittedRoles []string DiscordVoiceChannel string Songs []SongInfo + + QueueAddress string + LoginUsers map[string]string } ) @@ -52,11 +61,27 @@ func New() (*Settings, error) { 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{ Version: version, DiscordToken: string(discordToken), TwitchToken: string(twitchToken), + YoutubeToken: string(youtubetoken), TwitchUser: twitchUser, TwitchIRCRoom: twitchIRCRoom, SubdayDataPath: subdayDataPath, @@ -66,7 +91,7 @@ func New() (*Settings, error) { { Path: "songs/polka.dca", Signature: "!song", - Description: "сыграть гимн галчед (только для избранных", + Description: "сыграть гимн галчед (только для избранных)", Permissions: []string{"AlexV", "Rummy_Quamox", "Lidiya_owl"}, Timeout: 10 * time.Second, }, @@ -76,6 +101,14 @@ func New() (*Settings, error) { 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 } diff --git a/modules/twitchat/duphandler.go b/modules/twitchat/duphandler.go index b9a1433..699964a 100644 --- a/modules/twitchat/duphandler.go +++ b/modules/twitchat/duphandler.go @@ -26,7 +26,7 @@ func DupHandler() PrivateMessageHandler { } } -func (h *dupHandler) IsValid(m string) bool { +func (h *dupHandler) IsValid(m *twitch.PrivateMessage) bool { return true } diff --git a/modules/twitchat/handlers.go b/modules/twitchat/handlers.go index d6c6134..4e3d327 100644 --- a/modules/twitchat/handlers.go +++ b/modules/twitchat/handlers.go @@ -10,7 +10,7 @@ type ( } PrivateMessageHandler interface { - IsValid(string) bool + IsValid(m *twitch.PrivateMessage) bool Handle(m *twitch.PrivateMessage, r Responser) } ) diff --git a/modules/twitchat/logcheck.go b/modules/twitchat/logcheck.go new file mode 100644 index 0000000..98ef08a --- /dev/null +++ b/modules/twitchat/logcheck.go @@ -0,0 +1,23 @@ +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) +} diff --git a/modules/twitchat/songrequest.go b/modules/twitchat/songrequest.go new file mode 100644 index 0000000..f9c5612 --- /dev/null +++ b/modules/twitchat/songrequest.go @@ -0,0 +1,61 @@ +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 +} diff --git a/modules/twitchat/twitchat.go b/modules/twitchat/twitchat.go index ef70f7c..448b886 100644 --- a/modules/twitchat/twitchat.go +++ b/modules/twitchat/twitchat.go @@ -2,6 +2,7 @@ package twitchat import ( "galched-bot/modules/settings" + "galched-bot/modules/youtube" "github.com/gempir/go-twitch-irc/v2" ) @@ -14,12 +15,14 @@ type ( } ) -func New(s *settings.Settings) (*TwitchIRC, error) { +func New(s *settings.Settings, r *youtube.Requester) (*TwitchIRC, error) { var irc = new(TwitchIRC) irc.username = s.TwitchUser irc.handlers = append(irc.handlers, DupHandler()) + irc.handlers = append(irc.handlers, SongRequest(r)) + // irc.handlers = append(irc.handlers, LogCheck()) irc.chat = twitch.NewClient(s.TwitchUser, s.TwitchToken) irc.chat.OnPrivateMessage(irc.PrivateMessageHandler) @@ -45,7 +48,7 @@ func (c *TwitchIRC) PrivateMessageHandler(msg twitch.PrivateMessage) { return } for i := range c.handlers { - if c.handlers[i].IsValid(msg.Message) { + if c.handlers[i].IsValid(&msg) { c.handlers[i].Handle(&msg, c.chat) } } diff --git a/modules/web/server.go b/modules/web/server.go new file mode 100644 index 0000000..b7b3551 --- /dev/null +++ b/modules/web/server.go @@ -0,0 +1,159 @@ +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, + }) +} diff --git a/modules/youtube/requester.go b/modules/youtube/requester.go new file mode 100644 index 0000000..e50ed93 --- /dev/null +++ b/modules/youtube/requester.go @@ -0,0 +1,195 @@ +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\d+Y)?(?P\d+M)?(?P\d+D)?T?(?P\d+H)?(?P\d+M)?(?P\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) +} diff --git a/modules/youtube/requester_test.go b/modules/youtube/requester_test.go new file mode 100644 index 0000000..6e786d5 --- /dev/null +++ b/modules/youtube/requester_test.go @@ -0,0 +1,67 @@ +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: , 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: , got: ") + } + } +} diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..f73dd9a --- /dev/null +++ b/web/index.html @@ -0,0 +1,12 @@ + + + + + + +
+
+
+ + + \ No newline at end of file diff --git a/web/login.html b/web/login.html new file mode 100644 index 0000000..c406fd2 --- /dev/null +++ b/web/login.html @@ -0,0 +1,14 @@ + + + +
+
+

+ +

+ + +
+
+ + diff --git a/web/scripts.js b/web/scripts.js new file mode 100644 index 0000000..17481a0 --- /dev/null +++ b/web/scripts.js @@ -0,0 +1,120 @@ +// 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); diff --git a/web/style.css b/web/style.css new file mode 100644 index 0000000..c771414 --- /dev/null +++ b/web/style.css @@ -0,0 +1,4 @@ +body { + background-color: #000000; + color: #ffffff; +} \ No newline at end of file From c09e3a59882b29df6c8f7eedf2f088362718aa1a Mon Sep 17 00:00:00 2001 From: alexvanin Date: Sat, 11 Jan 2020 22:41:35 +0300 Subject: [PATCH 09/11] Add `.weblogins` to the list of git ignored files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 00e4b74..02ed68c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ tokens/*token +tokens/*logins bin/* !./bin/.gitkeep !./bin/* From d59b1c2d3b13549f191ab2e8bcf188559a606a4a Mon Sep 17 00:00:00 2001 From: alexvanin Date: Sun, 8 Mar 2020 21:03:29 +0300 Subject: [PATCH 10/11] Update go-twitch-irc to v2.2.2 --- changelog.md | 5 +++++ go.mod | 2 +- go.sum | 2 ++ modules/settings/settings.go | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index b781298..ed8761b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,10 @@ # Changelog +## 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 diff --git a/go.mod b/go.mod index 7482188..154df17 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module galched-bot require ( github.com/bwmarrin/discordgo v0.20.1 - github.com/gempir/go-twitch-irc/v2 v2.2.1 + 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/stretchr/testify v1.3.0 // indirect diff --git a/go.sum b/go.sum index a906d49..bf28298 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ 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/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= diff --git a/modules/settings/settings.go b/modules/settings/settings.go index b38af64..2f4e283 100644 --- a/modules/settings/settings.go +++ b/modules/settings/settings.go @@ -8,7 +8,7 @@ import ( ) const ( - version = "5.0.0" + version = "5.0.1" twitchUser = "galchedbot" twitchIRCRoom = "galched" discordTokenPath = "./tokens/.discordtoken" From 4d7d14e0b50f592e943ceaa5829caeac0bd548f5 Mon Sep 17 00:00:00 2001 From: alexvanin Date: Wed, 18 Mar 2020 22:33:33 +0300 Subject: [PATCH 11/11] Add two new twitch chat handlers in v5.1.0 --- changelog.md | 5 + main.go | 4 +- modules/patpet/patpet.go | 88 +++++++++++ modules/settings/settings.go | 5 +- modules/twitchat/dailyemote.go | 257 +++++++++++++++++++++++++++++++++ modules/twitchat/petcat.go | 39 +++++ modules/twitchat/twitchat.go | 5 +- 7 files changed, 400 insertions(+), 3 deletions(-) create mode 100644 modules/patpet/patpet.go create mode 100644 modules/twitchat/dailyemote.go create mode 100644 modules/twitchat/petcat.go diff --git a/changelog.md b/changelog.md index ed8761b..728e083 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,10 @@ # 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 diff --git a/main.go b/main.go index df7009d..81705ed 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "galched-bot/modules/discord" "galched-bot/modules/grace" + "galched-bot/modules/patpet" "galched-bot/modules/settings" "galched-bot/modules/subday" "galched-bot/modules/twitchat" @@ -87,7 +88,8 @@ func main() { var err error app := fx.New( fx.Logger(new(silentPrinter)), - fx.Provide(settings.New, grace.New, discord.New, subday.New, twitchat.New, web.New, youtube.New), + fx.Provide(settings.New, grace.New, discord.New, subday.New, + twitchat.New, web.New, youtube.New, patpet.New), fx.Invoke(start)) err = app.Start(context.Background()) diff --git a/modules/patpet/patpet.go b/modules/patpet/patpet.go new file mode 100644 index 0000000..35a9592 --- /dev/null +++ b/modules/patpet/patpet.go @@ -0,0 +1,88 @@ +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) +} diff --git a/modules/settings/settings.go b/modules/settings/settings.go index 2f4e283..39d5152 100644 --- a/modules/settings/settings.go +++ b/modules/settings/settings.go @@ -8,12 +8,13 @@ import ( ) const ( - version = "5.0.1" + version = "5.1.0" twitchUser = "galchedbot" twitchIRCRoom = "galched" discordTokenPath = "./tokens/.discordtoken" twitchTokenPath = "./tokens/.twitchtoken" subdayDataPath = "./backups/subday" + petDataPath = "./backups/pets" youtubeTokenPath = "./tokens/.youtubetoken" webLoginsPath = "./tokens/.weblogins" @@ -43,6 +44,7 @@ type ( TwitchToken string YoutubeToken string SubdayDataPath string + PetDataPath string PermittedRoles []string DiscordVoiceChannel string Songs []SongInfo @@ -85,6 +87,7 @@ func New() (*Settings, error) { TwitchUser: twitchUser, TwitchIRCRoom: twitchIRCRoom, SubdayDataPath: subdayDataPath, + PetDataPath: petDataPath, DiscordVoiceChannel: "301793085522706432", PermittedRoles: []string{subRole1, subRole2, galchedRole, smorcRole}, Songs: []SongInfo{ diff --git a/modules/twitchat/dailyemote.go b/modules/twitchat/dailyemote.go new file mode 100644 index 0000000..810ecec --- /dev/null +++ b/modules/twitchat/dailyemote.go @@ -0,0 +1,257 @@ +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()) +} diff --git a/modules/twitchat/petcat.go b/modules/twitchat/petcat.go new file mode 100644 index 0000000..e9aeb13 --- /dev/null +++ b/modules/twitchat/petcat.go @@ -0,0 +1,39 @@ +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() +} diff --git a/modules/twitchat/twitchat.go b/modules/twitchat/twitchat.go index 448b886..e64063a 100644 --- a/modules/twitchat/twitchat.go +++ b/modules/twitchat/twitchat.go @@ -1,6 +1,7 @@ package twitchat import ( + "galched-bot/modules/patpet" "galched-bot/modules/settings" "galched-bot/modules/youtube" @@ -15,13 +16,15 @@ type ( } ) -func New(s *settings.Settings, r *youtube.Requester) (*TwitchIRC, error) { +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)