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.

610 lines
27KB

  1. # -*- coding: utf-8 -*-
  2. # MIT License
  3. #
  4. # Copyright (c) 2017-2018 Roxanne Gibson
  5. #
  6. # Permission is hereby granted, free of charge, to any person obtaining a copy
  7. # of this software and associated documentation files (the "Software"), to deal
  8. # in the Software without restriction, including without limitation the rights
  9. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. # copies of the Software, and to permit persons to whom the Software is
  11. # furnished to do so, subject to the following conditions:
  12. #
  13. # The above copyright notice and this permission notice shall be included in all
  14. # copies or substantial portions of the Software.
  15. #
  16. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. # SOFTWARE.
  23. import datetime
  24. import random
  25. import re
  26. import discord
  27. from discord import Embed
  28. from discord.ext import commands
  29. import roxbot
  30. class Fun(commands.Cog):
  31. """The Fun cog provides many commands just meant to be fun. Full of a lot of misc commands as well that might provide a few laughs or be entertaining."""
  32. def __init__(self, bot_client):
  33. self.bot = bot_client
  34. self._8ball_responses = (
  35. "It is certain.",
  36. "It is decidedly so.",
  37. "Without a doubt.",
  38. "Yes - definitely.",
  39. "You may rely on it.",
  40. "As I see it, yes.",
  41. "Most likely.",
  42. "Outlook good.",
  43. "Yes.",
  44. "Signs point to yes.",
  45. "Reply hazy, try again.",
  46. "Ask again later.",
  47. "Better not tell you now.",
  48. "Cannot predict now.",
  49. "Concentrate and ask again.",
  50. "Don't count on it.",
  51. "My reply is no.",
  52. "My sources say no.",
  53. "Outlook not so good.",
  54. "Very doubtful."
  55. )
  56. self.croak = {} # Frogtips cache
  57. self.roxbot_fact_cache = {}
  58. self.kona_cache = {}
  59. @commands.command(aliases=["dice", "die"]) # Terra made this and it just work's but im too scared to clean it up so i hope it doesn't break
  60. async def roll(self, ctx, *, expression: str):
  61. """
  62. Rolls a die using dice expression format.
  63. Usage:
  64. {command_prefix}roll expression
  65. spaces in expression are ignored
  66. Example:
  67. .roll 2d20h1 + 7 # Rolls two D20s takes the highest 1, then adds 7
  68. .help roll #will give brief overview of dice expression format
  69. Dice expression format:
  70. An expression can consist of many sub expressions added together and then a multiplier at the end to indicate how many times the expression should be rolled.
  71. Sub expressions can be of many types:
  72. <number> #add this number to the total
  73. d<sides> #roll a dice with that many sides and add it to the total
  74. <n>d<sides> #roll n dice. each of those dice have <sides> number of sides, sum all the dice and add to the total
  75. add r<number> #reroll any rolls below <number>
  76. add h<number> #only sum the <number> highest rolls rather than all of them
  77. add l<number> #only sum the <number> lowest rolls rather than all of them
  78. x<number> #only use at the end. roll the rest of the expression <number> times(max 10)
  79. Credit: TBTerra#5677
  80. """
  81. max_rolls = 10
  82. max_verbose = 10
  83. max_dice = 1000
  84. response = ''
  85. roll_verbose = True
  86. # sanitise input by removing all spaces, converting to lower case
  87. expression = expression.lower().replace(' ', '')
  88. # check end of expression for a 'x<number>'
  89. parts = expression.split('x', 1)
  90. times = 1
  91. if len(parts) == 2: # if theres a x
  92. try: # try and work out the number after the x
  93. times = int(parts[1])
  94. if times < 1: # cant roll less than once
  95. times = 1
  96. elif times > max_rolls: # dont want to lag the bot/spam the chat by rolling hundreds of times
  97. response += "*Warning:* cannot roll an expression more than {0} times. will roll {0} times rather than {1}.\n".format(max_rolls, times)
  98. times = max_rolls
  99. except ValueError: # probably an input syntax error. safest to just roll once.
  100. times = 1
  101. response += "*Warning:* was unable to resolve how many times this command was meant to run. defaulted to once.\n"
  102. # voodoo magic regex (matches A,dB,AdB,AdBrC and AdBh/lD all at once, and splits them up to be processed)
  103. m = re.findall('(-?)((?:(\d*)d(\d+))|\d+)(r\d+)?([h,l]{1}\d+)?', parts[0])
  104. if not m: # either no arguments, or the expression contained nothing that could be seen as a number or roll
  105. return await ctx.send("Expression missing. If you are unsure of what the format should be, please use `{}help roll`".format(ctx.prefix))
  106. dice = [] # this is the list of all dice sets to be rolled
  107. # each element of the list is a 5 element list, containing
  108. # [0] the sign of the set
  109. # [1] how many dice it has
  110. # [2] how many sides on each dice
  111. # [3] what numbers to re-roll
  112. # [4] and how many to select
  113. for item in m:
  114. temp = [0]*5
  115. temp[0] = 1 if item[0] == '' else -1 # if theres a - at the beginning of the sub expression there needs to be a -1 multiplier applied to the sub expression total
  116. if 'd' in item[1]: # if its a dice/set of dice rather than a number
  117. temp[2] = int(item[3])
  118. if temp[2] == 0: # safety check for things like 2d0 + 1 (0 sided dice)
  119. return await ctx.send("cant roll a zero sided dice")
  120. if item[2] == '': # if its just a dY rather than an XdY
  121. temp[1] = 1
  122. else: # its a XdY type unknown if it has r,h,l modifyers, but they dont matter when sorting out the number and sides of dice
  123. temp[1] = int(item[2])
  124. if temp[1] > max_dice: # if there are an unreasonable number of dice, error out. almost no-one needs to roll 9999d20
  125. return await ctx.send("I can't do that. (Too many dice to roll, max {})".format(max_dice))
  126. if temp[1] > max_verbose and roll_verbose: # if there is a sub expression that involves lots of rolls then turn off verbose mode
  127. roll_verbose = False
  128. response += '*Warning:* large number of rolls detected, will not use verbose rolling.\n'
  129. else: # numbers are stored as N, 1 sided dice
  130. temp[1] = int(item[1])
  131. temp[2] = 1
  132. temp[3] = 0 if item[4] == '' else int(item[4][1:]) # if it has a reroll value use that. if not, reroll on 0
  133. if item[5] == '': # it has no select requirement
  134. temp[4] = 0
  135. else:
  136. if item[5][0] == 'h': # select highest use positive select argument
  137. temp[4] = int(item[5][1:])
  138. else: # select lowest so use negative select argument
  139. temp[4] = -int(item[5][1:])
  140. dice.append(temp)
  141. # at this point dice contains everything needed to do a roll. if you saved dice then you could roll it again without having to re-parse everything (possible roll saving feature in future?)
  142. for i in range(times):
  143. total = 0
  144. if times > 1:
  145. response += 'Roll {}: '.format(i+1)
  146. else:
  147. response += 'Rolled: '
  148. for j, die in enumerate(dice): # for each dice set in the expression
  149. if j != 0 and roll_verbose: # dont need the + before the first element
  150. response += ' + '
  151. if die[0] == -1 and roll_verbose: # all the dice sets will return positive numbers so the sign is set entirely by the sign value (element 0)
  152. response += '-'
  153. if die[2] == 1: # its just a number
  154. if roll_verbose:
  155. response += '{}'.format(die[1])
  156. total += die[0] * die[1]
  157. else: # its a dice or set of dice
  158. if roll_verbose:
  159. response += '('
  160. temp = []
  161. for k in range(die[1]): # for each dice in number of dice
  162. t = [0, '']
  163. t[0] = random.randint(1, die[2]) # roll the dice
  164. t[1] = '{}'.format(t[0])
  165. if t[0] <= die[3]: # if its below or equal to the re-roll value, then re-roll it
  166. t[0] = random.randint(1, die[2])
  167. t[1] += '__{}__'.format(t[0]) # underline the re-roll so its clear thats the one to pay attention to
  168. temp.append(t)
  169. def take_first(ele):
  170. return ele[0]
  171. if die[4] > 0: # if its selecting highest
  172. temp.sort(key=take_first, reverse=True) # sort the rolled dice. highest first
  173. for k, val in enumerate(temp):
  174. if k >= die[4]: # if the position in the sorted list is greater than the number of dice wanted, cross it out, and make it not count towards the total
  175. val[1] = '~~' + val[1] + '~~'
  176. val[0] = 0
  177. if die[4] < 0: # if its selecting lowest
  178. temp.sort(key=take_first)
  179. for k, val in enumerate(temp): # sort the rolled dice. lowest first
  180. if k >= -die[4]: # if the position in the sorted list is greater than the number of dice wanted, cross it out, and make it not count towards the total
  181. val[1] = '~~' + val[1] + '~~'
  182. val[0] = 0
  183. for k, val in enumerate(temp): # loop through all dice rolled and add them to the total. also print them if in verbose mode
  184. if roll_verbose:
  185. response += '{},'.format(val[1])
  186. total += die[0] * val[0]
  187. if roll_verbose:
  188. response = response[:-1] + ')' # clip the trailing ',' and replace it with a ')'
  189. if roll_verbose:
  190. response += ' Totaling: {}'.format(total)
  191. else:
  192. response += ' Total: {}'.format(total)
  193. if i < (times-1):
  194. response += '\n'
  195. return await ctx.send(response)
  196. @roxbot.checks.is_nsfw()
  197. @commands.command()
  198. async def spank(self, ctx, *, user: discord.User = None):
  199. """
  200. Spanks the mentioned user ;)
  201. Examples:
  202. # Two ways to give Roxbot spanks.
  203. ;spank @Roxbot#4170
  204. ;spank Roxbot
  205. """
  206. if not user:
  207. return await ctx.send("You didn't mention someone for me to spank")
  208. return await ctx.send(":peach: :wave: *{} spanks {}*".format(self.bot.user.name, user.name))
  209. @roxbot.checks.is_nsfw()
  210. @commands.command(aliases=["succ"])
  211. async def suck(self, ctx, *, user: discord.User = None):
  212. """
  213. Sucks the mentioned user ;)
  214. Examples:
  215. # Two ways to give Roxbot the succ.
  216. ;suck @Roxbot#4170
  217. ;suck Roxbot
  218. """
  219. if not user:
  220. return await ctx.send("You didn't mention someone for me to suck")
  221. return await ctx.send(":eggplant: :sweat_drops: :tongue: *{} sucks {}*".format(self.bot.user.name, user.name))
  222. @commands.command()
  223. async def hug(self, ctx, *, user: discord.User = None):
  224. """
  225. Hugs the mentioned user :3
  226. Examples:
  227. # Two ways to give Roxbot hugs.
  228. ;hug @Roxbot#4170
  229. ;hug Roxbot
  230. """
  231. if not user:
  232. return await ctx.send("You didn't mention someone for me to hug")
  233. return await ctx.send(":blush: *{} hugs {}*".format(self.bot.user.name, user.name))
  234. @commands.command(aliases=["headpat", "pat"])
  235. async def pet(self, ctx, *, user: discord.User = None):
  236. """
  237. Gives headpats to the mentioned user :3
  238. Examples:
  239. # Two ways to give Roxbot headpats.
  240. ;pet @Roxbot#4170
  241. ;pet Roxbot
  242. """
  243. if not user:
  244. return await ctx.send("You didn't mention someone for me to headpat")
  245. return await ctx.send("Nyaa! :3 *{} gives headpats to {}*".format(self.bot.user.name, user.name))
  246. @commands.command(aliases=["wf", "wr", "husbandorate", "hr", "spousurate", "sr"])
  247. async def waifurate(self, ctx, *waifu: commands.Greedy[discord.Member]):
  248. """
  249. Rates the mentioned waifu(s).
  250. By using the aliases husbandorate or spousurate, it will change how Roxbot addresses those who she has rated.
  251. This may allow multiple people to be rated at once :eyes:
  252. Example:
  253. ;waifurate @user#9999
  254. This command is dedicated to Hannah, who came up with the command. I hope she's out there getting her waifus rated in peace.
  255. """
  256. if ctx.invoked_with in ["hr", "husbandorate"]:
  257. waifu_text = "husbando"
  258. elif ctx.invoked_with in ["sr", "spousurate"]:
  259. waifu_text = "spousu"
  260. else:
  261. waifu_text = "waifu"
  262. if not waifu:
  263. return await ctx.send("You didn't mention anyone for me to rate.", delete_after=10)
  264. elif len(waifu) >= 20:
  265. return await ctx.send("I think you have too many {}s :thinking: I am not even gunna try and rate that.".format(waifu_text))
  266. rating = random.randrange(1, 11)
  267. if rating <= 2:
  268. emoji = ":sob:"
  269. elif rating <= 4:
  270. emoji = ":disappointed:"
  271. elif rating <= 6:
  272. emoji = ":thinking:"
  273. elif rating <= 8:
  274. emoji = ":blush:"
  275. elif rating == 9:
  276. emoji = ":kissing_heart:"
  277. else:
  278. emoji = ":heart_eyes:"
  279. waifu_list = []
  280. for x, w in enumerate(waifu):
  281. if w.name not in waifu_list: # To remove dupes
  282. waifu_list.append(w.name)
  283. if len(waifu_list) > 1:
  284. if len(waifu_list) == 2:
  285. oxford_comma = " and {}"
  286. else:
  287. oxford_comma = ", and {}"
  288. waifus = ", ".join(waifu_list[:-1]).strip(", ") + oxford_comma.format(waifu_list[-1])
  289. return await ctx.send("Oh poly {0} rating? :smirk: Your combined {0} rating for {3} is {1}/10. {2}".format(waifu_text, rating, emoji, waifus))
  290. else:
  291. return await ctx.send("Oh that's your {}? I rate {} a {}/10. {}".format(waifu_text, waifu[0].name, rating, emoji))
  292. @commands.command(aliases=["cf"])
  293. async def coinflip(self, ctx):
  294. """
  295. Flips a magical digital coin!
  296. """
  297. return await ctx.send("The coin landed on {}!".format(random.choice(["heads", "tails"])))
  298. @commands.command(aliases=["ae", "aesthetic"])
  299. async def aesthetics(self, ctx, *, text):
  300. """Converts text to be more a e s t h e t i c
  301. Example:
  302. # Convert "Hello World" to fixed-width text.
  303. ;ae Hello World
  304. """
  305. wide_map = dict((i, i + 0xFEE0) for i in range(0x21, 0x7F)) # Create dict with fixed width equivalents for chars
  306. wide_map[0x20] = 0x3000 # replace space with 'IDEOGRAPHIC SPACE'
  307. converted = str(text).translate(wide_map)
  308. output = await ctx.send(converted)
  309. if isinstance(ctx.channel, discord.TextChannel):
  310. await self.bot.log(
  311. ctx.guild,
  312. "aesthetics",
  313. User=ctx.author,
  314. User_ID=ctx.author.id,
  315. Output_Message_ID=output.id,
  316. Channel=ctx.channel,
  317. Channel_Mention=ctx.channel.mention,
  318. Time=roxbot.datetime.format(ctx.message.created_at)
  319. )
  320. @commands.command(aliases=["ft", "frog"])
  321. async def frogtips(self, ctx):
  322. """RETURNS FROG TIPS FOR HOW TO OPERATE YOUR FROG"""
  323. endpoint = "https://frog.tips/api/1/tips/"
  324. if not self.croak.get("tips"):
  325. self.croak = await roxbot.http.api_request(endpoint)
  326. tip = self.croak["tips"].pop(0)
  327. embed = discord.Embed(title="Frog Tip #{}".format(tip["number"]), description=tip["tip"], colour=roxbot.EmbedColours.frog_green)
  328. embed.set_author(name="HOW TO OPERATE YOUR FROG")
  329. embed.set_footer(text="https://frog.tips")
  330. return await ctx.send(embed=embed)
  331. @commands.command(aliases=["otd"])
  332. async def onthisday(self, ctx):
  333. """Returns a random fact of something that happened today!"""
  334. base_url = "http://numbersapi.com/"
  335. day = datetime.datetime.today().day
  336. month = datetime.datetime.today().month
  337. endpoint = "{}/{}/{}/?json".format(base_url, month, day)
  338. fact = await roxbot.http.api_request(endpoint)
  339. embed = discord.Embed(
  340. title="On This Day...",
  341. author="Day {}".format(fact["number"]),
  342. description=fact.get("text"),
  343. colour=roxbot.EmbedColours.yellow
  344. )
  345. embed.set_footer(text=base_url)
  346. return await ctx.send(embed=embed)
  347. @commands.command(aliases=["nf"])
  348. async def numberfact(self, ctx, number=-54):
  349. """Returns a fact for the positive integer given. A random number is chosen if none is given.
  350. Example:
  351. # Random fact for the number 35
  352. ;nf 35
  353. """
  354. base_url = "http://numbersapi.com/"
  355. if number < 0:
  356. endpoint = "/random/?json"
  357. else:
  358. endpoint = "{}/?json".format(number)
  359. url = base_url + endpoint
  360. fact = await roxbot.http.api_request(url)
  361. if fact["found"]:
  362. output = fact["text"]
  363. else:
  364. output = "There isn't any facts for {}... yet.".format(fact["number"])
  365. embed = discord.Embed(
  366. title="Fact about #{}".format(fact["number"]),
  367. description=output,
  368. colour=roxbot.EmbedColours.yellow
  369. )
  370. embed.set_footer(text=base_url)
  371. return await ctx.send(embed=embed)
  372. @commands.command()
  373. @commands.has_permissions(add_reactions=True)
  374. @commands.bot_has_permissions(add_reactions=True)
  375. async def xkcd(self, ctx, *, query=None):
  376. """
  377. Grabs the image & metadata of the given xkcd comic.
  378. The query can be a comic number, comic title, or latest to get the latest. If not given, Roxbot will return a random comic.
  379. Examples:
  380. # Get random comic
  381. ;xkcd
  382. # Get comic number 666
  383. ;xkcd 666
  384. # Get comic with the title "Silent Hammer"
  385. ;xkcd "Silent Hammer"
  386. # Get latest comic
  387. ;xkcd latest
  388. """
  389. msg = ""
  390. title_query_url = "http://www.explainxkcd.com/wiki/api.php?format=json&action=query&redirects&titles={}"
  391. xkcd_site = "https://xkcd.com/{}"
  392. random_url = "https://c.xkcd.com/random/comic"
  393. async def xkcd_lookup_num(num):
  394. return await roxbot.http.api_request(xkcd_site.format(str(num) + "/info.0.json"))
  395. async def xkcd_lookup_latest():
  396. return await roxbot.http.api_request(xkcd_site.format("/info.0.json"))
  397. async def xkcd_lookup_title(title):
  398. api = await roxbot.http.api_request(title_query_url.format(title.replace(" ", "_")))
  399. # if valid, query.redirects.to is the full & proper page title, including the actual number.
  400. try:
  401. full_page_title = api["query"]["redirects"][0]["to"]
  402. num = full_page_title.split(":")[0]
  403. return await xkcd_lookup_num(num)
  404. except KeyError: # this means query,redirects... didn't exist, done like this to save a massive if statement.
  405. return None
  406. async def random_xkcd():
  407. resp = await roxbot.http.request(random_url, **{"allow_redirects": False})
  408. comic_url = resp.headers["Location"]
  409. num = comic_url.split("/")[-2] # there's always a trailing / so it's the 2nd last segment
  410. return await xkcd_lookup_num(num)
  411. async with ctx.typing():
  412. # Check if passed a valid number
  413. if query in (None, "random"):
  414. # Get a random comic
  415. msg = "Showing a random xkcd"
  416. comic = await random_xkcd()
  417. elif query.isdigit():
  418. # If so, use that to look up
  419. comic = await xkcd_lookup_num(query)
  420. elif query == "latest":
  421. msg = "Showing the latest xkcd"
  422. # Get the latest comic
  423. comic = await xkcd_lookup_latest()
  424. else:
  425. # Removed case-insensitivity here
  426. # because it actually made some titles not show up due to multiple capital letters next to each other.
  427. comic = await xkcd_lookup_title(query)
  428. # If we couldn't find anything, return an error.
  429. if not comic:
  430. return await ctx.send("Couldn't find that comic.")
  431. else:
  432. # Otherwise, show the comic
  433. embed = Embed(title=comic["safe_title"], description="xkcd #{} by Randall Munroe".format(comic["num"]))
  434. embed.set_image(url=comic["img"])
  435. embed.set_footer(text=comic["alt"])
  436. embed.url = xkcd_site.format(comic["num"])
  437. output = await ctx.send(msg, embed=embed)
  438. await self.bot.delete_option(output, self.bot.get_emoji(444410658101002261))
  439. @commands.command(aliases=["za"])
  440. async def zalgo(self, ctx, *, text):
  441. """
  442. Sends text to the nether and returns it back to you ̭҉̭̭ͭi̭͎̭ṋ̭̀҈̭̭̋ ̭͉̭z̭̩̭a̭̭̽ḽ̦̭g̭̞̭o̭̤̭ ̭̭͑f̭̻̭o̭̭͢r̭̭̀m̭̭ͮ
  443. Example:
  444. # Convert "Hello World" to zalgo.
  445. ;zalgo Hello World
  446. """
  447. intensity = 10
  448. zalgo_chars = [*[chr(i) for i in range(0x0300, 0x036F + 1)], *[u'\u0488', u'\u0489']]
  449. zalgoised = []
  450. for letter in text:
  451. zalgoised.append(letter)
  452. zalgo_num = random.randint(0, intensity) + 1
  453. for _ in range(zalgo_num):
  454. zalgoised.append(random.choice(zalgo_chars))
  455. response = random.choice(zalgo_chars).join(zalgoised)
  456. output = await ctx.send(response)
  457. if isinstance(ctx.channel, discord.TextChannel):
  458. await self.bot.log(
  459. ctx.guild,
  460. "zalgo",
  461. User=ctx.author,
  462. User_ID=ctx.author.id,
  463. Output_Message_ID=output.id,
  464. Channel=ctx.channel,
  465. Channel_Mention=ctx.channel.mention,
  466. Time=roxbot.datetime.format(ctx.message.created_at)
  467. )
  468. @commands.command(aliases=["rf", "roxfacts", "roxfact"])
  469. async def roxbotfact(self, ctx):
  470. """Returns a random fact about Roxbot!
  471. Roxbot has her own lore that you can discover through out these facts. Written especially for Roxbot.
  472. """
  473. # Roxbot fact cache
  474. if isinstance(ctx.channel, discord.DMChannel):
  475. cache_id = ctx.author.id
  476. else:
  477. cache_id = ctx.guild.id
  478. # IF ID is not in cache, create cache for ID
  479. if not self.roxbot_fact_cache.get(cache_id, False):
  480. self.roxbot_fact_cache[cache_id] = []
  481. fact = None
  482. fact_index = 0
  483. # Loop in case of duplicate facts
  484. for x in range(10):
  485. fact_index = random.randrange(0, len(roxbot.roxbotfacts.facts))
  486. if fact_index not in self.roxbot_fact_cache:
  487. self.roxbot_fact_cache[cache_id].append(fact_index)
  488. fact = roxbot.roxbotfacts.facts[fact_index]
  489. if len(self.roxbot_fact_cache[cache_id]) >= 10:
  490. self.roxbot_fact_cache[cache_id].pop(0)
  491. break
  492. # This should never happen
  493. if fact is None:
  494. raise commands.CommandError("Cache Failure. Unable to post fact.")
  495. if fact[1] in roxbot.roxbotfacts.contributors:
  496. author = self.bot.get_user(roxbot.roxbotfacts.contributors[fact[1]])
  497. else:
  498. author = fact[1]
  499. if author is None: # Just in case Roxbot doesnt share a server with the author of the fact.
  500. author = fact[1]
  501. embed = discord.Embed(title="Roxbot Fact #{}!".format(fact_index+1), description=fact[0], colour=roxbot.EmbedColours.pink)
  502. embed.set_footer(text="Credit: {}".format(author))
  503. return await ctx.send(embed=embed)
  504. @commands.command(name="8ball", aliases=["magic8ball", "8b"])
  505. async def _8ball(self, ctx, *, question):
  506. """
  507. Asks a magic 8 ball to tell the future via the means of yes and no questions.
  508. Examples:
  509. ;8ball Will this command take off as a good idea?
  510. """
  511. answer = random.choice(self._8ball_responses)
  512. embed = discord.Embed(description=answer, colour=roxbot.EmbedColours.magic_8)
  513. embed.set_author(name="Magic 8-Ball", icon_url="https://twemoji.maxcdn.com/2/72x72/1f3b1.png")
  514. return await ctx.send(embed=embed)
  515. @commands.command(aliases=["konasfw", "konansfw"], hidden=True, enabled=False)
  516. async def konachan(self, ctx, *, tags=""):
  517. # TODO: Add documentation
  518. sfw = bool(ctx.invoked_with == "konasfw" or not ctx.channel.nsfw)
  519. nsfw = bool((ctx.invoked_with == "konansfw" and ctx.channel.nsfw) or ctx.channel.nsfw)
  520. if nsfw:
  521. base_url = "https://konachan.com/post.json/?tags="
  522. else:
  523. base_url = "https://konachan.net/post.json/?tags="
  524. # No banned tags cause konachan bans anything that would break discord tos and hopefully we are good
  525. # its all basically ecchi anyway.
  526. post = await roxbot.utils.danbooru_clone_api_req(
  527. ctx.channel,
  528. base_url,
  529. "",
  530. tags=tags,
  531. cache=self.kona_cache,
  532. sfw=sfw
  533. )
  534. if not post:
  535. return await ctx.send("Nothing was found. *psst, check the tags you gave me.*")
  536. else:
  537. output = await ctx.send(post)
  538. await self.bot.delete_option(output, self.bot.get_emoji(444410658101002261))
  539. def setup(bot_client):
  540. bot_client.add_cog(Fun(bot_client))