async def unban(self, ctx: commands.Context, *discord_users): """Unban user(s)""" if not discord_users: return await util.send_command_help(ctx) if len(discord_users) == 1: user = await util.get_user(ctx, discord_users[0]) if user is None: try: user = await self.bot.fetch_user(int(user)) except (ValueError, discord.NotFound): raise exceptions.CommandError( f"Invalid user or id `{user}`") try: await ctx.guild.unban(user) except discord.errors.Forbidden: raise exceptions.CommandError( f"It seems I don't have the permission to unban **{user}** {user.mention}" ) except discord.errors.NotFound: raise exceptions.CommandWarning( f"Unable to unban. **{user}** is not banned") else: return await util.send_success( ctx, f"Unbanned **{user}** {user.mention}") success = [] failure = [] for discord_user in discord_users: user = await util.get_user(ctx, discord_user) if user is None: try: user = await self.bot.fetch_user(int(discord_user)) except (ValueError, discord.NotFound): failure.append(f"`{discord_user}` Invalid user or id") continue try: await ctx.guild.unban(user) except discord.errors.Forbidden: failure.append(f"`{user}` No permission to unban") except discord.errors.NotFound: failure.append(f"`{user}` is not banned") else: success.append(f"`{user}` Unbanned") await util.send_tasks_result_list( ctx, success, failure, f":memo: Attempting to unban {len(discord_users)} users...")
async def unmute(self, ctx: commands.Context, member: discord.Member): """Unmute user""" mute_role_id = await self.bot.db.execute( """ SELECT mute_role_id FROM guild_settings WHERE guild_id = %s """, ctx.guild.id, one_value=True, ) mute_role = ctx.guild.get_role(mute_role_id) if not mute_role: raise exceptions.CommandWarning( "Mute role for this server has been deleted or is not set, " f"please use `{ctx.prefix}muterole <role>` to set it.") try: await member.remove_roles(mute_role) except discord.errors.Forbidden: raise exceptions.CommandError( f"It seems I don't have permission to unmute {member.mention}") await util.send_success(ctx, f"Unmuted {member.mention}") await self.bot.db.execute( """ DELETE FROM muted_user WHERE guild_id = %s AND user_id = %s """, ctx.guild.id, member.id, ) self.cache_needs_refreshing = True
async def purge(self, ctx: commands.Context, amount: int): """ Delete given amount of messages in the current channel Optionally if users are mentioned, only messages by those users are deleted. Usage: >purge <amount> [mentions...] """ if amount > 100: raise exceptions.CommandWarning( "You cannot delete more than 100 messages at a time.") await ctx.message.delete() if ctx.message.mentions: deleted = [] async for message in ctx.channel.history(limit=100, oldest_first=False): if message.author in ctx.message.mentions: deleted.append(message) if len(deleted) >= amount: break try: await ctx.channel.delete_messages(deleted) except discord.errors.HTTPException: raise exceptions.CommandError( "You can only delete messages that are under 14 days old.") else: deleted = await ctx.channel.purge(limit=amount) await ctx.send( f":put_litter_in_its_place: Deleted `{len(deleted)}` messages.", delete_after=5, )
async def youtube(self, ctx: commands.Context, *, query): """Search for videos from youtube""" url = "https://www.googleapis.com/youtube/v3/search" params = { "key": GOOGLE_API_KEY, "part": "snippet", "type": "video", "maxResults": 25, "q": query, } async with self.bot.session.get(url, params=params) as response: if response.status == 403: raise exceptions.CommandError( "Daily youtube api quota reached.") data = await response.json(loads=orjson.loads) if not data.get("items"): return await ctx.send("No results found!") await util.paginate_list( ctx, [ f"https://youtube.com/watch?v={item['id']['videoId']}" for item in data.get("items") ], use_locking=True, only_author=True, index_entries=True, )
async def executemany(self, statement, params): if await self.wait_for_pool(): async with self.pool.acquire() as conn: async with conn.cursor() as cur: await cur.executemany(statement, params) await conn.commit() return () raise exceptions.CommandError( "Could not connect to the local MariaDB instance!")
async def advice(self, ctx: commands.Context): """Get some life advice""" # for some reason the api is missing content for these ids missing_ids = {0, 22, 48, 67} slip_id = 0 while slip_id in missing_ids: slip_id = random.randint(1, 224) url = f"https://api.adviceslip.com/advice/{slip_id}" async with self.bot.session.get(url) as response: data = orjson.loads(await response.text()) try: await ctx.send(f"*{data['slip']['advice']}*") except KeyError: raise exceptions.CommandError(f"slip_id={slip_id} :: {data}")
async def random(self, ctx: commands.Context, gender=None): """Get a random kpop idol""" gender = get_gender(gender) idol_id_list = await self.bot.db.execute( "SELECT idol_id FROM kpop_idol WHERE gender IN %s", gender, as_list=True, ) if not idol_id_list: raise exceptions.CommandError("Looks like there are no idols in the database!") chosen_id = random.choice(idol_id_list) await self.send_idol(ctx, chosen_id)
async def notification_test(self, ctx: commands.Context, message: discord.Message = None): """ Test if Miso can send you a notification If supplied with a message id, will check if you would have been notified by it. """ if message is None: try: await self.send_notification(ctx.author, message or ctx.message, ["test"], test=True) await ctx.send(":ok_hand: Check your DM") except discord.errors.Forbidden: raise exceptions.CommandWarning( "I was unable to send you a DM! Please check your privacy settings." ) else: if ctx.author not in message.channel.members: raise exceptions.CommandError("You cannot see this message.") keywords = await self.bot.db.execute( "SELECT keyword FROM notification WHERE user_id = %s", ctx.author.id, as_list=True, ) pattern = regex.compile(self.keyword_regex, words=keywords, flags=regex.IGNORECASE) finds = pattern.findall(message.content) if not finds: await ctx.send(":x: This message would not notify you") else: keywords = list(set(finds)) await self.send_notification(ctx.author, message, keywords, test=True) await ctx.send(":ok_hand: Check your DM")
async def execute(self, statement, *params, one_row=False, one_value=False, as_list=False): if await self.wait_for_pool(): async with self.pool.acquire() as conn: async with conn.cursor() as cur: await cur.execute(statement, params) data = await cur.fetchall() if data is None: return () if data: if one_value: return data[0][0] if one_row: return data[0] if as_list: return [row[0] for row in data] return data return () raise exceptions.CommandError( "Could not connect to the local MariaDB instance!")
async def send_idol(self, ctx: commands.Context, idol_id): idol_data = await self.bot.db.execute( """ SELECT idol_id, full_name, stage_name, korean_name, korean_stage_name, date_of_birth, country, group_name, height, weight, gender, image_url FROM kpop_idol WHERE idol_id = %s """, idol_id, one_row=True, ) if not idol_data: raise exceptions.CommandError("There was an error getting idol data.") ( idol_id, full_name, stage_name, korean_name, korean_stage_name, date_of_birth, country, group_name, height, weight, gender, image_url, ) = idol_data if group_name is None: search_term = full_name else: search_term = f"{group_name} {stage_name} kpop" if image_url is None: image_url = await self.google_image_search(search_term) if image_url != "": await self.bot.db.execute( "UPDATE kpop_idol SET image_url = %s WHERE idol_id = %s", image_url, idol_id, ) content = discord.Embed() if gender == "F": content.colour = int("e7586d", 16) elif gender == "M": content.colour = int("226699", 16) content.title = ( self.gender_icon.get(gender, "") + (f"{group_name} " if group_name is not None else "") + stage_name ) today = datetime.date.today() age = ( today.year - date_of_birth.year - ((today.month, today.day) < (date_of_birth.month, date_of_birth.day)) ) content.set_image(url=image_url) content.add_field(name="Full name", value=full_name) content.add_field(name="Korean name", value=f"{korean_stage_name} ({korean_name})") content.add_field( name="Birthday", value=arrow.get(date_of_birth).format("YYYY-MM-DD") + f" (age {age})", ) content.add_field(name="Country", value=country) content.add_field(name="Height", value=f"{height} cm" if height else "unknown") content.add_field(name="Weight", value=f"{weight} kg" if weight else "unknown") await ctx.send(embed=content)
async def mute(self, ctx: commands.Context, member: discord.Member, *, duration=None): """Mute user""" mute_role_id = await self.bot.db.execute( """ SELECT mute_role_id FROM guild_settings WHERE guild_id = %s """, ctx.guild.id, one_value=True, ) mute_role = ctx.guild.get_role(mute_role_id) if not mute_role: raise exceptions.CommandWarning( "Mute role for this server has been deleted or is not set, " f"please use `{ctx.prefix}muterole <role>` to set it.") if member.id == 133311691852218378: return await ctx.send("no.") seconds = None if duration is not None: seconds = util.timefromstring(duration) if seconds is None or seconds == 0: raise exceptions.CommandWarning( f'Invalid mute duration "{duration}"') if seconds < 60: raise exceptions.CommandInfo( "The minimum duration of a mute is **1 minute**") if seconds > 604800: raise exceptions.CommandInfo( "The maximum duration of a mute is **1 week**") try: await member.add_roles(mute_role) except discord.errors.Forbidden: raise exceptions.CommandError( f"It seems I don't have permission to mute {member.mention}") await util.send_success( ctx, f"Muted {member.mention}" + (f" for **{util.stringfromtime(seconds)}**" if seconds is not None else ""), ) if seconds is not None: unmute_on = arrow.now().shift(seconds=seconds).datetime else: unmute_on = None await self.bot.db.execute( """ INSERT INTO muted_user (guild_id, user_id, channel_id, unmute_on) VALUES (%s, %s, %s, %s) ON DUPLICATE KEY UPDATE unmute_on = VALUES(unmute_on) """, ctx.guild.id, member.id, ctx.channel.id, unmute_on, ) self.cache_needs_refreshing = True
async def stock(self, ctx: commands.Context, *, symbol): """ Get price data for the US stock market Example: >stock $TSLA """ FINNHUB_API_QUOTE_URL = "https://finnhub.io/api/v1/quote" FINNHUB_API_PROFILE_URL = "https://finnhub.io/api/v1/stock/profile2" symbol = symbol.upper() params = {"symbol": symbol.strip("$"), "token": FINNHUB_TOKEN} async with self.bot.session.get(FINNHUB_API_QUOTE_URL, params=params) as response: quote_data = await response.json(loads=orjson.loads) error = quote_data.get("error") if error: raise exceptions.CommandError(error) if quote_data["c"] == 0: raise exceptions.CommandWarning("Company not found") async with self.bot.session.get(FINNHUB_API_PROFILE_URL, params=params) as response: company_profile = await response.json(loads=orjson.loads) change = float(quote_data["c"]) - float(quote_data["pc"]) GAINS = change > 0 arrow_emoji = emojis.GREEN_UP if GAINS else emojis.RED_DOWN percentage = ( (float(quote_data["c"]) / float(quote_data["pc"])) - 1) * 100 market_cap = int(company_profile["marketCapitalization"]) def get_money(key): return f"${quote_data[key]}" if company_profile.get("name") is not None: content = discord.Embed( title= f"${company_profile['ticker']} | {company_profile['name']}", ) content.set_thumbnail(url=company_profile.get("logo")) content.set_footer(text=company_profile["exchange"]) else: content = discord.Embed(title=f"${symbol}") content.add_field( name="Change", value= f"{'+' if GAINS else '-'}${abs(change):.2f}{arrow_emoji}\n({percentage:.2f}%)", ) content.add_field(name="Open", value=get_money("o")) content.add_field(name="Previous close", value=get_money("pc")) content.add_field(name="Current price", value=get_money("c")) content.add_field(name="Today's high", value=get_money("h")) content.add_field(name="Today's low", value=get_money("l")) content.add_field(name="Market capitalization", value=f"${market_cap:,}", inline=False) content.colour = discord.Color.green() if GAINS else discord.Color.red( ) content.timestamp = arrow.get(quote_data["t"]).datetime await ctx.send(embed=content)
async def instagram(self, ctx: commands.Context, *links): """Retrieve images from one or more instagram posts""" urls = [] download = False for link in links: if link.lower() in ["-d", "--download"]: download = True else: urls.append(link) if len(urls) > 5: raise exceptions.CommandWarning("Only 5 links at a time please!") for post_url in urls: result = regex.findall("/(p|reel|tv)/(.*?)(/|\\Z)", post_url) if result: shortcode = result[0][1] else: shortcode = post_url.strip("/").split("/")[0] post_url = f"https://www.instagram.com/p/{shortcode}" try: post = await self.ig.extract(shortcode) except instagram.ExpiredCookie: raise exceptions.CommandError( "The Instagram login cookie has expired, please ask my developer to reauthenticate!" ) except instagram.InstagramError as e: raise exceptions.CommandError(e.message) if download: # send as files caption = "\n".join([ f":bust_in_silhouette: **@{post.user.username}**", f":calendar: <t:{post.timestamp}:d>", f":link: <{post_url}>", ]) files = [] # discord normally has 8MB file size limit, but it can be increased in some guilds max_filesize = getattr(ctx.guild, "filesize_limit", 8388608) for n, media in enumerate(post.media, start=1): ext = "mp4" if media.media_type == instagram.MediaType.VIDEO else "jpg" dateformat = arrow.get(post.timestamp).format("YYMMDD") filename = f"{dateformat}-@{post.user.username}-{shortcode}-{n}.{ext}" # The url params are unescaped by aiohttp's built-in yarl # This causes problems with the hash-based request signing that instagram uses # Thankfully you can plug your own yarl.URL into session.get(), with encoded=True async with self.bot.session.get( yarl.URL(media.url, encoded=True)) as response: if (int( response.headers.get("content-length", max_filesize)) > max_filesize): caption += f"\n{media.url}" else: buffer = io.BytesIO(await response.read()) files.append( discord.File(fp=buffer, filename=filename)) # send files to discord await ctx.send(caption, files=files) else: # send as embeds content = discord.Embed(color=random.choice(self.ig_colors)) content.set_author(name=f"@{post.user.username}", icon_url=post.user.avatar_url, url=post_url) for n, media in enumerate(post.media, start=1): if n == len(post.media): content.timestamp = arrow.get(post.timestamp).datetime if media.media_type == instagram.MediaType.VIDEO: await ctx.send(media.url) else: content.set_image(url=media.url) await ctx.send(embed=content) content._author = None # finally delete discord automatic embed try: await ctx.message.edit(suppress=True) except (discord.Forbidden, discord.NotFound): pass