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.
 
 

207 lines
6.3 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 json
  17. from os import environ
  18. from enum import Enum
  19. from typing import Union
  20. from . import moderator
  21. class Command:
  22. name: str
  23. message: str
  24. aliases: [str]
  25. disabled: bool
  26. def __init__(self, name: str, message: str, aliases: [str] = None, disabled: bool = False):
  27. self.name = name
  28. self.message = message
  29. self.aliases = aliases if aliases is not None else []
  30. self.disabled = disabled
  31. @classmethod
  32. def from_dict(cls, params: dict):
  33. return Command(
  34. params.get('name'),
  35. params['message'],
  36. params.get('aliases', []),
  37. params.get('disabled', False)
  38. )
  39. class TimerStrategy(Enum):
  40. ROUND_ROBIN = "round-robin"
  41. SHUFFLE = "shuffle"
  42. class Timer:
  43. time_between: int
  44. msgs_between: int
  45. strategy: TimerStrategy
  46. pool: [Command]
  47. def __init__(
  48. self,
  49. time_between: int = 10,
  50. msgs_between: int = 10,
  51. strategy: TimerStrategy = TimerStrategy.ROUND_ROBIN,
  52. pool: [Command] = None
  53. ):
  54. self.time_between = time_between
  55. self.msgs_between = msgs_between
  56. self.strategy = strategy
  57. self.pool = pool if pool else []
  58. @classmethod
  59. def from_dict(cls, param: dict):
  60. pool = []
  61. for c in param.get('pool', []):
  62. command = Command.from_dict(c)
  63. if not command.disabled:
  64. pool.append(command)
  65. return Timer(
  66. time_between=param.get('between', {}).get('time', 10),
  67. msgs_between=param.get('between', {}).get('messages', 10),
  68. strategy=TimerStrategy(param.get('strategy', 'round-robin')),
  69. pool=pool
  70. )
  71. class Config:
  72. channel: str
  73. nickname: str
  74. token: str
  75. command_prefix: str
  76. commands: [Command]
  77. timer: Timer
  78. moderators: [moderator.Moderator]
  79. def __init__(
  80. self,
  81. channel: str,
  82. nickname: str,
  83. token: str,
  84. command_prefix: str,
  85. commands: [Command],
  86. timer: Timer,
  87. moderators: [moderator.Moderator]
  88. ):
  89. self.nickname = nickname
  90. self.channel = channel
  91. self.token = token
  92. self.command_prefix = command_prefix
  93. self.commands = commands
  94. self.timer = timer
  95. self.moderators = moderators
  96. @classmethod
  97. def from_dict(cls, params: dict, token: str):
  98. timer = Timer.from_dict(params.get('timer', {}))
  99. commands_prefix = params.get('command_prefix', '!')
  100. commands = []
  101. help_command = Command("help", "Voici les commandes disponibles : ")
  102. for command in params.get('commands', []):
  103. command = Command.from_dict(command)
  104. if command.disabled:
  105. continue
  106. commands.append(command)
  107. for command in timer.pool:
  108. if command.name is None:
  109. continue
  110. commands.append(command)
  111. moderators = []
  112. for mod in params.get('moderator', []):
  113. moderator_config = params['moderator'][mod]
  114. if mod == 'caps-lock':
  115. moderators.append(moderator.CapsLockModerator(
  116. moderator_config.get("message", "{author}, stop the caps lock!"),
  117. cls.parse_decision(moderator_config.get("decision", "delete")),
  118. moderator_config.get("duration", None),
  119. moderator_config.get("min-size", 5),
  120. moderator_config.get("threshold", 50)
  121. ))
  122. if mod == 'flood':
  123. moderators.append(moderator.FloodModerator(
  124. moderator_config.get("message", "{author}, stop the flood!"),
  125. cls.parse_decision(moderator_config.get("decision", "timeout")),
  126. moderator_config.get("duration", None),
  127. moderator_config.get("max-word-length", None),
  128. moderator_config.get("raid-cooldown", None),
  129. moderator_config.get("ignore-hashtags", False),
  130. moderator_config.get("max-msg-occurrences", None),
  131. moderator_config.get("min-time-between-occurrence", None)
  132. ))
  133. # Generate help command
  134. if params.get('help', True):
  135. for command in commands:
  136. help_command.message = "%s %s%s" % (help_command.message, commands_prefix, command.name)
  137. commands.append(help_command)
  138. return Config(
  139. params.get('channel'),
  140. params.get('nickname'),
  141. token,
  142. commands_prefix,
  143. commands,
  144. timer,
  145. moderators
  146. )
  147. @classmethod
  148. def parse_decision(cls, decision_str) -> moderator.ModerationDecision:
  149. if decision_str == "delete":
  150. decision = moderator.ModerationDecision.DELETE_MSG
  151. elif decision_str == "timeout":
  152. decision = moderator.ModerationDecision.TIMEOUT_USER
  153. else:
  154. print("WARNING: %s moderator's decision is invalid, it has been deactivated!")
  155. decision = moderator.ModerationDecision.ABSTAIN
  156. return decision
  157. def find_command(self, command: str) -> Union[None, Command]:
  158. if not command.startswith(self.command_prefix):
  159. return None
  160. command = command[1:]
  161. for c in self.commands:
  162. if c.name == command or command in c.aliases:
  163. return c
  164. return None
  165. def get_config(file_path: str):
  166. with open(file_path, 'r') as config_file:
  167. token = environ['TWITCH_TOKEN']
  168. return Config.from_dict(json.loads(config_file.read()), token)