2018-12-07 21:31:15 +01:00
|
|
|
package bot
|
|
|
|
|
|
|
|
import (
|
2020-12-05 18:32:21 +01:00
|
|
|
"math/rand"
|
2018-12-07 21:31:15 +01:00
|
|
|
"strings"
|
2021-04-16 18:27:19 +02:00
|
|
|
"sync"
|
2018-12-07 21:31:15 +01:00
|
|
|
"time"
|
|
|
|
|
2020-03-08 15:22:03 +01:00
|
|
|
"github.com/gempir/justlog/config"
|
2018-12-07 21:31:15 +01:00
|
|
|
"github.com/gempir/justlog/filelog"
|
2023-01-08 19:45:57 +01:00
|
|
|
expiremap "github.com/nursik/go-expire-map"
|
2018-12-07 21:31:15 +01:00
|
|
|
|
2022-01-12 19:44:06 +01:00
|
|
|
twitch "github.com/gempir/go-twitch-irc/v3"
|
2018-12-07 21:31:15 +01:00
|
|
|
"github.com/gempir/justlog/helix"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
2020-03-02 21:29:49 +01:00
|
|
|
// Bot basic logging bot
|
2018-12-07 21:31:15 +01:00
|
|
|
type Bot struct {
|
2021-04-16 18:03:41 +02:00
|
|
|
startTime time.Time
|
|
|
|
cfg *config.Config
|
|
|
|
helixClient helix.TwitchApiClient
|
2022-10-20 20:06:39 +02:00
|
|
|
logger filelog.Logger
|
2021-04-16 18:03:41 +02:00
|
|
|
worker []*worker
|
|
|
|
channels map[string]helix.UserData
|
2021-04-16 18:27:19 +02:00
|
|
|
clearchats sync.Map
|
2022-07-31 15:03:21 +02:00
|
|
|
OptoutCodes sync.Map
|
2023-01-08 19:45:57 +01:00
|
|
|
msgMap *expiremap.ExpireMap
|
2018-12-07 21:31:15 +01:00
|
|
|
}
|
|
|
|
|
2020-12-05 18:32:21 +01:00
|
|
|
type worker struct {
|
|
|
|
client *twitch.Client
|
2022-10-16 10:21:01 +02:00
|
|
|
joinedChannels map[string]bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func newWorker(client *twitch.Client) *worker {
|
|
|
|
return &worker{
|
|
|
|
client: client,
|
|
|
|
joinedChannels: map[string]bool{},
|
|
|
|
}
|
2020-12-05 18:32:21 +01:00
|
|
|
}
|
|
|
|
|
2020-03-02 21:29:49 +01:00
|
|
|
// NewBot create new bot instance
|
2022-10-20 20:06:39 +02:00
|
|
|
func NewBot(cfg *config.Config, helixClient helix.TwitchApiClient, fileLogger filelog.Logger) *Bot {
|
2020-03-08 17:54:06 +01:00
|
|
|
channels, err := helixClient.GetUsersByUserIds(cfg.Channels)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("[bot] failed to load configured channels %s", err.Error())
|
|
|
|
}
|
|
|
|
|
2018-12-07 21:31:15 +01:00
|
|
|
return &Bot{
|
2020-03-08 17:54:06 +01:00
|
|
|
cfg: cfg,
|
|
|
|
helixClient: helixClient,
|
2022-10-20 20:06:39 +02:00
|
|
|
logger: fileLogger,
|
2020-03-08 17:54:06 +01:00
|
|
|
channels: channels,
|
2020-12-05 18:32:21 +01:00
|
|
|
worker: []*worker{},
|
2022-07-31 15:03:21 +02:00
|
|
|
OptoutCodes: sync.Map{},
|
2023-01-08 19:45:57 +01:00
|
|
|
msgMap: expiremap.New(),
|
2018-12-07 21:31:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-05 18:32:21 +01:00
|
|
|
func (b *Bot) Say(channel, text string) {
|
|
|
|
randomIndex := rand.Intn(len(b.worker))
|
|
|
|
b.worker[randomIndex].client.Say(channel, text)
|
|
|
|
}
|
|
|
|
|
2020-03-02 21:29:49 +01:00
|
|
|
// Connect startup the logger and bot
|
2020-03-08 17:54:06 +01:00
|
|
|
func (b *Bot) Connect() {
|
2020-03-08 15:22:03 +01:00
|
|
|
b.startTime = time.Now()
|
2020-12-05 18:32:21 +01:00
|
|
|
client := b.newClient()
|
2022-10-16 10:21:01 +02:00
|
|
|
go b.startJoinLoop()
|
2018-12-07 21:31:15 +01:00
|
|
|
|
2020-03-08 15:22:03 +01:00
|
|
|
if strings.HasPrefix(b.cfg.Username, "justinfan") {
|
2021-10-03 01:38:31 +02:00
|
|
|
log.Info("[bot] joining as anonymous user")
|
2018-12-07 21:31:15 +01:00
|
|
|
} else {
|
2020-03-08 16:06:27 +01:00
|
|
|
log.Info("[bot] joining as user " + b.cfg.Username)
|
2018-12-07 21:31:15 +01:00
|
|
|
}
|
|
|
|
|
2023-01-08 19:45:57 +01:00
|
|
|
defer b.msgMap.Close()
|
2020-12-05 18:32:21 +01:00
|
|
|
log.Fatal(client.Connect())
|
|
|
|
}
|
|
|
|
|
2022-10-16 10:21:01 +02:00
|
|
|
// constantly join channels to rejoin some channels that got unbanned over time
|
|
|
|
func (b *Bot) startJoinLoop() {
|
|
|
|
for {
|
|
|
|
for _, channel := range b.channels {
|
|
|
|
b.Join(channel.Login)
|
|
|
|
}
|
|
|
|
|
|
|
|
time.Sleep(time.Hour * 1)
|
|
|
|
log.Info("[bot] running hourly join loop")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-16 18:03:41 +02:00
|
|
|
func (b *Bot) Part(channelNames ...string) {
|
2020-12-05 18:32:21 +01:00
|
|
|
for _, channelName := range channelNames {
|
|
|
|
log.Info("[bot] leaving " + channelName)
|
|
|
|
|
|
|
|
for _, worker := range b.worker {
|
|
|
|
worker.client.Depart(channelName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Bot) Join(channelNames ...string) {
|
|
|
|
for _, channel := range channelNames {
|
2022-10-16 10:21:01 +02:00
|
|
|
channel = strings.ToLower(channel)
|
2020-12-05 18:32:21 +01:00
|
|
|
|
|
|
|
joined := false
|
2022-10-16 10:21:01 +02:00
|
|
|
|
2020-12-05 18:32:21 +01:00
|
|
|
for _, worker := range b.worker {
|
2022-10-16 10:21:01 +02:00
|
|
|
if _, ok := worker.joinedChannels[channel]; ok {
|
|
|
|
// already joined but join again in case it was a temporary ban
|
2022-11-29 20:49:12 +01:00
|
|
|
worker.client.Join(channel)
|
|
|
|
joined = true
|
2022-12-21 17:30:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, worker := range b.worker {
|
|
|
|
if len(worker.joinedChannels) < 50 {
|
2020-12-05 18:32:21 +01:00
|
|
|
log.Info("[bot] joining " + channel)
|
|
|
|
worker.client.Join(channel)
|
2022-10-16 10:21:01 +02:00
|
|
|
worker.joinedChannels[channel] = true
|
2020-12-05 18:32:21 +01:00
|
|
|
joined = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2022-12-21 17:30:21 +01:00
|
|
|
|
2020-12-05 18:32:21 +01:00
|
|
|
if !joined {
|
|
|
|
client := b.newClient()
|
|
|
|
go client.Connect()
|
|
|
|
b.Join(channel)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Bot) newClient() *twitch.Client {
|
|
|
|
client := twitch.NewClient(b.cfg.Username, "oauth:"+b.cfg.OAuth)
|
2021-09-28 10:36:08 +02:00
|
|
|
if b.cfg.BotVerified {
|
2022-01-12 19:44:06 +01:00
|
|
|
client.SetJoinRateLimiter(twitch.CreateVerifiedRateLimiter())
|
2021-09-28 10:36:08 +02:00
|
|
|
}
|
|
|
|
|
2022-10-16 10:21:01 +02:00
|
|
|
b.worker = append(b.worker, newWorker(client))
|
2020-12-05 18:32:21 +01:00
|
|
|
log.Infof("[bot] creating new twitch connection, new total: %d", len(b.worker))
|
|
|
|
|
2021-01-08 22:19:20 +01:00
|
|
|
client.OnPrivateMessage(b.handlePrivateMessage)
|
|
|
|
client.OnUserNoticeMessage(b.handleUserNotice)
|
|
|
|
client.OnClearChatMessage(b.handleClearChat)
|
2018-12-07 21:31:15 +01:00
|
|
|
|
2021-01-08 22:19:20 +01:00
|
|
|
return client
|
|
|
|
}
|
2018-12-07 21:31:15 +01:00
|
|
|
|
2021-01-08 22:19:20 +01:00
|
|
|
func (b *Bot) handlePrivateMessage(message twitch.PrivateMessage) {
|
2023-01-08 19:45:57 +01:00
|
|
|
if _, ok := b.msgMap.Get(message.ID); ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
b.msgMap.Set(message.ID, true, time.Second*3)
|
|
|
|
|
2021-06-05 16:23:52 +02:00
|
|
|
b.handlePrivateMessageCommands(message)
|
|
|
|
|
|
|
|
if b.cfg.IsOptedOut(message.User.ID) || b.cfg.IsOptedOut(message.RoomID) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-01-08 22:19:20 +01:00
|
|
|
go func() {
|
2022-10-20 20:06:39 +02:00
|
|
|
err := b.logger.LogPrivateMessageForUser(message.User, message)
|
2021-01-08 22:19:20 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Error(err.Error())
|
|
|
|
}
|
|
|
|
}()
|
2018-12-07 21:31:15 +01:00
|
|
|
|
2021-01-08 22:19:20 +01:00
|
|
|
go func() {
|
2022-10-20 20:06:39 +02:00
|
|
|
err := b.logger.LogPrivateMessageForChannel(message)
|
2021-01-08 22:19:20 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Error(err.Error())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
2019-07-13 15:05:29 +02:00
|
|
|
|
2021-01-08 22:19:20 +01:00
|
|
|
func (b *Bot) handleUserNotice(message twitch.UserNoticeMessage) {
|
2023-01-08 19:45:57 +01:00
|
|
|
if _, ok := b.msgMap.Get(message.ID); ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
b.msgMap.Set(message.ID, true, time.Second*3)
|
|
|
|
|
2021-06-05 16:23:52 +02:00
|
|
|
if b.cfg.IsOptedOut(message.User.ID) || b.cfg.IsOptedOut(message.RoomID) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-01-08 22:19:20 +01:00
|
|
|
go func() {
|
2022-10-20 20:06:39 +02:00
|
|
|
err := b.logger.LogUserNoticeMessageForUser(message.User.ID, message)
|
2021-01-08 22:19:20 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Error(err.Error())
|
2019-07-13 15:05:29 +02:00
|
|
|
}
|
2021-01-08 22:19:20 +01:00
|
|
|
}()
|
2019-07-13 15:05:29 +02:00
|
|
|
|
2021-01-08 22:19:20 +01:00
|
|
|
if _, ok := message.Tags["msg-param-recipient-id"]; ok {
|
2019-07-13 15:05:29 +02:00
|
|
|
go func() {
|
2022-10-20 20:06:39 +02:00
|
|
|
err := b.logger.LogUserNoticeMessageForUser(message.Tags["msg-param-recipient-id"], message)
|
2019-07-13 15:05:29 +02:00
|
|
|
if err != nil {
|
|
|
|
log.Error(err.Error())
|
|
|
|
}
|
|
|
|
}()
|
2021-01-08 22:19:20 +01:00
|
|
|
}
|
2019-07-13 15:05:29 +02:00
|
|
|
|
2021-01-08 22:19:20 +01:00
|
|
|
go func() {
|
2022-10-20 20:06:39 +02:00
|
|
|
err := b.logger.LogUserNoticeMessageForChannel(message)
|
2021-01-08 22:19:20 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Error(err.Error())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
2018-12-07 21:31:15 +01:00
|
|
|
|
2021-01-08 22:19:20 +01:00
|
|
|
func (b *Bot) handleClearChat(message twitch.ClearChatMessage) {
|
2021-06-05 16:23:52 +02:00
|
|
|
if b.cfg.IsOptedOut(message.TargetUserID) || b.cfg.IsOptedOut(message.RoomID) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-16 19:46:00 +02:00
|
|
|
if message.BanDuration == 0 {
|
2021-04-16 18:27:19 +02:00
|
|
|
count, ok := b.clearchats.Load(message.RoomID)
|
2021-04-16 19:46:00 +02:00
|
|
|
if !ok {
|
|
|
|
count = 0
|
2021-04-16 18:27:19 +02:00
|
|
|
}
|
2021-04-16 19:46:00 +02:00
|
|
|
newCount := count.(int) + 1
|
|
|
|
b.clearchats.Store(message.RoomID, newCount)
|
2021-04-16 18:27:19 +02:00
|
|
|
|
2021-04-16 19:46:00 +02:00
|
|
|
go func() {
|
|
|
|
time.Sleep(time.Second * 1)
|
|
|
|
count, ok := b.clearchats.Load(message.RoomID)
|
|
|
|
if ok {
|
|
|
|
b.clearchats.Store(message.RoomID, count.(int)-1)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if newCount > 50 {
|
|
|
|
if newCount == 51 {
|
|
|
|
log.Infof("Stopped recording CLEARCHAT permabans in: %s", message.Channel)
|
|
|
|
}
|
|
|
|
return
|
2021-04-16 19:24:54 +02:00
|
|
|
}
|
2021-04-16 18:27:19 +02:00
|
|
|
}
|
|
|
|
|
2021-01-08 22:19:20 +01:00
|
|
|
go func() {
|
2022-10-20 20:06:39 +02:00
|
|
|
err := b.logger.LogClearchatMessageForUser(message.TargetUserID, message)
|
2021-01-08 22:19:20 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Error(err.Error())
|
|
|
|
}
|
|
|
|
}()
|
2018-12-07 21:31:15 +01:00
|
|
|
|
2021-01-08 22:19:20 +01:00
|
|
|
go func() {
|
2022-10-20 20:06:39 +02:00
|
|
|
err := b.logger.LogClearchatMessageForChannel(message)
|
2021-01-08 22:19:20 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Error(err.Error())
|
|
|
|
}
|
|
|
|
}()
|
2020-03-08 15:22:03 +01:00
|
|
|
}
|