async def chart(self, ctx, coin, pair="USDT", interval="1h", limit: int = 50): """Generates candlestick chart for a given cryptocurrency pair.""" if interval not in self.binance_intervals: raise exceptions.Error("Invalid interval.") if limit > 100: raise exceptions.Error("Limit must be 100 or less.") symbol = (coin + pair).upper() async with aiohttp.ClientSession() as session: url = "https://api.binance.com/api/v3/klines" params = {"symbol": symbol, "interval": interval, "limit": limit} async with session.get(url, params=params) as response: data = await response.json() if isinstance(data, dict): raise exceptions.Error(data.get("msg")) candle_data = [] for ticker in data: candle_data.append(str(ticker[:5])) current_price = Decimal(data[-1][4]).normalize() replacements = { "HEIGHT": 512, "TITLE": f"{coin.upper()} / {pair.upper()} | {interval} | {current_price:,f}", "DATA": ",".join(candle_data), } def dictsub(m): return str(replacements[m.group().strip("$")]) formatted_html = re.sub(r"\$(\S*)\$", dictsub, self.candlestick_chart_html) async with aiohttp.ClientSession() as session: data = { "html": formatted_html, "width": 720, "height": 512, "imageFormat": "png", } async with session.post("http://localhost:3000/html", data=data) as response: with open("downloads/candlestick.png", "wb") as f: while True: block = await response.content.read(1024) if not block: break f.write(block) with open("downloads/candlestick.png", "rb") as f: await ctx.send(file=discord.File(f))
async def fastban(self, ctx, *discord_users): """Ban user(s) without confirmation box.""" if not discord_users: return await util.send_command_help(ctx) 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): raise exceptions.Warning( f"Invalid user or id `{discord_user}`") if user.id == 133311691852218378: return await ctx.send("no.") try: await ctx.guild.ban(user, delete_message_days=0) except discord.errors.Forbidden: raise exceptions.Error( f"It seems I don't have the permission to ban **{user}**") else: await ctx.send(embed=discord.Embed( description=f":hammer: Banned `{user}`", color=int("f4900c", 16)))
async def unmute(self, ctx, 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.Warning( "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.Error( 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, amount: int): """ Delete some amount of messages in current channel. Optionally if users are mentioned, only messages by those users are deleted. Usage: >purge <amount> [mentions...] """ if amount > 100: raise exceptions.Warning( "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.Error( "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, *, query): """Search 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 aiohttp.ClientSession() as session: async with session.get(url, params=params) as response: if response.status == 403: raise exceptions.Error("Daily youtube api quota reached.") data = await response.json() 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 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 () else: if data: if one_value: return data[0][0] elif one_row: return data[0] elif as_list: return [row[0] for row in data] else: return data else: return () else: raise exceptions.Error( "Could not connect to the local MariaDB instance!")
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.Error( "Could not connect to the local MariaDB instance!")
async def random(self, ctx, 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.Error("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 price(self, ctx, coin, pair="USDT"): """See the current price and 25h statistics of cryptocurrency pair.""" symbol = (coin + pair).upper() url = "https://api.binance.com/api/v3/ticker/24hr" params = {"symbol": symbol} async with aiohttp.ClientSession() as session: async with session.get(url, params=params) as response: data = await response.json() error = data.get("msg") if error: raise exceptions.Error(error) content = discord.Embed(color=int("f3ba2e", 16)) content.set_author( name=f"{data.get('symbol')} | Binance", icon_url=self.binance_icon, url=f"https://www.binance.com/en/trade/{data.get('symbol')}", ) content.add_field( name="Current price", value=f"{Decimal(data.get('lastPrice')).normalize():,f}" ) content.add_field( name="24h High", value=f"{Decimal(data.get('highPrice')).normalize():,f}" ) content.add_field(name="24h Low", value=f"{Decimal(data.get('lowPrice')).normalize():,f}") pricechange = Decimal(data.get("priceChange")).normalize() if pricechange > 0: direction = emojis.GREEN_UP elif pricechange < 0: direction = emojis.RED_DOWN else: direction = ":white_small_square:" content.add_field( name="24h Change", value=f"{direction} {pricechange:,f} ({Decimal(data.get('priceChangePercent')).normalize():.2f}%)", ) content.add_field( name=f"24h Volume ({coin.upper()})", value=f"{Decimal(data.get('volume')).normalize():,.2f}", ) content.add_field( name=f"24h Volume ({pair.upper()})", value=f"{Decimal(data.get('quoteVolume')).normalize():,.2f}", ) await ctx.send(embed=content)
async def test(self, ctx, 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.Warning( "I was unable to send you a DM! Please check your privacy settings." ) else: if ctx.author not in message.channel.members: raise exceptions.Error("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 ban(self, ctx, *discord_users): """Ban user(s).""" if not discord_users: return await util.send_command_help(ctx) for discord_user in discord_users: user = await util.get_member(ctx, discord_user) if user is None: try: user = await self.bot.fetch_user(int(discord_user)) except (ValueError, discord.NotFound): raise exceptions.Warning( f"Invalid user or id `{discord_user}`") if user.id == 133311691852218378: return await ctx.send("no.") # confirmation dialog for guild members if isinstance(user, discord.Member): await self.send_ban_confirmation(ctx, user) elif isinstance(user, discord.User): try: await ctx.guild.ban(user) except discord.errors.Forbidden: raise exceptions.Error( f"It seems I don't have the permission to ban **{user}**" ) else: await ctx.send(embed=discord.Embed( description=f":hammer: Banned `{user}`", color=int("f4900c", 16))) else: raise exceptions.Warning( f"There was an error finding discord user `{discord_user}`" )
async def mute(self, ctx, 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.Warning( "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.Warning(f'Invalid mute duration "{duration}"') if seconds < 60: raise exceptions.Info( "The minimum duration of a mute is **1 minute**") if seconds > 604800: raise exceptions.Info( "The maximum duration of a mute is **1 week**") try: await member.add_roles(mute_role) except discord.errors.Forbidden: raise exceptions.Error( 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 send_idol(self, ctx, idol_id): idol_data = await self.bot.db.execute( """ SELECT full_name, stage_name, korean_name, korean_stage_name, date_of_birth, country, birthplace, group_name, height, weight, gender FROM kpop_idol WHERE idol_id = %s """, idol_id, one_row=True, ) if not idol_data: raise exceptions.Error("There was an error getting idol data.") ( full_name, stage_name, korean_name, korean_stage_name, date_of_birth, country, birthplace, group_name, height, weight, gender, ) = idol_data if group_name is None: search_term = full_name else: search_term = f"{group_name} {stage_name}" image = await self.google_image_search(search_term) content = discord.Embed() if gender == "F": content.color = int("e7586d", 16) elif gender == "M": content.color = 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) 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 instagram(self, ctx, **options): """Get all the images from one or more instagram posts.""" async with aiohttp.ClientSession() as session: for url in options["urls"]: result = regex.findall("/p/(.*?)(/|\\Z)", url) if result: url = f"https://www.instagram.com/p/{result[0][0]}" else: url = f"https://www.instagram.com/p/{url.strip('/').split('/')[0]}" headers = { "User-Agent": self.user_agents.get_random_user_agent(), "X-IG-App-ID": "936619743392459", "Cookie": "ig_cb=2; ig_did=CD9C9CD6-A65D-4CA0-A810-DA55EF09C143; csrftoken=du0pHNPkxq5hpQTiFhWVefvZ01aneHYa; mid=X67owAAEAAHD89ozXg9Fx48IPef0; ds_user_id=5951951432; sessionid=5951951432%3A0eHKUlAtGE83k1%3A13; fbm_124024574287414=base_domain=.instagram.com; shbid=10480; shbts=1618475928.6377892; rur=RVA", } post_id = url.split("/")[-1] newurl = "https://www.instagram.com/graphql/query/" params = { "query_hash": "505f2f2dfcfce5b99cb7ac4155cbf299", "variables": '{"shortcode":"' + post_id + '","include_reel":false,"include_logged_out":true}', } async with session.get(newurl, params=params, headers=headers, proxy=ROTATING_PROXY_URL) as response: try: data = await response.json() except aiohttp.ContentTypeError: raise exceptions.Error( "Instagram is banning me from accessing their page. Please try again later." ) data = data["data"]["shortcode_media"] if data is None: await ctx.send(f":warning: Invalid instagram URL `{url}`") continue medias = [] try: for x in data["edge_sidecar_to_children"]["edges"]: medias.append(x["node"]) except KeyError: medias.append(data) avatar_url = data["owner"]["profile_pic_url"] username = data["owner"]["username"] content = discord.Embed(color=random.choice(self.ig_colors)) content.set_author(name=f"@{username}", icon_url=avatar_url, url=url) if not medias: await ctx.send( f":warning: Could not find any media from `{url}`") continue if options["download"]: # send as files async with aiohttp.ClientSession() as session: await ctx.send(f"<{url}>") timestamp = arrow.get( data["taken_at_timestamp"]).format("YYMMDD") for n, file in enumerate(medias, start=1): if file.get("is_video"): media_url = file.get("video_url") extension = "mp4" else: media_url = file.get("display_url") extension = "jpg" filename = f"{timestamp}-@{username}-{post_id}-{n}.{extension}" async with session.get(media_url) as response: with open(filename, "wb") as f: while True: block = await response.content.read( 1024) if not block: break f.write(block) with open(filename, "rb") as f: await ctx.send(file=discord.File(f)) os.remove(filename) else: # send as embeds for medianode in medias: if medianode.get("is_video"): await ctx.send(embed=content) await ctx.send(medianode.get("video_url")) else: content.set_image(url=medianode.get("display_url")) await ctx.send(embed=content) content.description = None content._author = None try: # delete discord automatic embed await ctx.message.edit(suppress=True) except discord.Forbidden: pass
async def instagram(self, ctx, **options): """Get all the images from one or more instagram posts.""" async with aiohttp.ClientSession() as session: for url in options["urls"]: result = regex.findall("/p/(.*?)(/|\\Z)", url) if result: url = f"https://www.instagram.com/p/{result[0][0]}" else: url = f"https://www.instagram.com/p/{url.strip('/').split('/')[0]}" headers = { "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0", } post_id = url.split("/")[-1] newurl = "https://www.instagram.com/graphql/query/" params = { "query_hash": "505f2f2dfcfce5b99cb7ac4155cbf299", "variables": '{"shortcode":"' + post_id + '","include_reel":false,"include_logged_out":true}', } async with session.get(newurl, params=params, headers=headers, proxy=ROTATING_PROXY_URL) as response: try: data = await response.json() except aiohttp.ContentTypeError: raise exceptions.Error( "This proxy IP address has been banned by Instagram. Try again later." ) data = data["data"]["shortcode_media"] if data is None: await ctx.send(f":warning: Invalid instagram URL `{url}`") continue medias = [] try: for x in data["edge_sidecar_to_children"]["edges"]: medias.append(x["node"]) except KeyError: medias.append(data) avatar_url = data["owner"]["profile_pic_url"] username = data["owner"]["username"] content = discord.Embed(color=random.choice(self.ig_colors)) content.set_author(name=f"@{username}", icon_url=avatar_url, url=url) if not medias: await ctx.send( f":warning: Could not find any media from `{url}`") continue if options["download"]: # send as files async with aiohttp.ClientSession() as session: await ctx.send(f"<{url}>") timestamp = arrow.get( data["taken_at_timestamp"]).format("YYMMDD") for n, file in enumerate(medias, start=1): if file.get("is_video"): media_url = file.get("video_url") extension = "mp4" else: media_url = file.get("display_url") extension = "jpg" filename = f"{timestamp}-@{username}-{post_id}-{n}.{extension}" async with session.get(media_url) as response: with open(filename, "wb") as f: while True: block = await response.content.read( 1024) if not block: break f.write(block) with open(filename, "rb") as f: await ctx.send(file=discord.File(f)) os.remove(filename) else: # send as embeds for medianode in medias: if medianode.get("is_video"): await ctx.send(embed=content) await ctx.send(medianode.get("video_url")) else: content.set_image(url=medianode.get("display_url")) await ctx.send(embed=content) content.description = None content._author = None try: # delete discord automatic embed await ctx.message.edit(suppress=True) except discord.Forbidden: pass