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.

365 lignes
9.9KB

  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 = "./persist/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("^(\d+)d(\d+)([+-]\d+)?$")
  108. pattern2 = re.compile("^d(\d+)([+-]\d+)?$")
  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>[{+-}<modifier>]`"
  119. yield from discord_send(client, msg, response)
  120. # extract modifier, if any
  121. modifier = 0
  122. modpattern = re.compile("^(\d+)?d(\d+)[+-]\d+$")
  123. if modpattern.match(tmp):
  124. modifier = nums[len(nums) - 1]
  125. # negate modifier, if necessary
  126. modpattern = re.compile("^(\d+)?d(\d+)[-]\d+$")
  127. if modpattern.match(tmp):
  128. modifier = -modifier
  129. # limit ranges
  130. numdice = clamp(numdice, 1, 100)
  131. diceval = clamp(diceval, 1, 1000)
  132. # roll and sum dice
  133. rolls = []
  134. for i in range(numdice):
  135. rolls.append(random.randint(1, diceval))
  136. rollsum = sum(rolls) + modifier
  137. # generate response text
  138. response = "**{} rolled:** {}d{}".format(msg.author.display_name, numdice,
  139. diceval)
  140. if modifier > 0:
  141. response += "+{}".format(modifier)
  142. if modifier < 0:
  143. response += "{}".format(modifier)
  144. response += "\n**Rolls:** `{}`".format(rolls)
  145. response += "\n**Result:** `{}`".format(rollsum)
  146. if rollsum - modifier == numdice * diceval:
  147. response += " *(Natural - confirmed `{}`)*".format(
  148. random.randint(1, 20))
  149. elif rollsum - modifier == numdice:
  150. response += " *(Crit fail - confirmed `{}`)*".format(
  151. random.randint(1, 20))
  152. yield from discord_send(client, msg, response)
  153. @asyncio.coroutine
  154. def cmd_qr(client, msg):
  155. tmp = msg.content[4:]
  156. yield from discord_typing(client, msg)
  157. # generate qr code
  158. qr = subprocess.Popen(
  159. "qrencode -t png -o -".split(),
  160. stdin=subprocess.PIPE,
  161. stdout=subprocess.PIPE)
  162. qr.stdin.write(tmp.encode("utf-8"))
  163. qr.stdin.close()
  164. out = subprocess.check_output(
  165. "curl -F upload=@- https://w1r3.net".split(), stdin=qr.stdout)
  166. response = out.decode("utf-8").strip()
  167. yield from discord_send(client, msg, response)
  168. @asyncio.coroutine
  169. def cmd_np(client, msg):
  170. tmp = msg.content[4:]
  171. if tmp == "":
  172. response = lastfm_np(msg.author.name)
  173. else:
  174. response = lastfm_np(tmp)
  175. print("CALLING SEND")
  176. yield from discord_send(client, msg, response)
  177. @asyncio.coroutine
  178. def cmd_steam(client, msg):
  179. tmp = msg.content[7:]
  180. if tmp == "":
  181. response = steamdata(msg.author.name)
  182. else:
  183. response = steamdata(tmp)
  184. yield from discord_send(client, msg, response)
  185. # HELPER FUNCTIONS
  186. # gets now playing information from last.fm
  187. def lastfm_np(username):
  188. # sanitise username
  189. cleanusername = re.sub(r'[^a-zA-Z0-9_-]', '', username, 0)
  190. # fetch JSON from last.fm
  191. payload = {
  192. 'format': 'json',
  193. 'method': 'user.getRecentTracks',
  194. 'user': cleanusername,
  195. 'limit': '1',
  196. 'api_key': lfmkey
  197. }
  198. r = requests.get("http://ws.audioscrobbler.com/2.0/", params=payload)
  199. # read json data
  200. np = r.json()
  201. # check we got a valid response
  202. if 'error' in np:
  203. return "I couldn't get last.fm data for `{}`".format(username)
  204. # get fields
  205. try:
  206. username = np['recenttracks']['@attr']['user']
  207. track = np['recenttracks']['track'][0]
  208. album = track['album']['#text']
  209. artist = track['artist']['#text']
  210. song = track['name']
  211. nowplaying = '@attr' in track
  212. except IndexError:
  213. return "It looks like `{}` hasn't played anything recently.".format(
  214. username)
  215. # grammar
  216. if album != "":
  217. albumtext = "` from the album `{}`".format(album)
  218. else:
  219. albumtext = "`"
  220. if nowplaying == True:
  221. nowplaying = " is listening"
  222. else:
  223. nowplaying = " last listened"
  224. # construct string
  225. return "{}{} to `{}` by `{}{}".format(username, nowplaying, song, artist,
  226. albumtext)
  227. # gets general steam user info from a vanityurl name
  228. def steamdata(vanityname):
  229. # sanitise username
  230. cleanvanityname = re.sub(r'[^a-zA-Z0-9_-]', '', vanityname, 0)
  231. resolveurl = 'http://api.steampowered.com/ISteamUser/ResolveVanityURL/v0001/?key='
  232. dataurl = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key='
  233. # fetch json from steam
  234. try:
  235. idresponse = requests.get(resolveurl + steamkey + '&vanityurl=' +
  236. vanityname).json()['response']
  237. except:
  238. return "I can't connect to Steam"
  239. # check if user was found and extract steamid
  240. if idresponse['success'] is not 1:
  241. return "I couldn't find `{}`".format(vanityname)
  242. else:
  243. steamid = idresponse['steamid']
  244. # fetch steam user info
  245. try:
  246. dataresponse = requests.get(dataurl + steamkey + '&steamids=' +
  247. steamid).json()['response']['players'][0]
  248. except:
  249. return "Can't find info on `{}`".format(vanityname)
  250. personastates = [
  251. 'Offline', 'Online', 'Busy', 'Away', 'Snoozed', 'Looking to trade',
  252. 'Looking to play'
  253. ]
  254. if 'personaname' in dataresponse: namestr = dataresponse['personaname']
  255. else: namestr = ''
  256. if 'personastate' in dataresponse:
  257. statestr = '`' + personastates[dataresponse['personastate']] + '`'
  258. else:
  259. statestr = ''
  260. if 'gameextrainfo' in dataresponse:
  261. gamestr = ' playing `' + dataresponse['gameextrainfo'] + '`'
  262. else:
  263. gamestr = ''
  264. responsetext = [(namestr + ' is ' + statestr + gamestr).replace(' ', ' ')]
  265. return '\n'.join(responsetext)
  266. # COMMAND HANDLING
  267. prefix = "."
  268. commands = {
  269. "help": cmd_help,
  270. "info": cmd_info,
  271. "upskirt": cmd_upskirt,
  272. "whoami": cmd_whoami,
  273. "whois": cmd_whois,
  274. "seen": cmd_seen,
  275. "say": cmd_say,
  276. "sayy": cmd_sayy,
  277. "markov": cmd_markov,
  278. "roll": cmd_roll,
  279. "qr": cmd_qr,
  280. "np": cmd_np,
  281. "steam": cmd_steam,
  282. }