Maki is a Discord bot that does things. Written in Python 3 and relies on Discord.py API implementation.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

349 lignes
9.3KB

  1. # Maki
  2. # ----
  3. # Discord bot by MrDetonia
  4. #
  5. # Copyright 2018 Zac Herd
  6. # Licensed under BSD 3-clause License, see LICENSE.md for more info
  7. # IMPORTS
  8. import asyncio
  9. import os
  10. import sys
  11. import re
  12. import requests
  13. import random
  14. import subprocess
  15. # LOCAL IMPORTS
  16. from common import *
  17. from helpers import *
  18. from secret import lfmkey, steamkey
  19. import markov
  20. # COMMAND IMPLEMENTATIONS
  21. @asyncio.coroutine
  22. def cmd_help(client, msg):
  23. yield from discord_send(client, msg, helptext)
  24. @asyncio.coroutine
  25. def cmd_info(client, msg):
  26. pyver = "{}.{}.{}".format(sys.version_info[0], sys.version_info[1],
  27. sys.version_info[2])
  28. appinfo = yield from client.application_info()
  29. response = "I am **{}**, a Discord bot by **{}** | `{}` | Python `{}` | discord.py `{}`".format(
  30. appinfo.name, appinfo.owner.name, version, pyver, discord.__version__)
  31. yield from discord_send(client, msg, response)
  32. @asyncio.coroutine
  33. def cmd_upskirt(client, msg):
  34. response = "No, don\'t look at my pantsu, baka! <https://github.com/MrDetonia/maki>"
  35. yield from discord_send(client, msg, response)
  36. whoistring = "**{}#{}**: `{}`\n**Account Created:** `{}`"
  37. @asyncio.coroutine
  38. def cmd_whoami(client, msg):
  39. response = whoistring.format(msg.author.name,
  40. msg.author.discriminator, msg.author.id,
  41. strfromdt(msg.author.created_at))
  42. yield from discord_send(client, msg, response)
  43. @asyncio.coroutine
  44. def cmd_whois(client, msg):
  45. tmp = msg.content[7:]
  46. user = msg.server.get_member_named(tmp)
  47. if user == None:
  48. reponse = "I can't find `{}`".format(tmp)
  49. else:
  50. response = whoistring.format(user.name, user.discriminator, user.id,
  51. strfromdt(user.created_at))
  52. yield from discord_send(client, msg, response)
  53. @asyncio.coroutine
  54. def cmd_seen(client, msg):
  55. tmp = msg.content[6:]
  56. user = msg.server.get_member_named(tmp)
  57. if user == None:
  58. reponse = "I can't find `{}`".format(tmp)
  59. elif user.name == "Maki":
  60. reponse = "I'm right here!"
  61. else:
  62. target = msg.server.id + user.id
  63. if target in history and history[target][0] == msg.server.id:
  64. response = "**{}** was last seen saying the following at {}:\n{}".format(
  65. user.name, strfromdt(dtfromts(history[target][1])),
  66. history[target][2])
  67. else:
  68. response = "I haven't seen **{}** speak yet!".format(tmp)
  69. yield from discord_send(client, msg, response)
  70. @asyncio.coroutine
  71. def cmd_say(client, msg):
  72. response = msg.content[5:]
  73. yield from client.delete_message(msg)
  74. yield from discord_send(client, msg, response)
  75. @asyncio.coroutine
  76. def cmd_sayy(client, msg):
  77. response = " ".join(msg.content[6:])
  78. yield from client.delete_message(msg)
  79. yield from discord_send(client, msg, response)
  80. @asyncio.coroutine
  81. def cmd_markov(client, msg):
  82. yield from discord_typing(client, msg)
  83. tmp = msg.content[8:]
  84. target = ""
  85. if tmp == "Maki":
  86. response = "My markovs always say the same thing"
  87. else:
  88. if tmp == "":
  89. target = "{}-{}".format(msg.server.id, msg.author.id)
  90. else:
  91. try:
  92. target = "{}-{}".format(msg.server.id,
  93. msg.server.get_member_named(tmp).id)
  94. except AttributeError:
  95. reponse = "I can't find `{}`".format(tmp)
  96. if target != "":
  97. mfile = "./markovs/" + target
  98. if os.path.isfile(mfile):
  99. mc = markov.Markov(open(mfile))
  100. response = mc.generate_text(random.randint(20, 40))
  101. else:
  102. response = "I haven't seen `{}` speak yet.".format(tmp)
  103. yield from discord_send(client, msg, response)
  104. @asyncio.coroutine
  105. def cmd_roll(client, msg):
  106. tmp = msg.content[6:]
  107. pattern = re.compile("^([0-9]+)d([0-9]+)$")
  108. pattern2 = re.compile("^d([0-9]+)$")
  109. # extract numbers
  110. nums = [int(s) for s in re.findall(r"\d+", tmp)]
  111. if pattern.match(tmp):
  112. numdice = nums[0]
  113. diceval = nums[1]
  114. elif pattern2.match(tmp):
  115. numdice = 1
  116. diceval = nums[0]
  117. else:
  118. response = "Expected format: `[<num>]d<value>`"
  119. yield from discord_send(client, msg, response)
  120. # limit ranges
  121. numdice = clamp(numdice, 1, 10)
  122. diceval = clamp(diceval, 1, 1000)
  123. # roll and sum dice
  124. rolls = []
  125. for i in range(numdice):
  126. rolls.append(random.randint(1, diceval))
  127. rollsum = sum(rolls)
  128. response = "**{} rolled:** {}d{}".format(msg.author.display_name, numdice,
  129. diceval)
  130. if numdice > 1:
  131. response += "\n**Rolls:** `{}`".format(rolls)
  132. response += "\n**Result:** `{}`".format(rollsum)
  133. if rollsum == numdice * diceval:
  134. response += " *(Natural)*"
  135. elif rollsum == numdice:
  136. response += " *(Crit fail)*"
  137. yield from discord_send(client, msg, response)
  138. @asyncio.coroutine
  139. def cmd_qr(client, msg):
  140. tmp = msg.content[4:]
  141. yield from discord_typing(client, msg)
  142. # generate qr code
  143. qr = subprocess.Popen(
  144. "qrencode -t png -o -".split(),
  145. stdin=subprocess.PIPE,
  146. stdout=subprocess.PIPE)
  147. qr.stdin.write(tmp.encode("utf-8"))
  148. qr.stdin.close()
  149. out = subprocess.check_output(
  150. "curl -F upload=@- https://w1r3.net".split(), stdin=qr.stdout)
  151. response = out.decode("utf-8").strip()
  152. yield from discord_send(client, msg, response)
  153. @asyncio.coroutine
  154. def cmd_np(client, msg):
  155. tmp = msg.content[4:]
  156. if tmp == "":
  157. response = lastfm_np(msg.author.name)
  158. else:
  159. response = lastfm_np(tmp)
  160. print("CALLING SEND")
  161. yield from discord_send(client, msg, response)
  162. @asyncio.coroutine
  163. def cmd_steam(client, msg):
  164. tmp = msg.content[7:]
  165. if tmp == "":
  166. response = steamdata(msg.author.name)
  167. else:
  168. response = steamdata(tmp)
  169. yield from discord_send(client, msg, response)
  170. # HELPER FUNCTIONS
  171. # gets now playing information from last.fm
  172. def lastfm_np(username):
  173. # sanitise username
  174. cleanusername = re.sub(r'[^a-zA-Z0-9_-]', '', username, 0)
  175. # fetch JSON from last.fm
  176. payload = {
  177. 'format': 'json',
  178. 'method': 'user.getRecentTracks',
  179. 'user': cleanusername,
  180. 'limit': '1',
  181. 'api_key': lfmkey
  182. }
  183. r = requests.get("http://ws.audioscrobbler.com/2.0/", params=payload)
  184. # read json data
  185. np = r.json()
  186. # check we got a valid response
  187. if 'error' in np:
  188. return "I couldn't get last.fm data for `{}`".format(username)
  189. # get fields
  190. try:
  191. username = np['recenttracks']['@attr']['user']
  192. track = np['recenttracks']['track'][0]
  193. album = track['album']['#text']
  194. artist = track['artist']['#text']
  195. song = track['name']
  196. nowplaying = '@attr' in track
  197. except IndexError:
  198. return "It looks like `{}` hasn't played anything recently.".format(
  199. username)
  200. # grammar
  201. if album != "":
  202. albumtext = "` from the album `{}`".format(album)
  203. else:
  204. albumtext = "`"
  205. if nowplaying == True:
  206. nowplaying = " is listening"
  207. else:
  208. nowplaying = " last listened"
  209. # construct string
  210. return "{}{} to `{}` by `{}{}".format(username, nowplaying, song, artist,
  211. albumtext)
  212. # gets general steam user info from a vanityurl name
  213. def steamdata(vanityname):
  214. # sanitise username
  215. cleanvanityname = re.sub(r'[^a-zA-Z0-9_-]', '', vanityname, 0)
  216. resolveurl = 'http://api.steampowered.com/ISteamUser/ResolveVanityURL/v0001/?key='
  217. dataurl = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key='
  218. # fetch json from steam
  219. try:
  220. idresponse = requests.get(resolveurl + steamkey + '&vanityurl=' +
  221. vanityname).json()['response']
  222. except:
  223. return "I can't connect to Steam"
  224. # check if user was found and extract steamid
  225. if idresponse['success'] is not 1:
  226. return "I couldn't find `{}`".format(vanityname)
  227. else:
  228. steamid = idresponse['steamid']
  229. # fetch steam user info
  230. try:
  231. dataresponse = requests.get(dataurl + steamkey + '&steamids=' +
  232. steamid).json()['response']['players'][0]
  233. except:
  234. return "Can't find info on `{}`".format(vanityname)
  235. personastates = [
  236. 'Offline', 'Online', 'Busy', 'Away', 'Snoozed', 'Looking to trade',
  237. 'Looking to play'
  238. ]
  239. if 'personaname' in dataresponse: namestr = dataresponse['personaname']
  240. else: namestr = ''
  241. if 'personastate' in dataresponse:
  242. statestr = '`' + personastates[dataresponse['personastate']] + '`'
  243. else:
  244. statestr = ''
  245. if 'gameextrainfo' in dataresponse:
  246. gamestr = ' playing `' + dataresponse['gameextrainfo'] + '`'
  247. else:
  248. gamestr = ''
  249. responsetext = [(namestr + ' is ' + statestr + gamestr).replace(' ', ' ')]
  250. return '\n'.join(responsetext)
  251. # COMMAND HANDLING
  252. prefix = "."
  253. commands = {
  254. "help": cmd_help,
  255. "info": cmd_info,
  256. "upskirt": cmd_upskirt,
  257. "whoami": cmd_whoami,
  258. "whois": cmd_whois,
  259. "seen": cmd_seen,
  260. "say": cmd_say,
  261. "sayy": cmd_sayy,
  262. "markov": cmd_markov,
  263. "roll": cmd_roll,
  264. "qr": cmd_qr,
  265. "np": cmd_np,
  266. "steam": cmd_steam,
  267. }