async def get_ship_builder_links( ctx: Context, user_info: entity.EntityInfo, as_embed: bool = settings.USE_EMBEDS) -> Union[List[Embed], List[str]]: user_info, user_ship_info = await get_inspect_ship_for_user( user_info[user.USER_KEY_NAME]) ship_design_id = user_ship_info[SHIP_DESIGN_KEY_NAME] ships_designs_data = await ships_designs_retriever.get_data_dict3() ship_design_info = ships_designs_data[ship_design_id] rooms = '-'.join([ ','.join((ship_room_info['Column'], ship_room_info['Row'], ship_room_info[room.ROOM_DESIGN_KEY_NAME])) for ship_room_info in user_ship_info['Rooms'].values() ]) query_params = f'ship={ship_design_id}&rooms={rooms}' pixel_prestige_link = f'{SHIP_BUILDER_PIXEL_PRESTIGE_BASE_PATH}{query_params}' pixyship_link = f'{SHIP_BUILDER_PIXYSHIP_BASE_PATH}{query_params}' ship_builder_links = [('Pixel Prestige builder', pixel_prestige_link), ('Pixyship builder', pixyship_link)] fields = [] fleet_name = user_info.get(fleet.FLEET_DESCRIPTION_PROPERTY_NAME) if entity.entity_property_has_value(fleet_name): fields.append(('Fleet', escape_markdown(fleet_name), False)) fields += [ ('Trophies', user_info['Trophy'], None), ('Ship', f'{ship_design_info["ShipDesignName"]} (level {ship_design_info["ShipLevel"]})', None), ] post_title = user_info[user.USER_DESCRIPTION_PROPERTY_NAME] if as_embed: miniship_sprite_url = await sprites.get_download_sprite_link( ship_design_info.get('MiniShipSpriteId')) user_pin_sprite_url = await sprites.get_download_sprite_link( user_info.get('IconSpriteId')) colour = utils.discord.get_bot_member_colour(ctx.bot, ctx.guild) result = utils.discord.create_basic_embeds_from_fields( post_title, fields=fields, colour=colour, thumbnail_url=miniship_sprite_url, icon_url=user_pin_sprite_url) for title, link in ship_builder_links: result.append( utils.discord.create_embed(post_title, description=f'[{title}]({link})', colour=colour, thumbnail_url=miniship_sprite_url, icon_url=user_pin_sprite_url)) else: for title, link in ship_builder_links: fields.append((title, f'<{link}>', None)) result = [ f'{key}{entity.DEFAULT_DETAIL_PROPERTY_LONG_SEPARATOR}{value}' for key, value, _ in fields ] result.insert( 0, f'**Ship builder links for {escape_markdown(post_title)}**') return result
async def tag_raw(self, ctx: commands.Context, tag: TagConverter): """Get a tag's raw content.""" tagscript = escape_markdown(tag.tagscript) for page in pagify(tagscript): await ctx.send( page, allowed_mentions=discord.AllowedMentions.none(), )
def __prepare_top_fleets(fleets_data: EntitiesData) -> List[Tuple]: result = [ (position, escape_markdown(fleet_info[fleet.FLEET_DESCRIPTION_PROPERTY_NAME]), fleet_info['Trophy'], fleet_info['Score']) for position, fleet_info in enumerate(fleets_data.values(), start=1) ] return result
async def tag_raw(self, ctx, tag: TagConverter): """Get a tag's raw content.""" await ctx.send( escape_markdown(tag.tagscript[:2000]), allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False), )
async def _basic_search(self, ctx, query: str, category: str) -> None: """Basic search formatting.""" is_nsfw = ctx.channel.is_nsfw() if hasattr(ctx.channel, "is_nsfw") else False async with ctx.typing(): # Searches try: results = await self._search_logic(query, is_nsfw, category) except SafesearchFail: await ctx.send( f":x: Sorry {ctx.author.mention}, your message contains filtered words, I've removed this message." ) return await ctx.message.delete() count = len(results) # Ignore markdown when displaying query_display = utils.escape_mentions(query) query_display = utils.escape_markdown(query_display) # Return if no results if not count: return await ctx.send( f"No results found for `{query_display}`.") # Gets the first entry's data first_title = self.tomd.handle(results[0]["title"]).rstrip("\n") first_url = results[0]["url"] first_desc = self.tomd.handle(results[0]["desc"]).rstrip("\n") # Builds the substring for each of the other result. other_results: List[str] = [] for result in results[1:count]: title = self.tomd.handle(result["title"]).rstrip("\n") url = result["url"] other_results.append(f"**{title}** {url}") other_msg = "\n\n".join(other_results).strip() # Builds message msg = textwrap.dedent(f""" [{first_title}]({first_url}) {first_desc} {other_msg} """) msg = re.sub( r"(https?://(?:www\.)?[-a-zA-Z0-9@:%._+~#=]+\." r"[a-zA-Z0-9()]+\b[-a-zA-Z0-9()@:%_+.~#?&/=]*)", r"<\1>", msg) embed = Embed(title="Search Results", description=msg, color=Color.blue()) embed.set_footer( text= f"Showing {count} results for {query_display} | Powered by HotWired." ) await ctx.send(embed=embed)
async def on_member_remove(self, member): for verify_channel_id in self.verify_channel_list: if member.guild.id != int(verify_channel_id[1]): continue channel = self.bot.get_channel(int(verify_channel_id[0])) if not channel: # something is misconfigured continue if member.bot: await channel.send( f"beep boop boop beep, {member.mention} has left our army of bots" ) return async with self.bot.db.execute( "SELECT osu_id, osu_username FROM users WHERE user_id = ?", [int(member.id)]) as cursor: osu_id = await cursor.fetchone() if osu_id: try: fresh_osu_data = await self.bot.osuweb.get_user_array( osu_id[0]) embed = await osuwebembed.small_user_array( fresh_osu_data, 0xffffff, "User left") member_name = fresh_osu_data["username"] except: print("Connection issues?") embed = None member_name = member.name else: embed = None member_name = member.name escaped_member_name = escape_markdown(member_name) async with self.bot.db.execute( "SELECT message FROM member_goodbye_messages") as cursor: member_goodbye_messages = await cursor.fetchall() goodbye_message = random.choice(member_goodbye_messages) try: about_this_ban = await member.guild.fetch_ban(member) ban_reason_string = about_this_ban.reason await channel.send( f"**{escaped_member_name}** has been banned for the reason of '{ban_reason_string}'", embed=embed) except discord.NotFound: await channel.send(goodbye_message[0] % f"**{escaped_member_name}**", embed=embed) except discord.Forbidden: await channel.send(goodbye_message[0] % f"**{escaped_member_name}**", embed=embed)
async def referee_match_notification(self, guild, tournament, bracket, channel, match_info, delta, match_date): if list(filter(None, [cell.value for cell in match_info.referees])): return if not (delta.days == 0 and delta.seconds >= 20700 and delta.seconds < 21600): return referee_role = tosurnament.get_role(guild.roles, tournament.referee_role_id, "Referee") if referee_role: referee = referee_role.mention else: referee = "Referees" match_date_str = tosurnament.get_pretty_date(tournament, match_date) team1 = escape_markdown(match_info.team1.value) team2 = escape_markdown(match_info.team2.value) message = await self.send_reply( channel, "referee_match_notification", "notification", match_info.match_id.value, team1, team2, referee, match_date_str, ) match_notification = MatchNotification( message_id_hash=message.id, message_id=message.id, tournament_id=tournament.id, bracket_id=bracket.id, match_id=match_info.match_id.value, team1_mention=team1, team2_mention=team2, date_info=match_date_str, notification_type=1, ) self.bot.session.add(match_notification) try: await message.add_reaction("😱") await message.add_reaction("💪") except Exception as e: self.bot.info(str(type(e)) + ": " + str(e))
def create_listen_embed(profile: Dict, item: Dict, date: datetime.datetime = None) -> discord.Embed: """Create the embed when getting a listen (live or recent)""" username = escape_markdown(profile["username"]) def _format_artist(artist: Dict) -> str: name = escape_markdown(artist["name"] or "Unknown Artist") artist_url = artist["source_url"] if artist_url: return f"[{name}]({artist_url})" else: return name artist_list = list(map(_format_artist, item["artists"])) if len(artist_list): artist_list[0] = f"**{artist_list[0]}**" artist_names = ", ".join(artist_list) album = item["album"] album_url = album["source_url"] album_name = escape_markdown(album["name"]) album_art = album["art_url"] or default_album_art embed = discord.Embed(type="rich") embed.set_author( name=f"Listening now on Spotify - {username}" if date is None else f"Recently played - {username}", url=f"{website_url}/user/{profile['username']}", icon_url=profile["profile"]["avatar_small"], ) embed.set_thumbnail(url=album_art) embed.url = item["song"]["source_url"] embed.title = escape_markdown(item["song"]["name"]) description = artist_names if album_name: if album_url is None: description += f"\n\nAlbum: **{album_name}**" else: description += f"\n\nAlbum: **[{album_name}]({album_url})**" embed.description = description embed.timestamp = date or embed.Empty return embed
def sanitation(given_string: str, limit=13): """ Sanitation for discord markdown and inforces a string limit. """ if len(given_string) > 12: given_string = given_string[:13] given_string = escape_markdown(given_string) return given_string
async def tag_global_raw(self, ctx: commands.Context, tag: TagConverter(check_global=True, global_priority=True)): """Get a tag's raw content.""" for page in pagify(tag.tagscript, shorten_by=100): await ctx.send( escape_markdown(page), allowed_mentions=discord.AllowedMentions.none(), )
async def snippet_raw(self, ctx, *, name: str.lower): """ View the raw content of a snippet. """ val = self.bot.snippets.get(name) if val is None: embed = create_not_found_embed(name, self.bot.snippets.keys(), "Snippet") return await ctx.send(embed=embed) return await ctx.send(escape_markdown(escape_mentions(val)).replace("<", "\\<"))
async def avatar(self, ctx, users: commands.Greedy[discord.User]): """Sends avatar link of mentioned users.""" if not users: return await ctx.send("Please mention at least one user.") res = "Avatar URL for:\r\n" for user in users: res += f"- {user.name}: {user.avatar_url_as(format='png')}\r\n" escaped_res = escape_markdown(res) await ctx.send(escaped_res)
async def unbanusr(self, ctx, user: discord.User, reason: str = "No reason provided"): clean_user = escape_markdown( text=escape_mentions(f"{user.name}#{user.discriminator}")) await ctx.message.guild.unban( user, reason=f"{ctx.author} (Unban) - {reason}") await ctx.send(f":eyes: {str(clean_user)} has been unbanned. ")
async def get_team_mention(self, guild, players_spreadsheet, team_name): if not players_spreadsheet: return escape_markdown(team_name) try: team_info = TeamInfo.from_team_name(players_spreadsheet, team_name) if players_spreadsheet.range_team_name: team_role = tosurnament.get_role(guild.roles, None, team_name) if team_role: return team_role.mention user = tosurnament.UserAbstraction.get_from_player_info( self.bot, team_info.get_team_captain(), guild) member = user.get_member(guild) if member: return member.mention return escape_markdown(team_name) except Exception as e: self.bot.info(str(type(e)) + ": " + str(e)) return escape_markdown(team_name)
async def on_message_delete(self, message: discord.Message) -> None: """Log message delete event to message change log.""" channel = message.channel author = message.author # Ignore DMs. if not message.guild: return if message.guild.id != GuildConstant.id or channel.id in GuildConstant.modlog_blacklist: return self._cached_deletes.append(message.id) if message.id in self._ignored[Event.message_delete]: self._ignored[Event.message_delete].remove(message.id) return if author.bot: return author_str = escape_markdown(str(author)) if channel.category: response = ( f"**Author:** {author_str} (`{author.id}`)\n" f"**Channel:** {channel.category}/#{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{message.id}`\n" "\n") else: response = (f"**Author:** {author_str} (`{author.id}`)\n" f"**Channel:** #{channel.name} (`{channel.id}`)\n" f"**Message ID:** `{message.id}`\n" "\n") if message.attachments: # Prepend the message metadata with the number of attachments response = f"**Attachments:** {len(message.attachments)}\n" + response # Shorten the message content if necessary content = message.clean_content remaining_chars = 2040 - len(response) if len(content) > remaining_chars: botlog_url = await self.upload_log(messages=[message], actor_id=message.author.id) ending = f"\n\nMessage truncated, [full message here]({botlog_url})." truncation_point = remaining_chars - len(ending) content = f"{content[:truncation_point]}...{ending}" response += f"{content}" await self.send_log_message(Icons.message_delete, Colours.soft_red, "Message deleted", response, channel_id=Channels.message_log)
async def complaint(self, ctx, *, complaint): channel = self.bot.get_guild(SERVER_ID).get_channel(CONTACT_ID) await ctx.author.send("You send a complaint:") await ctx.author.send(utils.escape_markdown(complaint)) await channel.send(f"COMPLAINT received from {ctx.author.name}:") await channel.send(f"{utils.escape_markdown(complaint)}") await ctx.send("Your complaint was submitted. It will be reviewed with the administrators.", delete_after=10) if not isinstance(ctx.channel, discord.channel.DMChannel): # if a server channel then delete invoking message await ctx.message.delete()
def to_codeblock(content, language="py", replace_existing=True, escape_md=True, new="'''"): if replace_existing: content = content.replace("```", new) if escape_md: content = escape_markdown(content) return f"```{language}\n{content}\n```"
async def binarytotext(self, ctx, *, binary: str): binary = binary.replace(' ', '') binary = binary.replace('\n', '') bytes_list = [binary[i:i + 8] for i in range(0, len(binary), 8)] output = '' for byte in bytes_list: output += chr(int(byte, 2)) await ctx.send(escape_markdown(escape_mentions(output)))
async def contact(self, ctx, *, message): channel = self.bot.get_guild(SERVER_ID).get_channel(CONTACT_ID) await ctx.author.send("You send a message:") await ctx.author.send(utils.escape_markdown(message)) await channel.send(f"CONTACT received from {ctx.author.name}:") await channel.send(f"{utils.escape_markdown(message)}") await ctx.send("Your message was sent. It will be looked at by the moderators. ", delete_after=10) if not isinstance(ctx.channel, discord.channel.DMChannel): # if a server channel then delete invoking message await ctx.message.delete()
async def tag_raw(self, ctx, name: str): """Get a tag's raw content.""" tag = await self.get_stored_tag(ctx, name) if tag: await ctx.send( escape_markdown(tag["tag"]), allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=False), )
async def get_top_posts(self, subreddit: Subreddit, time: str = "all", amount: int = 5) -> Embed: """ Get the top amount of posts for a given subreddit within a specified timeframe. A time of "all" will get posts from all time, "day" will get top daily posts and "week" will get the top weekly posts. The amount should be between 0 and 25 as Reddit's JSON requests only provide 25 posts at most. """ embed = Embed(description="") posts = await self.fetch_posts(route=f"{subreddit}/top", amount=amount, params={"t": time}) if not posts: embed.title = random.choice(ERROR_REPLIES) embed.colour = Colour.red() embed.description = ( "Sorry! We couldn't find any posts from that subreddit. " "If this problem persists, please let us know.") return embed for post in posts: data = post["data"] text = data["selftext"] if text: text = textwrap.shorten(text, width=128, placeholder="...") text += "\n" # Add newline to separate embed info ups = data["ups"] comments = data["num_comments"] author = data["author"] title = textwrap.shorten(data["title"], width=64, placeholder="...") # Normal brackets interfere with Markdown. title = escape_markdown(title).replace("[", "⦋").replace("]", "⦌") link = self.URL + data["permalink"] embed.description += ( f"**[{title}]({link})**\n" f"{text}" f"{Emojis.upvotes} {ups} {Emojis.comments} {comments} {Emojis.user} {author}\n\n" ) embed.colour = Colour.blurple() return embed
async def get_face(self) -> str: db_channel = await self.get_db_channel() if not db_channel.use_emojis: faces = self.get_cosmetics()['faces'] face = escape_markdown(random.choice(faces)) else: faces = self.get_cosmetics()['emojis'] face = random.choice(faces) return face
async def catfact(self, ctx: commands.Context): """Get a random cat fact.""" try: async with ctx.typing(): fact = await self.get_fact() await ctx.send(escape_markdown(fact)) except RequestError: self.logger.exception("Error fetching cat facts.") await ctx.send( "Something went wrong while I was researching cat facts. Sorry. :(" )
async def search_user(self, ctx: Context, user: t.Union[MemberOrUser, discord.Object]) -> None: """Search for infractions by member.""" infraction_list = await self.bot.api_client.get( 'bot/infractions/expanded', params={'user__id': str(user.id)}) if isinstance(user, (discord.Member, discord.User)): user_str = escape_markdown(str(user)) else: if infraction_list: user = infraction_list[0]["user"] user_str = escape_markdown( user["name"]) + f"#{user['discriminator']:04}" else: user_str = str(user.id) embed = discord.Embed( title=f"Infractions for {user_str} ({len(infraction_list)} total)", colour=discord.Colour.orange()) await self.send_infraction_list(ctx, embed, infraction_list)
async def suggestion(self, ctx, *, suggestion): channel = self.bot.get_guild(SERVER_ID).get_channel(CONTACT_ID) await ctx.author.send("You send a suggestion:") await ctx.author.send(utils.escape_markdown(suggestion)) await channel.send(f"SUGGESTION received from {ctx.author.name}:") await channel.send(f"{utils.escape_markdown(suggestion)}") await ctx.send("Your suggestion was submitted. It will be reviewed by the moderators, but we can't respond to " "everyone directly. ", delete_after=10) if not isinstance(ctx.channel, discord.channel.DMChannel): # if a server channel then delete invoking message await ctx.message.delete()
async def question(self, ctx, *, question): channel = self.bot.get_guild(SERVER_ID).get_channel(CONTACT_ID) await ctx.author.send("You send a question") await ctx.author.send(utils.escape_markdown(question)) await channel.send(f"QUESTION received from {ctx.author.name}:") await channel.send(f"{utils.escape_markdown(question)}") await ctx.send("Your question was submitted. It will be looked at by the moderators. If we're asked similar " "questions many times it may be added to some kind of FAQ. ", delete_after=10) if not isinstance(ctx.channel, discord.channel.DMChannel): # if a server channel then delete invoking message await ctx.message.delete()
async def on_member_update(self, before: discord.Member, after: discord.Member) -> None: """Log member update event to user log.""" if before.guild.id != GuildConstant.id: return if before.id in self._ignored[Event.member_update]: self._ignored[Event.member_update].remove(before.id) return changes = self.get_role_diff(before.roles, after.roles) # The regex is a simple way to exclude all sequence and mapping types. diff = DeepDiff(before, after, exclude_regex_paths=r".*\[.*") # A type change seems to always take precedent over a value change. Furthermore, it will # include the value change along with the type change anyway. Therefore, it's OK to # "overwrite" values_changed; in practice there will never even be anything to overwrite. diff_values = { **diff.get("values_changed", {}), **diff.get("type_changes", {}) } for attr, value in diff_values.items(): if not attr: # Not sure why, but it happens. continue attr = attr[5:] # Remove "root." prefix. attr = attr.replace("_", " ").replace(".", " ").capitalize() new = value.get("new_value") old = value.get("old_value") changes.append(f"**{attr}:** `{old}` **→** `{new}`") if not changes: return message = "" for item in sorted(changes): message += f"{Emojis.bullet} {item}\n" member_str = escape_markdown(str(after)) message = f"**{member_str}** (`{after.id}`)\n{message}" await self.send_log_message( icon_url=Icons.user_update, colour=Colour.blurple(), title="Member updated", text=message, thumbnail=after.avatar_url_as(static_format="png"), channel_id=Channels.user_log)
async def is_live_cmd(self, ctx, streamer_name): """ Callback for the isLive command -- input: ctx: interactions.CommandContext streamer_name: str """ stream = socials.get_live_status(streamer_name) if stream is not None: # The streamer is live msg = escape_markdown( f"{streamer_name} is currently live on \"{stream.title}\", go check out on https://www.twitch.tv/{streamer_name} ! {Constants.FUEGO_EMOJI}" ) await ctx.send(msg) else: # The streamer is not live msg = escape_markdown( f"{streamer_name} is not live yet. Follow https://www.twitch.tv/{streamer_name} to stay tuned ! {Constants.FUEGO_EMOJI}" ) await ctx.send(msg)
async def about(self, ctx): version = self.bot.version channel_types = Counter(type(c) for c in self.bot.get_all_channels()) voice = channel_types[discord.channel.VoiceChannel] text = channel_types[discord.channel.TextChannel] te = len([c for c in set(self.bot.walk_commands()) if c.cog_name == "Owner"]) se = len([c for c in set(self.bot.walk_commands()) if c.cog_name == "Staff"]) xd = len([c for c in set(self.bot.walk_commands())]) if await ctx.bot.is_owner(ctx.author): ts = 0 elif await ctx.bot.is_admin(ctx.author): ts = te elif not await ctx.bot.is_admin(ctx.author): ts = te + se totcmd = xd - ts mems = sum([x.member_count for x in self.bot.guilds]) website = 'https://dredd-bot.xyz/' Moksej = self.bot.get_user(345457928972533773) embed = discord.Embed(color=self.bot.settings['colors']['embed_color']) embed.set_author(name=_("About {0}").format(self.bot.user), icon_url=self.bot.user.avatar_url) embed.description = _(""" Dredd is a bot that will help your server with moderation, provide fun to your members, and much more! The bot is currently running on **V{0}** and is currently maintained. **Developer:** [{1}](https://discord.com/users/345457928972533773) **Library & version:** {2} [enhanced discord.py {3}](https://github.com/iDutchy/discord.py) **Last boot:** {4} **Created:** {5} ({6}) **Links:** • [Support server]({7}) • [Bot invite]({8}) • [Website]({17}) **Latest Changes:** {9} **Total:** • Commands: **{10}** • Members: **{11}** • Servers: **{12}** • Channels: {13} **{14}** | {15} **{16}**\n """).format(version, escape_markdown(str(Moksej), as_needed=False), self.bot.settings['emojis']['misc']['python'], discord.__version__, btime.human_timedelta(self.bot.uptime), default.date(self.bot.user.created_at), default.timeago(datetime.utcnow() - self.bot.user.created_at.replace(tzinfo=None)), self.bot.support, self.bot.invite, self.get_last_commits(), f'{totcmd:,}', f'{mems:,}', f'{len(self.bot.guilds):,}', self.bot.settings['emojis']['logs']['unlock'], f'{text:,}', self.bot.settings['emojis']['logs']['vcunlock'], f'{voice:,}', website) embed.set_image( url=self.bot.settings['banners']['default']) await ctx.send(embed=embed)
async def profile_command(self, ctx: commands.Context, user: Union[discord.User, str] = None): await ctx.trigger_typing() profile = await self.get_wavyfm_user(ctx, user or ctx.author) if not profile: return try: stats = self.client.users.by_uri( profile["uri"]).get_history_stats() total_listens = "{:,}".format(stats["total_listens"]) total_artists = "{:,}".format(stats["total_artists"]) username = escape_markdown(profile["username"]) url = profile["profile"]["url"] bio = escape_markdown( escape_markdown(profile["profile"].get("biography") or "")) avatar = profile["profile"]["avatar"] embed = discord.Embed(type="rich") embed.set_author(name=f"{username} on wavy.fm", url=url) embed.title = username embed.url = url embed.description = bio embed.set_thumbnail(url=avatar) embed.add_field(name="Listens", value=total_listens, inline=True) embed.add_field(name="Artists", value=total_artists, inline=True) await ctx.send(embed=embed) except wavyfm.WavyException: logging.error("Error while getting profile", exc_info=True) await ctx.reply( "Sorry, an internal error occurred. Please try again later.") except ReadTimeout: logging.error("Timeout while getting profile", exc_info=True) await ctx.reply( "Sorry, this command took too long to execute. Please try again later." )