KiTTY, un bot Discord qui est un petit chat :) Il est basé sur une ancienne version du bot Red, sous Python 3.6 et qui a des fonctionnalités bien sympatiques !
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.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

333 lines
12 KiB

  1. from discord.ext import commands
  2. from random import choice
  3. from .utils.dataIO import dataIO
  4. from .utils import checks
  5. from .utils.chat_formatting import box
  6. from collections import Counter, defaultdict, namedtuple
  7. import discord
  8. import time
  9. import os
  10. import asyncio
  11. import chardet
  12. DEFAULTS = {"MAX_SCORE" : 10,
  13. "TIMEOUT" : 120,
  14. "DELAY" : 15,
  15. "BOT_PLAYS" : False,
  16. "REVEAL_ANSWER": True}
  17. TriviaLine = namedtuple("TriviaLine", "question answers")
  18. class Trivia:
  19. """General commands."""
  20. def __init__(self, bot):
  21. self.bot = bot
  22. self.trivia_sessions = []
  23. self.file_path = "data/trivia/settings.json"
  24. settings = dataIO.load_json(self.file_path)
  25. self.settings = defaultdict(lambda: DEFAULTS.copy(), settings)
  26. @commands.group(pass_context=True, no_pm=True)
  27. @checks.mod_or_permissions(administrator=True)
  28. async def triviaset(self, ctx):
  29. """Change trivia settings"""
  30. server = ctx.message.server
  31. if ctx.invoked_subcommand is None:
  32. settings = self.settings[server.id]
  33. msg = box("Red gains points: {BOT_PLAYS}\n"
  34. "Seconds to answer: {DELAY}\n"
  35. "Points to win: {MAX_SCORE}\n"
  36. "Reveal answer on timeout: {REVEAL_ANSWER}\n"
  37. "".format(**settings))
  38. msg += "\nSee {}help triviaset to edit the settings".format(ctx.prefix)
  39. await self.bot.say(msg)
  40. @triviaset.command(pass_context=True)
  41. async def maxscore(self, ctx, score : int):
  42. """Points required to win"""
  43. server = ctx.message.server
  44. if score > 0:
  45. self.settings[server.id]["MAX_SCORE"] = score
  46. self.save_settings()
  47. await self.bot.say("Points required to win set to {}".format(score))
  48. else:
  49. await self.bot.say("Score must be superior to 0.")
  50. @triviaset.command(pass_context=True)
  51. async def timelimit(self, ctx, seconds : int):
  52. """Maximum seconds to answer"""
  53. server = ctx.message.server
  54. if seconds > 4:
  55. self.settings[server.id]["DELAY"] = seconds
  56. self.save_settings()
  57. await self.bot.say("Maximum seconds to answer set to {}".format(seconds))
  58. else:
  59. await self.bot.say("Seconds must be at least 5.")
  60. @triviaset.command(pass_context=True)
  61. async def botplays(self, ctx):
  62. """Red gains points"""
  63. server = ctx.message.server
  64. if self.settings[server.id]["BOT_PLAYS"]:
  65. self.settings[server.id]["BOT_PLAYS"] = False
  66. await self.bot.say("Alright, I won't embarass you at trivia anymore.")
  67. else:
  68. self.settings[server.id]["BOT_PLAYS"] = True
  69. await self.bot.say("I'll gain a point every time you don't answer in time.")
  70. self.save_settings()
  71. @triviaset.command(pass_context=True)
  72. async def revealanswer(self, ctx):
  73. """Reveals answer to the question on timeout"""
  74. server = ctx.message.server
  75. if self.settings[server.id]["REVEAL_ANSWER"]:
  76. self.settings[server.id]["REVEAL_ANSWER"] = False
  77. await self.bot.say("I won't reveal the answer to the questions anymore.")
  78. else:
  79. self.settings[server.id]["REVEAL_ANSWER"] = True
  80. await self.bot.say("I'll reveal the answer if no one knows it.")
  81. self.save_settings()
  82. @commands.group(pass_context=True, invoke_without_command=True, no_pm=True)
  83. async def trivia(self, ctx, list_name: str):
  84. """Start a trivia session with the specified list"""
  85. message = ctx.message
  86. server = message.server
  87. session = self.get_trivia_by_channel(message.channel)
  88. if not session:
  89. try:
  90. trivia_list = self.parse_trivia_list(list_name)
  91. except FileNotFoundError:
  92. await self.bot.say("That trivia list doesn't exist.")
  93. except Exception as e:
  94. print(e)
  95. await self.bot.say("Error loading the trivia list.")
  96. else:
  97. settings = self.settings[server.id]
  98. t = TriviaSession(self.bot, trivia_list, message, settings)
  99. self.trivia_sessions.append(t)
  100. await t.new_question()
  101. else:
  102. await self.bot.say("A trivia session is already ongoing in this channel.")
  103. @trivia.group(name="stop", pass_context=True, no_pm=True)
  104. async def trivia_stop(self, ctx):
  105. """Stops an ongoing trivia session"""
  106. author = ctx.message.author
  107. server = author.server
  108. admin_role = self.bot.settings.get_server_admin(server)
  109. mod_role = self.bot.settings.get_server_mod(server)
  110. is_admin = discord.utils.get(author.roles, name=admin_role)
  111. is_mod = discord.utils.get(author.roles, name=mod_role)
  112. is_owner = author.id == self.bot.settings.owner
  113. is_server_owner = author == server.owner
  114. is_authorized = is_admin or is_mod or is_owner or is_server_owner
  115. session = self.get_trivia_by_channel(ctx.message.channel)
  116. if session:
  117. if author == session.starter or is_authorized:
  118. await session.end_game()
  119. await self.bot.say("Trivia stopped.")
  120. else:
  121. await self.bot.say("You are not allowed to do that.")
  122. else:
  123. await self.bot.say("There's no trivia session ongoing in this channel.")
  124. @trivia.group(name="list")
  125. async def trivia_list(self):
  126. """Shows available trivia lists"""
  127. lists = os.listdir("data/trivia/")
  128. lists = [l for l in lists if l.endswith(".txt") and " " not in l]
  129. lists = [l.replace(".txt", "") for l in lists]
  130. if lists:
  131. msg = "+ Available trivia lists\n\n" + ", ".join(sorted(lists))
  132. msg = box(msg, lang="diff")
  133. if len(lists) < 100:
  134. await self.bot.say(msg)
  135. else:
  136. await self.bot.whisper(msg)
  137. else:
  138. await self.bot.say("There are no trivia lists available.")
  139. def parse_trivia_list(self, filename):
  140. path = "data/trivia/{}.txt".format(filename)
  141. parsed_list = []
  142. with open(path, "rb") as f:
  143. try:
  144. encoding = chardet.detect(f.read())["encoding"]
  145. except:
  146. encoding = "ISO-8859-1"
  147. with open(path, "r", encoding=encoding) as f:
  148. trivia_list = f.readlines()
  149. for line in trivia_list:
  150. if "`" not in line:
  151. continue
  152. line = line.replace("\n", "")
  153. line = line.split("`")
  154. question = line[0]
  155. answers = []
  156. for l in line[1:]:
  157. answers.append(l.strip())
  158. if len(line) >= 2 and question and answers:
  159. line = TriviaLine(question=question, answers=answers)
  160. parsed_list.append(line)
  161. if not parsed_list:
  162. raise ValueError("Empty trivia list")
  163. return parsed_list
  164. def get_trivia_by_channel(self, channel):
  165. for t in self.trivia_sessions:
  166. if t.channel == channel:
  167. return t
  168. return None
  169. async def on_message(self, message):
  170. if message.author != self.bot.user:
  171. session = self.get_trivia_by_channel(message.channel)
  172. if session:
  173. await session.check_answer(message)
  174. async def on_trivia_end(self, instance):
  175. if instance in self.trivia_sessions:
  176. self.trivia_sessions.remove(instance)
  177. def save_settings(self):
  178. dataIO.save_json(self.file_path, self.settings)
  179. class TriviaSession():
  180. def __init__(self, bot, trivia_list, message, settings):
  181. self.bot = bot
  182. self.reveal_messages = ("I know this one! {}!",
  183. "Easy: {}.",
  184. "Oh really? It's {} of course.")
  185. self.fail_messages = ("To the next one I guess...",
  186. "Moving on...",
  187. "I'm sure you'll know the answer of the next one.",
  188. "\N{PENSIVE FACE} Next one.")
  189. self.current_line = None # {"QUESTION" : "String", "ANSWERS" : []}
  190. self.question_list = trivia_list
  191. self.channel = message.channel
  192. self.starter = message.author
  193. self.scores = Counter()
  194. self.status = "new question"
  195. self.timer = None
  196. self.timeout = time.perf_counter()
  197. self.count = 0
  198. self.settings = settings
  199. async def stop_trivia(self):
  200. self.status = "stop"
  201. self.bot.dispatch("trivia_end", self)
  202. async def end_game(self):
  203. self.status = "stop"
  204. if self.scores:
  205. await self.send_table()
  206. self.bot.dispatch("trivia_end", self)
  207. async def new_question(self):
  208. for score in self.scores.values():
  209. if score == self.settings["MAX_SCORE"]:
  210. await self.end_game()
  211. return True
  212. if self.question_list == []:
  213. await self.end_game()
  214. return True
  215. self.current_line = choice(self.question_list)
  216. self.question_list.remove(self.current_line)
  217. self.status = "waiting for answer"
  218. self.count += 1
  219. self.timer = int(time.perf_counter())
  220. msg = "**Question number {}!**\n\n{}".format(self.count, self.current_line.question)
  221. await self.bot.say(msg)
  222. while self.status != "correct answer" and abs(self.timer - int(time.perf_counter())) <= self.settings["DELAY"]:
  223. if abs(self.timeout - int(time.perf_counter())) >= self.settings["TIMEOUT"]:
  224. await self.bot.say("Guys...? Well, I guess I'll stop then.")
  225. await self.stop_trivia()
  226. return True
  227. await asyncio.sleep(1) #Waiting for an answer or for the time limit
  228. if self.status == "correct answer":
  229. self.status = "new question"
  230. await asyncio.sleep(3)
  231. if not self.status == "stop":
  232. await self.new_question()
  233. elif self.status == "stop":
  234. return True
  235. else:
  236. if self.settings["REVEAL_ANSWER"]:
  237. msg = choice(self.reveal_messages).format(self.current_line.answers[0])
  238. else:
  239. msg = choice(self.fail_messages)
  240. if self.settings["BOT_PLAYS"]:
  241. msg += " **+1** for me!"
  242. self.scores[self.bot.user] += 1
  243. self.current_line = None
  244. await self.bot.say(msg)
  245. await self.bot.type()
  246. await asyncio.sleep(3)
  247. if not self.status == "stop":
  248. await self.new_question()
  249. async def send_table(self):
  250. t = "+ Results: \n\n"
  251. for user, score in self.scores.most_common():
  252. t += "+ {}\t{}\n".format(user, score)
  253. await self.bot.say(box(t, lang="diff"))
  254. async def check_answer(self, message):
  255. if message.author == self.bot.user:
  256. return
  257. elif self.current_line is None:
  258. return
  259. self.timeout = time.perf_counter()
  260. has_guessed = False
  261. for answer in self.current_line.answers:
  262. answer = answer.lower()
  263. guess = message.content.lower()
  264. if " " not in answer: # Exact matching, issue #331
  265. guess = guess.split(" ")
  266. for word in guess:
  267. if word == answer:
  268. has_guessed = True
  269. else: # The answer has spaces, we can't be as strict
  270. if answer in guess:
  271. has_guessed = True
  272. if has_guessed:
  273. self.current_line = None
  274. self.status = "correct answer"
  275. self.scores[message.author] += 1
  276. msg = "You got it {}! **+1** to you!".format(message.author.name)
  277. await self.bot.send_message(message.channel, msg)
  278. def check_folders():
  279. folders = ("data", "data/trivia/")
  280. for folder in folders:
  281. if not os.path.exists(folder):
  282. print("Creating " + folder + " folder...")
  283. os.makedirs(folder)
  284. def check_files():
  285. if not os.path.isfile("data/trivia/settings.json"):
  286. print("Creating empty settings.json...")
  287. dataIO.save_json("data/trivia/settings.json", {})
  288. def setup(bot):
  289. check_folders()
  290. check_files()
  291. bot.add_cog(Trivia(bot))