def entrypoint(self): # Prepare signal handler signal.signal(signal.SIGTERM, self.shutdown) signal.signal(signal.SIGINT, self.shutdown) if os.environ.get('DISCORD_TOKEN'): LOG.info( "Loading API key from environment variable DISCORD_TOKEN.") elif self.config.get('apiKey') is None: if HuskyUtils.is_docker(): LOG.critical( "Please specify the API key by using the DISCORD_TOKEN environment varaible when using " "Docker.") exit(1) if self.__daemon_mode: LOG.critical( "The bot does not have an API key assigned to it. Please either specify a key in the env " "variable DISCORD_TOKEN, add a key to the config, or run this bot in non-daemon mode." ) exit(1) else: print( "The bot does not have an API key defined. Please enter one below..." ) key = input("Discord API Key? ") self.config.set('apiKey', key) print("The API key has been set!") LOG.info("The bot's log path is: {}".format(self.__log_path)) if HuskyUtils.is_docker(): LOG.info( "The bot has detected it is running in Docker. Some internal systems have been changed in order " "to better suit the container land.") if self.__daemon_mode: LOG.info( "The bot is currently loaded in Daemon Mode. In Daemon Mode, certain functionalities are " "slightly altered to better utilize the headless environment.") if self.developer_mode: LOG.info( "The bot is running in DEVELOPER MODE! Some features may behave in unexpected ways or may " "otherwise break. Some bot safety checks are disabled with this mode on." ) self.run(os.environ.get('DISCORD_TOKEN') or self.config['apiKey']) if self.config.get("restartReason") is not None: print("READY FOR RESTART!") os.execl(sys.executable, *([sys.executable] + sys.argv))
async def about(self, ctx: discord.ext.commands.Context): """ This command returns a quick summary of this bot and its current state. """ repo = git.Repo(search_parent_directories=True) sha = repo.head.object.hexsha debug_str = '| Developer Build' if self.bot.developer_mode else '' embed = discord.Embed( title=f"About {self.bot.user.name} {debug_str}", description="This bot (known in code as **HuskyBot**) is a custom-made Discord moderation and management " "utility bot initially for [DIY Tech](https://discord.gg/diytech). It's an implementation of " "the WolfBot platform for Discord, built on the popular " "[discord.py rewrite](https://github.com/Rapptz/discord.py). It features seamless integration " "with any workflow, and some of the most powerful plugin management and integration features " "available in any commercial Discord bot. HuskyBot is built for speed and reliability for " "guilds of any size, as well as easy and intuitive administration.", color=Colors.INFO ) embed.add_field(name="Authors", value="[KazWolfe](https://github.com/KazWolfe), " "[Clover](https://github.com/cclover550)", inline=False) embed.add_field(name="Bot Version", value=f"[`{sha[:8]}`]({GIT_URL}/commit/{sha})", inline=True) embed.add_field(name="Library Version", value=f"discord.py {discord.__version__}", inline=True) embed.add_field(name="Python Version", value=f"Python {platform.python_version()}") embed.add_field(name="Current Host", value=f"`{socket.gethostname()}`", inline=True) platform_type = HuskyUtils.get_platform_type() if platform_type: pl_pretty_index = {"compose": "Docker (using Compose)", "systemd": "Linux (systemd)"} embed.add_field(name="Platform", value=pl_pretty_index.get(platform_type, platform_type), inline=True) init_time = self._session_store.get('initTime') if init_time: uptime = datetime.datetime.now() - init_time embed.add_field( name="Uptime", value=HuskyUtils.get_delta_timestr(uptime), inline=True ) embed.set_thumbnail(url=ctx.bot.user.avatar_url) embed.set_footer(text=f"(c) {datetime.datetime.now().year}, KazWolfe | Andwooooooo!", icon_url="https://avatars3.githubusercontent.com/u/5192145") await ctx.send(embed=embed)
async def process_message(self, message: discord.Message, context: str): # config loading as_config: dict = self._config.get("antiSpam", {}) global_config: dict = as_config.get("__global__", {}) exemption_config: list = global_config.get("exemptedRoles", []) if not HuskyUtils.should_process_message(message): return if exemption_config and HuskyUtils.member_has_any_role( message.author, exemption_config): return for module in self.__modules__.values(): asyncio.ensure_future(module.process_message(message, context))
def __initialize_logger(self): # Build the to-file logger HuskyBot uses. file_log_handler = HuskyUtils.CompressingRotatingFileHandler( self.session_store.get('logPath'), maxBytes=(1024**2) * 5, backupCount=5, encoding='utf-8') file_log_handler.setFormatter( logging.Formatter( "%(asctime)s [%(levelname)s] %(name)s: %(message)s")) # Build the to-stream logger stream_log_handler = logging.StreamHandler(sys.stdout) if self.__daemon_mode: stream_log_handler.setFormatter( logging.Formatter("[%(levelname)s] %(name)s: %(message)s")) logging.basicConfig( level=logging.WARNING, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", handlers=[file_log_handler, stream_log_handler]) bot_logger = logging.getLogger("HuskyBot") bot_logger.setLevel(logging.INFO) if self.developer_mode: bot_logger.setLevel(logging.DEBUG) LOG.setLevel(logging.DEBUG) return bot_logger
async def calculate_entropy(self, message: discord.Message): if message.content is None or message.content == "": return # run on about 20% of messages if random.randint(1, 5) != 3: return entropy = HuskyUtils.calculate_str_entropy(message.content) clean_content = message.content.replace('\n', ' // ') s = clean_content if len( clean_content) < 20 else f"{clean_content[:20]}..." LOG.info( f"[EntropyCalc] Message {message.id} in #{message.channel.name} ({s}) has " f"length={len(message.content)} and entropy {entropy}.") with open("logs/entropy.log", 'a') as f: f.write( json.dumps({ "text": message.content, "entropy": entropy, "length": len(message.content) }) + "\n")
async def get_response(self, ctx: commands.Context, *, trigger: str): responses = self._config.get("responses", {}) try: response = responses[trigger.lower()] except KeyError: await ctx.send(embed=discord.Embed( title="Response Manager", description=f"The trigger `{trigger}` does not exist.", color=Colors.DANGER)) return embed = discord.Embed(color=Colors.INFO, title=f"Response Data for {trigger.lower()}") for k in response.keys(): v = response[k] if k == "response" and response.get('isEmbed', False): v = "< Embedded JSON >" embed.add_field(name=k, value=HuskyUtils.trim_string(str(v), 1000), inline=True) await ctx.send(embed=embed)
async def user_unban_logger(self, guild: discord.Guild, user: discord.User): if "userBan" not in self._config.get("loggers", {}).keys(): return logger_ignores: dict = self._session_store.get('loggerIgnores', {}) ignored_bans = logger_ignores.setdefault('ban', []) if user.id in ignored_bans: return alert_channel = self._config.get('specialChannels', {}).get(ChannelKeys.STAFF_LOG.value, None) if alert_channel is None: return alert_channel = self.bot.get_channel(alert_channel) embed = discord.Embed( title=Emojis.UNBAN + " User unbanned", description=f"{user} was unbanned from the guild.", color=Colors.PRIMARY) embed.set_thumbnail(url=user.avatar_url) embed.add_field(name="User ID", value=user.id) embed.add_field(name="Unban Timestamp", value=HuskyUtils.get_timestamp()) LOG.info(f"User {user} was unbanned from {guild.name}.") await alert_channel.send(embed=embed)
async def on_raw_reaction_clear(self, event: discord.RawReactionClearEvent): channel = self.bot.get_channel( event.channel_id) # type: discord.TextChannel message = await channel.fetch_message(event.message_id ) # type: discord.Message channel_config = self._config.get('reactToPin', {}).get( str(channel.id)) # type: dict if not HuskyUtils.should_process_message(message): return if message.id in channel_config.get('permanent', []): LOG.info( "Reactions were cleared on a permanently pinned message, ignoring." ) return # Check if the message is pinned if message not in await channel.pins(): LOG.debug("Can't unpin a message that isn't currently pinned.") return await message.unpin()
async def convert(self, ctx: commands.Context, argument: str): if argument in ["0", "perm", "permanent", "inf", "infinite", "-"]: return None try: return HuskyUtils.get_timedelta_from_string(argument) except ValueError as e: raise commands.BadArgument(str(e))
async def user_rename_logger(self, before: discord.Member, after: discord.Member): if "userRename" not in self._config.get("loggers", {}).keys(): return logger_ignores: dict = self._session_store.get('loggerIgnores', {}) ignored_nicks = logger_ignores.setdefault('nickname', []) alert_channel = self._config.get('specialChannels', {}).get(ChannelKeys.USER_LOG.value, None) if alert_channel is None: return alert_channel = self.bot.get_channel(alert_channel) if before.nick == after.nick and before.name == after.name: return if before.nick != after.nick: update_type = 'nickname' old_val = before.nick new_val = after.nick if before.id in ignored_nicks: return elif before.name != after.name: update_type = 'username' old_val = before.name new_val = after.name else: return embed = discord.Embed( description= f"User's {update_type} has been changed. Information below.", color=Colors.INFO) embed.add_field(name=f"Old {update_type.capitalize()}", value=old_val, inline=True) embed.add_field(name=f"New {update_type.capitalize()}", value=new_val, inline=True) embed.add_field(name="Display Name", value=HuskyUtils.escape_markdown(after.display_name), inline=True) embed.add_field(name="User ID", value=after.id, inline=True) embed.set_author(name=f"{after}'s {update_type} has changed!", icon_url=after.avatar_url) await alert_channel.send(embed=embed)
async def user_filter(self, message: discord.Message): flag_users = self._config.get("flaggedUsers", []) alert_channel = self._config.get('specialChannels', {}).get( ChannelKeys.STAFF_ALERTS.value, None) if alert_channel is not None: alert_channel: discord.TextChannel = self.bot.get_channel( alert_channel) if not HuskyUtils.should_process_message(message): return if message.author.id in flag_users: embed = discord.Embed( title=Emojis.RED_FLAG + " Message autoflag raised!", description= f"A message from flagged user {message.author.mention} was detected and has been raised to " f"staff. Please investigate.", color=Colors.WARNING) embed.add_field(name="Message Content", value=HuskyUtils.trim_string( message.content, 1000), inline=False) embed.add_field(name="Message ID", value=message.id, inline=True) embed.add_field(name="Channel", value=message.channel.mention, inline=True) embed.add_field(name="User ID", value=message.author.id, inline=True) embed.add_field(name="Message Timestamp", value=message.created_at.strftime(DATETIME_FORMAT), inline=True) if alert_channel is not None: await alert_channel.send(embed=embed, delete_after=self._delete_time) LOG.info("Got user flagged message (from %s in %s): %s", message.author, message.channel, message.content)
def undersized_gif_check(file) -> bool: # Try to see if this gif is too big for its size (over 5000px^2, but less than 1mb) (width, height) = HuskyUtils.get_image_size(file.name) if (width > 5000) and (height > 5000) and os.path.getsize( file.name) < 1000000: LOG.info( "Found a GIF that exceeds sane size limits (over 5000px^2, but under 1mb)" ) return True return False
async def on_ping(self, message: discord.Message): if not HuskyUtils.should_process_message(message): return if message.content.startswith(self._bot.command_prefix): return # hacky way to determine if a message is only a bot mention if message.clean_content == f"@{message.guild.me.display_name}": return if self._bot.user in message.mentions: await message.channel.send(self.husky_speak())
async def generate_flag(self, ctx: commands.Context): """ When necessary, this command allows the bot to generate a new flag file for internal use. This command will invalidate the existing flag file entirely. """ self.generate_ctf_file(True) fh = HuskyUtils.get_sha1_hash_of_file(CTF_PATH) await ctx.send(embed=discord.Embed( title=Emojis.RED_FLAG + " HuskyBot CTF Challenge", description= f"The flag file has been generated. The file's hash is `{fh[-8:]}`.", color=Colors.SUCCESS))
async def filter_message(self, message: discord.Message, context: str = "new_message"): if not HuskyUtils.should_process_message(message): return if message.author.permissions_in(message.channel).manage_messages: return for ubl_term in self.bot.config.get('ubl', {}).get('bannedPhrases', []): if re.search(ubl_term, message.content, re.IGNORECASE) is not None: await message.author.ban(reason=f"User used UBL keyword `{ubl_term}`. Purging user...", delete_message_days=5) await message.guild.unban(message.author, reason="UBL ban reversal") LOG.info("Kicked UBL triggering user (context %s, keyword %s, from %s in %s): %s", context, message.author, ubl_term, message.channel, message.content)
async def log(self, ctx: discord.ext.commands.Context, lines: int = 10): """ Extract a segment of the bot's current log file. This command takes an optional parameter (lines) which can be used to seek back in the bot's log file by a certain number of lines. This command has a limited output of 2000 characters, so the log may be trimmed. This allows for administrators to creatively abuse the lines function to get basic pagination. WARNING: The log command may reveal some sensitive information about bot execution! Parameters ---------- ctx :: Discord context <!nodoc> lines :: The number of lines to pull from the log file. """ log_file = self._session_store.get('logPath') if log_file is None: await ctx.send(embed=discord.Embed( title="Bot Manager", description= "A log file was expected, but was not found or configured. This suggests a *serious* " "problem with the bot.", color=Colors.DANGER)) return logs = HuskyUtils.tail(log_file, lines) log_title = f"**Log Entries from {log_file}**" log_data = HuskyUtils.trim_string( logs.replace('```', '`\u200b`\u200b`'), 2000 - (len(log_title) + 15), True) await ctx.send(log_title + "\n" + f"```{log_data}```")
async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): channel = self.bot.get_channel( payload.channel_id) # type: discord.TextChannel message = await channel.fetch_message(payload.message_id ) # type: discord.Message channel_config = self._config.get('reactToPin', {}).get( str(channel.id)) # type: dict if not HuskyUtils.should_process_message(message): return if channel_config is None or not channel_config.get('enabled', False): LOG.debug( f"A pin configuration was not found for channel {channel}. Ignoring message." ) return # Check if the message is pinned if message not in await channel.pins(): LOG.debug("Can't unpin a message that isn't currently pinned.") return if str(payload.emoji) != channel_config.get('emoji'): LOG.debug( f"Got an invalid emoji for message {message.id} in channel {channel}, ignoring." ) return if message.id in channel_config.get('permanent', []): LOG.info( "Reactions dropped below threshold on permanently pinned message, ignoring but logging." ) return # we are in a valid channel now, with a valid emote. current_reactions = await self.count_reactions(message, payload.emoji) if current_reactions >= channel_config.get('requiredToPin', 6): LOG.debug( "Got a valid removal event for the emote, but there are too many reactions to unpin." ) return await message.unpin() LOG.info( f"Unpinned previously pinned message {message.id} in {channel}, as it is no longer at the required " f"reaction count.")
async def start_giveaway(self, ctx: commands.Context, title: str, end_time: datetime.datetime, winners: int) -> HuskyData.GiveawayObject: """ Begin a new Giveaway. This command will build a new Giveaway, register it with the event loop and cache, and store it in the persistent file. If a giveaway is to be executed, it ***must*** be created with this method. :param ctx: The Context responsible for creating the giveaway. :param title: The title/name of the giveaway (usually the object people will win) :param end_time: A DateTime to end the giveaway :param winners: A number of winners (greater than zero) to choose from. :return: Returns the created GiveawayObject """ channel = ctx.channel if winners == 1: winner_str = "1 winner" else: winner_str = f"{winners} winners" giveaway_embed = discord.Embed( title=f"{Emojis.GIVEAWAY} New Giveaway: {title}!", description=f"A giveaway has been started for **{title}**!\n\nAnyone may enter, and up to {winner_str} " f"will be selected for the final prize. React with the {Emojis.GIVEAWAY} emoji to enter." f"\n\nThis giveaway will end at {end_time.strftime(DATETIME_FORMAT)}", color=Colors.INFO ) message = await ctx.send(embed=giveaway_embed) await message.add_reaction(Emojis.GIVEAWAY) giveaway = HuskyData.GiveawayObject() giveaway.name = title giveaway.end_time = end_time.timestamp() # All timestamps are UTC. giveaway.winner_count = winners giveaway.register_channel_id = channel.id giveaway.register_message_id = message.id pos = HuskyUtils.get_sort_index(self.__cache__, giveaway, 'end_time') # note, we insert the giveaway, and sort. this is a rare operation, so a sort is "acceptable" # Null-ending giveaways (usually impossible) will be placed at the very end. self.__cache__.insert(pos, giveaway) self.__cache__.sort(key=lambda g: g.end_time if g.end_time else 10 * 100) self._giveaway_config.set(GIVEAWAY_CONFIG_KEY, self.__cache__) return giveaway
async def on_ping(self, message: discord.Message): if not HuskyUtils.should_process_message(message): return if message.content.startswith(self._bot.command_prefix): return bot_mention = f"<@{self._bot.user.id}>" # Don't let people ping Husky to get a response. if bot_mention == message.content: return if bot_mention in message.content: await message.channel.send(self.husky_speak())
async def on_message(self, message: discord.Message): if not HuskyUtils.should_process_message(message): return if message.author.id in self._config.get('userBlacklist', []): return if message.channel.id in self._config.get('disabledChannels', []) \ and isinstance(message.author, discord.Member) \ and not message.author.permissions_in(message.channel).manage_messages: return if self._session_store.get('lockdown', False): return responses = self._config.get("responses", {}) for response in responses.keys(): if not (message.content.lower().startswith(response.lower())): continue if not ((responses[response].get('allowedChannels') is None) or (message.channel.id in responses[response].get('allowedChannels'))): continue if HuskyUtils.member_has_any_role(message.author, responses[response].get('requiredRoles')) \ or bool(message.author.permissions_in(message.channel).manage_messages): if responses[response].get('isEmbed', False): await message.channel.send( content=None, embed=discord.Embed.from_dict( responses[response]['response'])) else: await message.channel.send( content=responses[response]['response'])
async def requestify(self, ctx: commands.Context, url: str, method: str = "GET", *, data: str = None): """ Make an HTTP call to an [external] server. This command functionally acts as cURL, and allows for HTTP calls to be made and sent to a server. This command may hit HuskyBot's internal API server. The following HTTP methods are supported: GET, POST, PUT, DELETE, PATCH The bot API server is available at http://127.0.0.1 at whatever port is defined (default 9339). """ method = method.upper() supported_methods = ["GET", "POST", "PUT", "DELETE", "PATCH"] if method not in supported_methods: await ctx.send(embed=discord.Embed( title="Invalid request method!", description="Only the following request methods are supported:\n\n" "{}".format(', '.join('`{}`'.format(m) for m in supported_methods)), color=Colors.ERROR )) return try: async with aiohttp.client.request(method, url, data=data) as response: if 100 <= response.status <= 199: color = Colors.INFO elif 200 <= response.status <= 299: color = Colors.SUCCESS elif 300 <= response.status <= 399: color = Colors.WARNING else: color = Colors.DANGER await ctx.send(embed=discord.Embed( title=f"HTTP Status {response.status}", description="```{}```".format(HuskyUtils.trim_string(await response.text(), 2000)), color=color )) except aiohttp.client.ClientError as ex: await ctx.send(embed=discord.Embed( title="Could Not Make Request", description=f"Requestify failed to make a request due to error `{type(ex).__name__}`. " f"Data has been logged.", color=Colors.DANGER )) LOG.warning("Requestify raised exception.", ex)
def entrypoint(self): if os.environ.get('DISCORD_TOKEN'): LOG.debug( "Loading API key from environment variable DISCORD_TOKEN.") elif self.config.get('apiKey'): LOG.warning( "DEPRECATION WARNING - The Discord API key is being retrieved from the config file. " "This capability will be removed in a future release. Please move the token to the " "DISCORD_TOKEN environment variable.") else: LOG.critical( "The API key for HuskyBot must be loaded via the DISCORD_TOKEN environment variable." ) exit(1) LOG.info("The bot's log path is: {}".format(self.__log_path)) if HuskyUtils.is_docker(): LOG.info( "The bot has detected it is running in Docker. Some internal systems have been changed in order " "to better suit the container land.") if self.__daemon_mode: LOG.info( "The bot is currently loaded in Daemon Mode. In Daemon Mode, certain functionalities are " "slightly altered to better utilize the headless environment.") if self.developer_mode: LOG.info( "The bot is running in DEVELOPER MODE! Some features may behave in unexpected ways or may " "otherwise break. Some bot safety checks are disabled with this mode on." ) self.run(os.getenv('DISCORD_TOKEN', self.config.get('apiKey'))) LOG.info("Shutting down HuskyBot...") self.config.save() LOG.debug("Config file saved/written to disk.") if self.db: self.db.dispose() LOG.debug("DB connection shut down") if self.config.get("restartReason") is not None: LOG.info("Bot is ready for restart...") os.execl(sys.executable, *([sys.executable] + sys.argv))
def generate_ctf_file(self, force: bool = False): ctf_config = self._config.get('ctf', {}) if os.path.isfile(CTF_PATH) and not force: LOG.debug("A CTF file already exists, so not regenerating.") return with open(CTF_PATH, 'w') as f: f.write( FLAG_TEMPLATE.format(flag_key=str(uuid.uuid4()), ts=HuskyUtils.get_timestamp(), salt=secrets.token_urlsafe(64))) ctf_config['pwned_by'] = None ctf_config['pwned_at'] = None self._config.set('ctf', ctf_config) LOG.info("Generated a CTF flag file!")
async def on_error(self, event_method, *args, **kwargs): exception = sys.exc_info() channel = self.config.get('specialChannels', {}).get(ChannelKeys.STAFF_LOG.value, None) if channel is None: LOG.warning( 'A logging channel is not set up! Error messages will not be forwarded to ' 'Discord.') raise exception channel = self.get_channel(channel) if isinstance(exception, discord.HTTPException) and exception.code == 502: LOG.error( f"Got HTTP status code {exception.code} for method {event_method} - Discord is " f"likely borked now.") else: LOG.error('Exception in method %s:\n%s', event_method, traceback.format_exc()) try: embed = discord.Embed( title="Bot Exception Handler", description="Exception in method `{}`:\n```{}```".format( event_method, HuskyUtils.trim_string( traceback.format_exc().replace( '```', '`\u200b`\u200b`'), 1500)), color=Colors.DANGER) owner_id = self.session_store.get('appInfo', None).owner.id dev_ping = self.config.get("specialRoles", {}).get( SpecialRoleKeys.BOT_DEVS.value, owner_id) await channel.send( "<@{}>, an error has occurred with the bot. See attached " "embed.".format(dev_ping), embed=embed) except Exception as e: LOG.critical( "There was an error sending an error to the error channel.\n " + str(e)) raise e
async def user_ban_logger(self, guild: discord.Guild, user: discord.User): if "userBan" not in self._config.get("loggers", {}).keys(): return logger_ignores: dict = self._session_store.get('loggerIgnores', {}) ignored_bans = logger_ignores.setdefault('ban', []) if user.id in ignored_bans: return # Get timestamp as soon as the event is fired, because waiting for bans may take a while. timestamp = HuskyUtils.get_timestamp() alert_channel = self._config.get('specialChannels', {}).get(ChannelKeys.STAFF_LOG.value, None) if alert_channel is None: return alert_channel = self.bot.get_channel(alert_channel) embed = discord.Embed(title=Emojis.BAN + " User banned", description=f"{user} was banned from the guild.", color=Colors.DANGER) ban_entry = discord.utils.get(await guild.bans(), user=user) if ban_entry is None: raise ValueError( f"A ban record for user {user.id} was expected, but no entry was found" ) ban_reason = ban_entry.reason if ban_reason is None: ban_reason = "<No ban reason provided>" embed.set_thumbnail(url=user.avatar_url) embed.add_field(name="User ID", value=user.id, inline=True) embed.add_field(name="Ban Timestamp", value=timestamp, inline=True) embed.add_field(name="Ban Reason", value=ban_reason, inline=False) LOG.info( f"User {user} was banned from {guild.name} for '{ban_reason}'.") await alert_channel.send(embed=embed)
async def requestify(self, ctx: commands.Context, url: str, method: str = "GET", *, data: str = None): method = method.upper() supported_methods = ["GET", "POST", "PUT", "DELETE", "PATCH"] if method not in supported_methods: await ctx.send(embed=discord.Embed( title="Invalid request method!", description= "Only the following request methods are supported:\n\n" "{}".format(', '.join('`{}`'.format(m) for m in supported_methods)), color=Colors.ERROR)) return try: async with aiohttp.client.request(method, url, data=data) as response: if 100 <= response.status <= 199: color = Colors.INFO elif 200 <= response.status <= 299: color = Colors.SUCCESS elif 300 <= response.status <= 399: color = Colors.WARNING else: color = Colors.DANGER await ctx.send(embed=discord.Embed( title=f"HTTP Status {response.status}", description="```{}```".format( HuskyUtils.trim_string(await response.text(), 2000)), color=color)) except aiohttp.client.ClientError as ex: await ctx.send(embed=discord.Embed( title="Could Not Make Request", description= f"Requestify failed to make a request due to error `{type(ex).__name__}`. " f"Data has been logged.", color=Colors.DANGER)) LOG.warning("Requestify raised exception.", ex)
async def get_file_hash(self, ctx: commands.Context): """ This command will expose the SHA1 hash of the CTF flag file, and not much else. For information about what's in the flag file, do `/help CTFChallenge`. """ if not os.path.isfile(CTF_PATH): await ctx.send(embed=discord.Embed( title=Emojis.RED_FLAG + " HuskyBot CTF Challenge", description= f"A CTF flag file does not currently exist. Please contact a bot admin.", color=Colors.DANGER)) return fhash = HuskyUtils.get_sha1_hash_of_file(CTF_PATH) await ctx.send(embed=discord.Embed( title=Emojis.RED_FLAG + " HuskyBot CTF Challenge", description=f"The flag file's SHA1 hash is: \n```{fhash}```", color=Colors.SUCCESS))
async def on_message(self, message: discord.Message): author = message.author if not HuskyUtils.should_process_message(message): return if message.content.startswith(self.command_prefix): if (author.id in self.config.get('userBlacklist', [])) and (author.id not in self.superusers): LOG.info("Blacklisted user %s attempted to run command %s", message.author, message.content) return if message.content.lower().split(' ')[0][1:] in self.config.get( 'ignoredCommands', []): LOG.info("User %s ran an ignored command %s", message.author, message.content) return if message.content.lower().split(' ')[0].startswith('/r/'): LOG.info("User %s linked to subreddit %s, ignoring command", message.author, message.content) return if self.session_store.get( 'lockdown', False) and (author.id not in self.superusers): LOG.info( "Lockdown mode is enabled for the bot. Command blocked.") return if message.channel.id in self.config.get("disabledChannels", []) and isinstance(author, discord.Member) \ and not author.permissions_in(message.channel).manage_messages: LOG.info( f"Got a command from a disabled channel {message.channel}. Command blocked." ) return LOG.info("User %s ran %s", author, message.content) await self.process_commands(message)
def generate_cleanup_filter(): if filter_def is None: return None content_list = filter_def.split('--') # Filter types regex_list = [] user_list = [] for filter_candidate in content_list: if filter_candidate is None or filter_candidate == '': continue filter_candidate = filter_candidate.strip() filter_candidate = filter_candidate.split(" ", 1) if filter_candidate[0] in ["user", "author", "member"]: user_id = HuskyUtils.get_user_id_from_arbitrary_str( ctx.guild, filter_candidate[1]) user_list.append(user_id) elif filter_candidate[0] in ["regex"]: regex_list.append(filter_candidate[1]) else: raise KeyError( f"Filter {filter_candidate[0]} is not valid!") def dynamic_check(message: discord.Message): if len(user_list) > 0 and message.author.id not in user_list: return False for regex in regex_list: if len(regex_list) > 0 and re.search( regex, message.content) is None: return False return True return dynamic_check
async def user_leave_logger(self, member: discord.Member): if "userLeave" not in self._config.get("loggers", {}).keys(): return alert_channel = self._config.get('specialChannels', {}).get(ChannelKeys.USER_LOG.value, None) if alert_channel is None: LOG.debug("skipping log event - no userlog channel set.") return alert_channel = member.guild.get_channel(alert_channel) embed = discord.Embed(title=Emojis.DOOR + " Member left the guild", description=f"{member} has left the guild.", color=Colors.WARNING) embed.set_thumbnail(url=member.avatar_url) embed.add_field(name="User ID", value=member.id) embed.add_field(name="Leave Timestamp", value=HuskyUtils.get_timestamp()) roles_on_leave = [] for role in member.roles: # type: discord.Role if role.is_default(): continue roles_on_leave.append(role.mention) if roles_on_leave: embed.add_field(name="Roles on Leave", value=", ".join(roles_on_leave), inline=False) LOG.info(f"User {member} has left {member.guild.name}.") await alert_channel.send(embed=embed)