# Twason - The KISS Twitch bot
# Copyright (C) 2021 Jérôme Deuchnord
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
import irc3
from random import shuffle
from datetime import datetime, timedelta
from . import utils
from .config import TimerStrategy
from .moderator import ModerationDecision, Moderator, FloodModerator
from . import twitch_message
config = None
@irc3.plugin
class TwitchBot:
def __init__(self, bot: irc3.IrcBot):
self.config = config
self.messages_stack = []
self.bot = bot
self.log = self.bot.log
self.last_timer_date = datetime.now()
self.nb_messages_since_timer = 0
def connection_made(self):
print('connected')
def server_ready(self):
print('ready')
def connection_lost(self):
print('connection lost')
@staticmethod
def _parse_variables(in_str: str, **kwargs):
for key in kwargs:
value = kwargs[key]
in_str = in_str.replace('{%s}' % key, value)
return in_str
# @irc3.event(r'^(?P.+)$')
# def on_all(self, data):
# print(data)
@irc3.event(irc3.rfc.PRIVMSG)
def on_msg(self, mask: str = None, target: str = None, data: str = None, tags: str = None, **_):
author = mask.split('!')[0]
command = self.config.find_command(data.lower().split(' ')[0])
tags_dict = utils.parse_tags(tags)
if command is not None:
print('%s: %s%s' % (author, self.config.command_prefix, command.name))
self.bot.privmsg(target, self._parse_variables(command.message, author=author))
elif tags_dict.get('mod') == '0':
self.moderate(tags_dict, data, author, target)
self.nb_messages_since_timer += 1
self.play_timer()
@irc3.event(twitch_message.USERNOTICE)
def on_user_notice(self, tags: str, **_):
tags = utils.parse_tags(tags)
if tags.get('msg-id', None) == 'raid':
# Notice the Flood moderator a raid has just happened
for moderator in self.config.moderators:
if isinstance(moderator, FloodModerator) and moderator.raid_cooldown is not None:
print("Raid received from %s. Disabling the Flood moderator." % tags.get('display-name'))
moderator.declare_raid()
break
def play_timer(self):
if not self.messages_stack:
print('Filling the timer messages stack in')
self.messages_stack = self.config.timer.pool.copy()
if self.config.timer.strategy == TimerStrategy.SHUFFLE:
print('Shuffle!')
shuffle(self.messages_stack)
if self.nb_messages_since_timer < self.config.timer.msgs_between or \
datetime.now() < self.last_timer_date + timedelta(minutes=self.config.timer.time_between):
return
command = self.messages_stack.pop(0)
print("Timer: %s" % command.message)
self.bot.privmsg('#%s' % self.config.channel, command.message)
self.nb_messages_since_timer = 0
self.last_timer_date = datetime.now()
def moderate(self, tags: {str: str}, msg: str, author: str, channel: str):
def delete_msg(mod: Moderator):
print("[DELETE (reason: %s)] %s: %s" % (mod.get_name(), author, msg))
self.bot.privmsg(
channel,
"/delete %s" % tags['id']
)
def timeout(mod: Moderator):
print("[TIMEOUT (reason: %s)] %s: %s" % (mod.get_name(), author, msg))
self.bot.privmsg(
channel,
"/timeout %s %d %s" % (
author,
mod.timeout_duration,
self._parse_variables(mod.message, author=author)
)
)
# Ignore emotes-only messages
if tags.get('emote-only', '0') == '1':
return
message_to_moderate = msg
# Remove emotes from message before moderating
for emote in tags.get('emotes', '').split('/'):
if emote == '':
break
for indices in emote.split(':')[1].split(','):
[first, last] = indices.split('-')
first, last = int(first), int(last)
if first == 0:
message_to_moderate = message_to_moderate[last + 1:]
else:
message_to_moderate = message_to_moderate[:first - 1] + message_to_moderate[last + 1:]
for moderator in self.config.moderators:
vote = moderator.vote(message_to_moderate, author)
if vote == ModerationDecision.ABSTAIN:
continue
if vote == ModerationDecision.DELETE_MSG:
delete_msg(moderator)
if vote == ModerationDecision.TIMEOUT_USER:
timeout(moderator)
self.bot.privmsg(channel, self._parse_variables(moderator.message, author=author))
break
@irc3.event(irc3.rfc.JOIN)
def on_join(self, mask, channel, **_):
print('JOINED %s as %s' % (channel, mask))
@irc3.event(irc3.rfc.CONNECTED)
def on_connected(self, **_):
for line in [
"CAP REQ :twitch.tv/commands",
"CAP REQ :twitch.tv/tags"
]:
self.bot.send_line(line)
self.bot.join('#%s' % self.config.channel)