The KISS Twitch bot
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

157 lines
5.2 KiB

  1. # Twason - The KISS Twitch bot
  2. # Copyright (C) 2021 Jérôme Deuchnord
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU Affero General Public License as
  6. # published by the Free Software Foundation, either version 3 of the
  7. # License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU Affero General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. import irc3
  17. from . import utils
  18. from .config import TimerStrategy
  19. from .moderator import ModerationDecision, Moderator
  20. from random import shuffle
  21. from datetime import datetime, timedelta
  22. config = None
  23. @irc3.plugin
  24. class TwitchBot:
  25. def __init__(self, bot: irc3.IrcBot):
  26. self.config = config
  27. self.messages_stack = []
  28. self.bot = bot
  29. self.log = self.bot.log
  30. self.last_timer_date = datetime.now()
  31. self.nb_messages_since_timer = 0
  32. def connection_made(self):
  33. print('connected')
  34. def server_ready(self):
  35. print('ready')
  36. def connection_lost(self):
  37. print('connection lost')
  38. @staticmethod
  39. def _parse_variables(in_str: str, **kwargs):
  40. for key in kwargs:
  41. value = kwargs[key]
  42. in_str = in_str.replace('{%s}' % key, value)
  43. return in_str
  44. @irc3.event(irc3.rfc.PRIVMSG)
  45. def on_msg(self, mask: str = None, target: str = None, data: str = None, tags: str = None, **_):
  46. author = mask.split('!')[0]
  47. command = self.config.find_command(data.lower().split(' ')[0])
  48. tags_dict = utils.parse_tags(tags)
  49. if command is not None:
  50. print('%s: %s%s' % (author, self.config.command_prefix, command.name))
  51. self.bot.privmsg(target, self._parse_variables(command.message, author=author))
  52. elif tags_dict.get('mod') == '0':
  53. self.moderate(tags_dict, data, author, target)
  54. self.nb_messages_since_timer += 1
  55. self.play_timer()
  56. def play_timer(self):
  57. if not self.messages_stack:
  58. print('Filling the timer messages stack in')
  59. self.messages_stack = self.config.timer.pool.copy()
  60. if self.config.timer.strategy == TimerStrategy.SHUFFLE:
  61. print('Shuffle!')
  62. shuffle(self.messages_stack)
  63. if self.nb_messages_since_timer < self.config.timer.msgs_between or \
  64. datetime.now() < self.last_timer_date + timedelta(minutes=self.config.timer.time_between):
  65. return
  66. command = self.messages_stack.pop(0)
  67. print("Timer: %s" % command.message)
  68. self.bot.privmsg('#%s' % self.config.channel, command.message)
  69. self.nb_messages_since_timer = 0
  70. self.last_timer_date = datetime.now()
  71. def moderate(self, tags: {str: str}, msg: str, author: str, channel: str):
  72. print(tags)
  73. def delete_msg(mod: Moderator):
  74. print("[DELETE (reason: %s)] %s: %s" % (mod.get_name(), author, msg))
  75. self.bot.privmsg(
  76. channel,
  77. "/delete %s" % tags['id']
  78. )
  79. def timeout(mod: Moderator):
  80. print("[TIMEOUT (reason: %s)] %s: %s" % (mod.get_name(), author, msg))
  81. self.bot.privmsg(
  82. channel,
  83. "/timeout %s %d %s" % (
  84. author,
  85. mod.timeout_duration,
  86. self._parse_variables(mod.message, author=author)
  87. )
  88. )
  89. # Ignore emotes-only messages
  90. if tags.get('emote-only', '0') == '1':
  91. return
  92. message_to_moderate = msg
  93. # Remove emotes from message before moderating
  94. for emote in tags.get('emotes', '').split('/'):
  95. if emote == '':
  96. break
  97. for indices in emote.split(':')[1].split(','):
  98. [first, last] = indices.split('-')
  99. first, last = int(first), int(last)
  100. if first == 0:
  101. message_to_moderate = message_to_moderate[last + 1:]
  102. else:
  103. message_to_moderate = message_to_moderate[:first - 1] + message_to_moderate[last + 1:]
  104. for moderator in self.config.moderators:
  105. vote = moderator.vote(message_to_moderate)
  106. if vote == ModerationDecision.ABSTAIN:
  107. continue
  108. if vote == ModerationDecision.DELETE_MSG:
  109. delete_msg(moderator)
  110. if vote == ModerationDecision.TIMEOUT_USER:
  111. timeout(moderator)
  112. self.bot.privmsg(channel, self._parse_variables(moderator.message, author=author))
  113. break
  114. @irc3.event(irc3.rfc.JOIN)
  115. def on_join(self, mask, channel, **_):
  116. print('JOINED %s as %s' % (channel, mask))
  117. @irc3.event(irc3.rfc.CONNECTED)
  118. def on_connected(self, **_):
  119. for line in [
  120. "CAP REQ :twitch.tv/commands",
  121. "CAP REQ :twitch.tv/tags"
  122. ]:
  123. self.bot.send_line(line)
  124. self.bot.join('#%s' % self.config.channel)