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.

434 lines
16KB

  1. import discord
  2. from discord.ext import commands
  3. from .utils.chat_formatting import escape_mass_mentions, italics, pagify
  4. from random import randint
  5. from random import choice
  6. from enum import Enum
  7. from urllib.parse import quote_plus
  8. import datetime
  9. import time
  10. import aiohttp
  11. import asyncio
  12. settings = {"POLL_DURATION" : 60}
  13. class RPS(Enum):
  14. rock = "\N{MOYAI}"
  15. paper = "\N{PAGE FACING UP}"
  16. scissors = "\N{BLACK SCISSORS}"
  17. class RPSParser:
  18. def __init__(self, argument):
  19. argument = argument.lower()
  20. if argument == "rock":
  21. self.choice = RPS.rock
  22. elif argument == "paper":
  23. self.choice = RPS.paper
  24. elif argument == "scissors":
  25. self.choice = RPS.scissors
  26. else:
  27. raise
  28. class General:
  29. """General commands."""
  30. def __init__(self, bot):
  31. self.bot = bot
  32. self.stopwatches = {}
  33. self.ball = ["As I see it, yes", "It is certain", "It is decidedly so", "Most likely", "Outlook good",
  34. "Signs point to yes", "Without a doubt", "Yes", "Yes – definitely", "You may rely on it", "Reply hazy, try again",
  35. "Ask again later", "Better not tell you now", "Cannot predict now", "Concentrate and ask again",
  36. "Don't count on it", "My reply is no", "My sources say no", "Outlook not so good", "Very doubtful", "My cat is agree"]
  37. self.poll_sessions = []
  38. @commands.command(hidden=True)
  39. async def ping(self):
  40. """Pong."""
  41. await self.bot.say(":cat: Meow ?")
  42. @commands.command()
  43. async def choose(self, *choices):
  44. """Chooses between multiple choices.
  45. To denote multiple choices, you should use double quotes.
  46. """
  47. choices = [escape_mass_mentions(c) for c in choices]
  48. if len(choices) < 2:
  49. await self.bot.say('Not enough choices to pick from.')
  50. else:
  51. await self.bot.say(choice(choices))
  52. @commands.command(pass_context=True)
  53. async def roll(self, ctx, number : int = 100):
  54. """Rolls random number (between 1 and user choice)
  55. Defaults to 100.
  56. """
  57. author = ctx.message.author
  58. if number > 1:
  59. n = randint(1, number)
  60. await self.bot.say("{} :game_die: {} :game_die:".format(author.mention, n))
  61. else:
  62. await self.bot.say("{} Maybe higher than 1? ;P".format(author.mention))
  63. @commands.command(pass_context=True)
  64. async def flip(self, ctx, user : discord.Member=None):
  65. """Flips a coin... or a user.
  66. Defaults to coin.
  67. """
  68. if user != None:
  69. msg = ""
  70. if user.id == self.bot.user.id:
  71. user = ctx.message.author
  72. msg = "Nice try. You think this is funny? How about *this* instead:\n\n"
  73. char = "abcdefghijklmnopqrstuvwxyz"
  74. tran = "ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz"
  75. table = str.maketrans(char, tran)
  76. name = user.display_name.translate(table)
  77. char = char.upper()
  78. tran = "∀qƆpƎℲפHIſʞ˥WNOԀQᴚS┴∩ΛMX⅄Z"
  79. table = str.maketrans(char, tran)
  80. name = name.translate(table)
  81. await self.bot.say(msg + "(╯°□°)╯︵ " + name[::-1])
  82. else:
  83. await self.bot.say("*flips a coin and... " + choice(["HEADS!*", "TAILS!*"]))
  84. @commands.command(pass_context=True)
  85. async def rps(self, ctx, your_choice : RPSParser):
  86. """Play rock paper scissors"""
  87. author = ctx.message.author
  88. player_choice = your_choice.choice
  89. red_choice = choice((RPS.rock, RPS.paper, RPS.scissors))
  90. cond = {
  91. (RPS.rock, RPS.paper) : False,
  92. (RPS.rock, RPS.scissors) : True,
  93. (RPS.paper, RPS.rock) : True,
  94. (RPS.paper, RPS.scissors) : False,
  95. (RPS.scissors, RPS.rock) : False,
  96. (RPS.scissors, RPS.paper) : True
  97. }
  98. if red_choice == player_choice:
  99. outcome = None # Tie
  100. else:
  101. outcome = cond[(player_choice, red_choice)]
  102. if outcome is True:
  103. await self.bot.say("{} You win {}!"
  104. "".format(red_choice.value, author.mention))
  105. elif outcome is False:
  106. await self.bot.say("{} You lose {}!"
  107. "".format(red_choice.value, author.mention))
  108. else:
  109. await self.bot.say("{} We're square {}!"
  110. "".format(red_choice.value, author.mention))
  111. @commands.command(name="8", aliases=["8ball"])
  112. async def _8ball(self, *, question : str):
  113. """Ask 8 ball a question
  114. Question must end with a question mark.
  115. """
  116. if question.endswith("?") and question != "?":
  117. await self.bot.say("`" + choice(self.ball) + "`")
  118. else:
  119. await self.bot.say("That doesn't look like a question.")
  120. @commands.command(aliases=["sw"], pass_context=True)
  121. async def stopwatch(self, ctx):
  122. """Starts/stops stopwatch"""
  123. author = ctx.message.author
  124. if not author.id in self.stopwatches:
  125. self.stopwatches[author.id] = int(time.perf_counter())
  126. await self.bot.say(author.mention + " Stopwatch started!")
  127. else:
  128. tmp = abs(self.stopwatches[author.id] - int(time.perf_counter()))
  129. tmp = str(datetime.timedelta(seconds=tmp))
  130. await self.bot.say(author.mention + " Stopwatch stopped! Time: **" + tmp + "**")
  131. self.stopwatches.pop(author.id, None)
  132. @commands.command()
  133. async def lmgtfy(self, *, search_terms : str):
  134. """Creates a lmgtfy link"""
  135. search_terms = escape_mass_mentions(search_terms.replace(" ", "+"))
  136. await self.bot.say("https://lmgtfy.com/?q={}".format(search_terms))
  137. @commands.command(no_pm=True, hidden=True)
  138. async def hug(self, user : discord.Member, intensity : int=1):
  139. """Because everyone likes hugs
  140. Up to 10 intensity levels."""
  141. name = italics(user.display_name)
  142. if intensity <= 0:
  143. msg = "(っ˘̩╭╮˘̩)っ" + name
  144. elif intensity <= 3:
  145. msg = "(っ´▽`)っ" + name
  146. elif intensity <= 6:
  147. msg = "╰(*´︶`*)╯" + name
  148. elif intensity <= 9:
  149. msg = "(つ≧▽≦)つ" + name
  150. elif intensity >= 10:
  151. msg = "(づ ̄ ³ ̄)づ {} ⊂(´・ω・`⊂)".format(name)
  152. await self.bot.say(msg)
  153. @commands.command(pass_context=True, no_pm=True)
  154. async def userinfo(self, ctx, *, user: discord.Member=None):
  155. """Shows users's informations"""
  156. author = ctx.message.author
  157. server = ctx.message.server
  158. if not user:
  159. user = author
  160. roles = [x.name for x in user.roles if x.name != "@everyone"]
  161. joined_at = self.fetch_joined_at(user, server)
  162. since_created = (ctx.message.timestamp - user.created_at).days
  163. since_joined = (ctx.message.timestamp - joined_at).days
  164. user_joined = joined_at.strftime("%d %b %Y %H:%M")
  165. user_created = user.created_at.strftime("%d %b %Y %H:%M")
  166. member_number = sorted(server.members,
  167. key=lambda m: m.joined_at).index(user) + 1
  168. created_on = "{}\n({} days ago)".format(user_created, since_created)
  169. joined_on = "{}\n({} days ago)".format(user_joined, since_joined)
  170. game = "Chilling in {} status".format(user.status)
  171. if user.game is None:
  172. pass
  173. elif user.game.url is None:
  174. game = "Playing {}".format(user.game)
  175. else:
  176. game = "Streaming: [{}]({})".format(user.game, user.game.url)
  177. if roles:
  178. roles = sorted(roles, key=[x.name for x in server.role_hierarchy
  179. if x.name != "@everyone"].index)
  180. roles = ", ".join(roles)
  181. else:
  182. roles = "None"
  183. data = discord.Embed(description=game, colour=user.colour)
  184. data.add_field(name="Joined Discord on", value=created_on)
  185. data.add_field(name="Joined this server on", value=joined_on)
  186. data.add_field(name="Roles", value=roles, inline=False)
  187. data.set_footer(text="Member #{} | User ID:{}"
  188. "".format(member_number, user.id))
  189. name = str(user)
  190. name = " ~ ".join((name, user.nick)) if user.nick else name
  191. if user.avatar_url:
  192. data.set_author(name=name, url=user.avatar_url)
  193. data.set_thumbnail(url=user.avatar_url)
  194. else:
  195. data.set_author(name=name)
  196. try:
  197. await self.bot.say(embed=data)
  198. except discord.HTTPException:
  199. await self.bot.say("I need the `Embed links` permission "
  200. "to send this")
  201. @commands.command(pass_context=True, no_pm=True)
  202. async def serverinfo(self, ctx):
  203. """Shows server's informations"""
  204. server = ctx.message.server
  205. online = len([m.status for m in server.members
  206. if m.status != discord.Status.offline])
  207. total_users = len(server.members)
  208. text_channels = len([x for x in server.channels
  209. if x.type == discord.ChannelType.text])
  210. voice_channels = len([x for x in server.channels
  211. if x.type == discord.ChannelType.voice])
  212. passed = (ctx.message.timestamp - server.created_at).days
  213. created_at = ("Since {}. That's over {} days ago!"
  214. "".format(server.created_at.strftime("%d %b %Y %H:%M"),
  215. passed))
  216. colour = ''.join([choice('0123456789ABCDEF') for x in range(6)])
  217. colour = int(colour, 16)
  218. data = discord.Embed(
  219. description=created_at,
  220. colour=discord.Colour(value=colour))
  221. data.add_field(name="Region", value=str(server.region))
  222. data.add_field(name="Users", value="{}/{}".format(online, total_users))
  223. data.add_field(name="Text Channels", value=text_channels)
  224. data.add_field(name="Voice Channels", value=voice_channels)
  225. data.add_field(name="Roles", value=len(server.roles))
  226. data.add_field(name="Owner", value=str(server.owner))
  227. data.set_footer(text="Server ID: " + server.id)
  228. if server.icon_url:
  229. data.set_author(name=server.name, url=server.icon_url)
  230. data.set_thumbnail(url=server.icon_url)
  231. else:
  232. data.set_author(name=server.name)
  233. try:
  234. await self.bot.say(embed=data)
  235. except discord.HTTPException:
  236. await self.bot.say("I need the `Embed links` permission "
  237. "to send this")
  238. @commands.command()
  239. async def urban(self, *, search_terms : str, definition_number : int=1):
  240. """Urban Dictionary search
  241. Definition number must be between 1 and 10"""
  242. def encode(s):
  243. return quote_plus(s, encoding='utf-8', errors='replace')
  244. # definition_number is just there to show up in the help
  245. # all this mess is to avoid forcing double quotes on the user
  246. search_terms = search_terms.split(" ")
  247. try:
  248. if len(search_terms) > 1:
  249. pos = int(search_terms[-1]) - 1
  250. search_terms = search_terms[:-1]
  251. else:
  252. pos = 0
  253. if pos not in range(0, 11): # API only provides the
  254. pos = 0 # top 10 definitions
  255. except ValueError:
  256. pos = 0
  257. search_terms = "+".join([encode(s) for s in search_terms])
  258. url = "http://api.urbandictionary.com/v0/define?term=" + search_terms
  259. try:
  260. async with aiohttp.get(url) as r:
  261. result = await r.json()
  262. if result["list"]:
  263. definition = result['list'][pos]['definition']
  264. example = result['list'][pos]['example']
  265. defs = len(result['list'])
  266. msg = ("**Definition #{} out of {}:\n**{}\n\n"
  267. "**Example:\n**{}".format(pos+1, defs, definition,
  268. example))
  269. msg = pagify(msg, ["\n"])
  270. for page in msg:
  271. await self.bot.say(page)
  272. else:
  273. await self.bot.say("Your search terms gave no results.")
  274. except IndexError:
  275. await self.bot.say("There is no definition #{}".format(pos+1))
  276. except:
  277. await self.bot.say("Error (404 ? I don't want to know). :cat:")
  278. @commands.command(pass_context=True, no_pm=True)
  279. async def poll(self, ctx, *text):
  280. """Starts/stops a poll
  281. Usage example:
  282. poll Is this a poll?;Yes;No;Maybe
  283. poll stop"""
  284. message = ctx.message
  285. if len(text) == 1:
  286. if text[0].lower() == "stop":
  287. await self.endpoll(message)
  288. return
  289. if not self.getPollByChannel(message):
  290. check = " ".join(text).lower()
  291. if "@everyone" in check or "@here" in check:
  292. await self.bot.say("Nice try.")
  293. return
  294. p = NewPoll(message, " ".join(text), self)
  295. if p.valid:
  296. self.poll_sessions.append(p)
  297. await p.start()
  298. else:
  299. await self.bot.say("poll question;option1;option2 (...)")
  300. else:
  301. await self.bot.say("A poll is already ongoing in this channel.")
  302. async def endpoll(self, message):
  303. if self.getPollByChannel(message):
  304. p = self.getPollByChannel(message)
  305. if p.author == message.author.id: # or isMemberAdmin(message)
  306. await self.getPollByChannel(message).endPoll()
  307. else:
  308. await self.bot.say("Only admins and the author can stop the poll.")
  309. else:
  310. await self.bot.say("There's no poll ongoing in this channel.")
  311. def getPollByChannel(self, message):
  312. for poll in self.poll_sessions:
  313. if poll.channel == message.channel:
  314. return poll
  315. return False
  316. async def check_poll_votes(self, message):
  317. if message.author.id != self.bot.user.id:
  318. if self.getPollByChannel(message):
  319. self.getPollByChannel(message).checkAnswer(message)
  320. def fetch_joined_at(self, user, server):
  321. """Just a special case for someone special :^)"""
  322. if user.id == "96130341705637888" and server.id == "133049272517001216":
  323. return datetime.datetime(2016, 1, 10, 6, 8, 4, 443000)
  324. else:
  325. return user.joined_at
  326. class NewPoll():
  327. def __init__(self, message, text, main):
  328. self.channel = message.channel
  329. self.author = message.author.id
  330. self.client = main.bot
  331. self.poll_sessions = main.poll_sessions
  332. msg = [ans.strip() for ans in text.split(";")]
  333. if len(msg) < 2: # Needs at least one question and 2 choices
  334. self.valid = False
  335. return None
  336. else:
  337. self.valid = True
  338. self.already_voted = []
  339. self.question = msg[0]
  340. msg.remove(self.question)
  341. self.answers = {}
  342. i = 1
  343. for answer in msg: # {id : {answer, votes}}
  344. self.answers[i] = {"ANSWER" : answer, "VOTES" : 0}
  345. i += 1
  346. async def start(self):
  347. msg = "**POLL STARTED!**\n\n{}\n\n".format(self.question)
  348. for id, data in self.answers.items():
  349. msg += "{}. *{}*\n".format(id, data["ANSWER"])
  350. msg += "\nType the number to vote!"
  351. await self.client.send_message(self.channel, msg)
  352. await asyncio.sleep(settings["POLL_DURATION"])
  353. if self.valid:
  354. await self.endPoll()
  355. async def endPoll(self):
  356. self.valid = False
  357. msg = "**POLL ENDED!**\n\n{}\n\n".format(self.question)
  358. for data in self.answers.values():
  359. msg += "*{}* - {} votes\n".format(data["ANSWER"], str(data["VOTES"]))
  360. await self.client.send_message(self.channel, msg)
  361. self.poll_sessions.remove(self)
  362. def checkAnswer(self, message):
  363. try:
  364. i = int(message.content)
  365. if i in self.answers.keys():
  366. if message.author.id not in self.already_voted:
  367. data = self.answers[i]
  368. data["VOTES"] += 1
  369. self.answers[i] = data
  370. self.already_voted.append(message.author.id)
  371. except ValueError:
  372. pass
  373. def setup(bot):
  374. n = General(bot)
  375. bot.add_listener(n.check_poll_votes, "on_message")
  376. bot.add_cog(n)