async def do_removal(self, ctx, limit, predicate, *, before=None, after=None): if limit > 2000: return await ctx.send( f'Too many messages to search given ({limit}/2000)') if before is None: before = ctx.message else: before = disnake.Object(id=before) if after is not None: after = disnake.Object(id=after) try: deleted = await ctx.channel.purge(limit=limit, before=before, after=after, check=predicate) except disnake.Forbidden as e: return await ctx.send( 'I do not have permissions to delete messages.') except disnake.HTTPException as e: return await ctx.send(f'Error: {e} (try a smaller search?)') spammers = Counter(m.author.display_name for m in deleted) deleted = len(deleted) messages = [ f'{deleted} message{" was" if deleted == 1 else "s were"} removed.' ] if deleted: messages.append('') spammers = sorted(spammers.items(), key=lambda t: t[1], reverse=True) messages.extend(f'**{name}**: {count}' for name, count in spammers) to_send = '\n'.join(messages) if len(to_send) > 2000: await ctx.send(f'Successfully removed {deleted} messages.', delete_after=10) else: await ctx.send(to_send, delete_after=10)
async def convert_argument(ctx, converters: list, args: Any, param) -> Any: for converter in converters: # Enums if issubclass(converter, Enum): if isinstance(args, converter): return args if isinstance(args, str): return getattr(converter, args) return converter(int(args)) try: p = disnake.Object(id=0) p.name = param converted = await _actual_conversion(ctx, converter, str(args), p) return converted except BadArgument: pass except ConversionError: pass raise BadArgument( f'Converting {args} to "{" or ".join([c.__name__ for c in converters])}" failed for parameter "{param}".' )
async def get(self, ctx: Context, cog_only=False) -> Config: await self._bot.wait_until_ready() if not isinstance(ctx, Context): message = disnake.Object(id=0) message.guild = self._bot.get_guild(ctx) message._state = message.guild._state ctx = Context(bot=self._bot, message=message, prefix=None, view=None) document = await self._bot.config.guild(ctx.guild.id) cog_doc = document.get(self._template.__key__, {}) if not cog_only: items = self._bot.config.general_context._template.items + self._template.items doc = { **cog_doc, **document.get( self._bot.config.general_context._template.__key__, {}) } else: items = self._template.items doc = cog_doc log.debug( f'Creating config {self._template} for guild {ctx.guild.name} ({ctx.guild.id})' ) return await Config.from_doc(self._template, ctx, doc, items)
async def pack_messages(messages, guild_id): out = "" for message in messages: name = await Utils.username(message.author, clean=False) reply = "" if message.reply_to is not None: reply = f" | In reply to https://discord.com/channels/{message.server}/{message.channel}/{message.reply_to}" timestamp = datetime.datetime.strftime(disnake.Object(message.messageid).created_at.astimezone(pytz.timezone(Configuration.get_var(guild_id, 'GENERAL', 'TIMEZONE'))),'%H:%M:%S') out += f"{timestamp} {message.server} - {message.channel} - {message.messageid} | {name} ({message.author}) | {message.content}{reply} | {(', '.join(Utils.assemble_attachment(message.channel, attachment.id, attachment.name) for attachment in message.attachments))}\r\n" return out
async def handle_emoji_suggestion_message_edit( self, message: disnake.RawMessageUpdateEvent): if message.channel_id == EMOJI_SUGGESTIONS_CHAN_ID: channel = self.bot.get_channel(EMOJI_SUGGESTIONS_CHAN_ID) if channel is None: return try: await channel.delete_messages( [disnake.Object(message.message_id)]) except disnake.HTTPException: pass
class ModQueueItem(object): __slots__ = ('_bot', '_collection', 'id', 'type', 'author_id', 'guild_id', 'message', 'matches', 'timestamp', 'edits', 'action', 'mod_id', 'action_timestamp', 'deleted_at', 'deleted_by_id') @classmethod def from_doc(cls, doc, bot: commands.Bot, collection): self = cls() self._bot = bot self._collection = collection self._from_doc(doc) return self def _from_doc(self, doc): self.id = doc.get('id') self.type = ModQueueItem.Type(doc['type']) self.author_id = doc['author_id'] self.guild_id = doc['guild_id'] self.message = ModQueueItem.Message.from_doc(doc, self._bot) self.matches = doc['matches'] self.timestamp = doc['timestamp'] self.deleted_at = doc.get('deleted_at', False) self.deleted_by_id = doc.get('deleted_by_id', None) self.edits = None if 'edits' in doc: self.edits = [ModQueueItem.Edit.from_doc(d) for d in doc['edits']] self.action = None if 'action' in doc: self.action = Action(doc['action']) self.mod_id = doc.get('mod_id', None) self.action_timestamp = doc.get('action_timestamp', None) @property def author(self) -> disnake.User: if author := self._bot.get_user(self.author_id): return author # if the author can't be loaded for some reason, we will build a fake one author = disnake.Object(self.author_id) author.name = '[USER NOT FOUND]' author.discriminator = '0000' author.display_avatar = 'https://cdn.discordapp.com/embed/avatars/0.png' author.mention = f'<@{self.author_id}>' return author
async def ios_body(self, channel=disnake.Object(id='534431057001316362')): process = subprocess.Popen(["ssh", "merlin"], stdout=subprocess.PIPE) output, _ = process.communicate() memory, rest = output.decode('utf-8').split("semafory:\n") semaphores, processes = rest.split("procesy:\n") try: parsed_memory = parse_memory(memory) parsed_semaphores, parsed_files = parse_semaphores(semaphores) parsed_processes = parse_processes(processes) parsed_resources = { RESOURCE_TYPE.MEMORY: parsed_memory, RESOURCE_TYPE.SEMAPHORE: parsed_semaphores, RESOURCE_TYPE.FILE: parsed_files, RESOURCE_TYPE.PROCESS: parsed_processes, } await print_output(self.bot, channel, "merlinovi", filter_year(parsed_resources)) except IndexError: await channel.send("Toastere, máš bordel v parsování.") process = subprocess.Popen(["ssh", "eva"], stdout=subprocess.PIPE) output, _ = process.communicate() memory, rest = output.decode('utf-8').split("semafory:\n") semaphores, processes = rest.split("procesy:\n") # remove unwanted processes processes = filter_processes(processes) try: parsed_memory = parse_memory(memory) parsed_semaphores, _ = parse_semaphores(semaphores) parsed_processes = parse_processes(processes) parsed_resources = { RESOURCE_TYPE.MEMORY: parsed_memory, RESOURCE_TYPE.SEMAPHORE: parsed_semaphores, RESOURCE_TYPE.PROCESS: parsed_processes, } await print_output(self.bot, channel, "evě", filter_year(parsed_resources)) except IndexError: await channel.send("Toastere, máš bordel v parsování.") # eva doesn't seem to have /dev/shm await channel.send("Pokud nevíte jak po sobě uklidit, checkněte: " + "https://discordapp.com/channels/" + "461541385204400138/534431057001316362/" + "698701631495340033")
async def on_tempban_timer_complete(self, timer): guild_id, mod_id, member_id = timer.args guild = self.bot.get_guild(guild_id) if guild is None: # RIP return moderator = guild.get_member(mod_id) if moderator is None: try: moderator = await self.bot.fetch_user(mod_id) except: # request failed somehow moderator = f'Mod ID {mod_id}' else: moderator = f'{moderator} (ID: {mod_id})' else: moderator = f'{moderator} (ID: {mod_id})' reason = f'Automatic unban from timer made on {timer.created_at} by {moderator}.' await guild.unban(disnake.Object(id=member_id), reason=reason)
async def softban(self, ctx, member: MemberID, *, reason: ActionReason = None): """Soft bans a member from the server. A softban is basically banning the member from the server but then unbanning the member as well. This allows you to essentially kick the member while removing their messages. In order for this to work, the bot must have Ban Member permissions. To use this command you must have Kick Members permissions. """ if reason is None: reason = f'Action done by {ctx.author} (ID: {ctx.author.id})' obj = disnake.Object(id=member) await ctx.guild.ban(obj, reason=reason) await ctx.guild.unban(obj, reason=reason) await ctx.send('\N{OK HAND SIGN}')
async def purge(self, ctx, *, args: str = None): '''Advanced purge command. Do `help purge` for usage examples and argument list. Arguments are parsed as command line arguments. Examples: Delete all messages within the last 200 containing the word "spam": `purge --check 200 --contains "spam"` Delete all messages within the last 100 from two members: `purge --user @runie @dave` Delete maximum 6 messages within the last 400 starting with "ham": `purge --check 400 --max 6 --starts "ham"` List of arguments: ``` --check <int> Amount of messages the bot will check for deletion. --max <int> Maximum amount of messages the bot will delete. --bot Only delete messages from bots. --user member [...] Only delete messages from these members. --after message_id Start deleting after this message id. --before message_id Delete, at most, up until this message id. --contains <string> [...] Delete messages containing this string(s). --starts <string> [...] Delete messages starting with this string(s). --ends <string> [...] Delete messages ending with this string(s).```''' parser = NoExitArgumentParser(prog='purge', add_help=False, allow_abbrev=False) parser.add_argument( '-c', '--check', type=int, metavar='message_count', help='Total amount of messages checked for deletion.') parser.add_argument( '-m', '--max', type=int, metavar='message_count', help='Total amount of messages the bot will delete.') parser.add_argument('--bot', action='store_true', help='Only delete messages from bots.') parser.add_argument('-u', '--user', nargs='+', metavar='user', help='Only delete messages from this member(s).') parser.add_argument('-a', '--after', type=int, metavar='id', help='Start deleting after this message id.') parser.add_argument('-b', '--before', type=int, metavar='id', help='Delete, at most, up until this message id.') parser.add_argument('--contains', nargs='+', metavar='text', help='Delete messages containing this string(s).') parser.add_argument( '--starts', nargs='+', metavar='text', help='Delete messages starting with this string(s).') parser.add_argument('--ends', nargs='+', metavar='text', help='Delete messages ending with this string(s).') if args is None: await ctx.send('```\n{0}\n```'.format(parser.format_help())) return try: args = parser.parse_args(shlex.split(args)) except Exception as e: raise commands.CommandError(str(e).partition('error: ')[2]) preds = [ lambda m: m.id != ctx.message.id, lambda m: m.id != RULES_MSG_ID ] if args.user: converter = MaybeMemberConverter() members = [] for id in args.user: try: member = await converter.convert(ctx, id) members.append(member) except commands.CommandError: raise commands.CommandError( 'Unknown user: "******"'.format(id)) # yes, if both objects were disnake.Member I could do m.author in members, # but since member can be FakeUser I need to do an explicit id comparison preds.append( lambda m: any(m.author.id == member.id for member in members)) if args.contains: preds.append(lambda m: any( (s.lower() in m.content.lower()) for s in args.contains)) if args.bot: preds.append(lambda m: m.author.bot) if args.starts: preds.append(lambda m: any(m.content.lower().startswith(s.lower()) for s in args.starts)) if args.ends: preds.append(lambda m: any(m.content.lower().endswith(s.lower()) for s in args.ends)) count = args.max deleted = 0 def predicate(message): nonlocal deleted if count is not None and deleted >= count: return False if all(pred(message) for pred in preds): deleted += 1 return True # limit is 100 be default limit = 100 after = None before = None # set to 512 if after flag is set if args.after: after = disnake.Object(id=args.after) limit = PURGE_LIMIT if args.before: before = disnake.Object(id=args.before) # if we actually want to manually specify it doe if args.check is not None: limit = max(0, min(PURGE_LIMIT, args.check)) try: deleted_messages = await ctx.channel.purge(limit=limit, check=predicate, before=before, after=after) except disnake.HTTPException: raise commands.CommandError( 'Error occurred when deleting messages.') deleted_count = len(deleted_messages) log.info('%s purged %s messages in %s', po(ctx.author), deleted_count, po(ctx.guild)) await ctx.send('{0} messages deleted.'.format(deleted_count), delete_after=10)
def get_guild_prefixes(self, guild, *, local_inject=_prefix_callable): proxy_msg = disnake.Object(id=0) proxy_msg.guild = guild return local_inject(self, proxy_msg)
async def massban(self, ctx, *, args): """Mass bans multiple members from the server. This command has a powerful "command line" syntax. To use this command you and the bot must both have Ban Members permission. **Every option is optional.** Users are only banned **if and only if** all conditions are met. The following options are valid. `--channel` or `-c`: Channel to search for message history. `--reason` or `-r`: The reason for the ban. `--regex`: Regex that usernames must match. `--created`: Matches users whose accounts were created less than specified minutes ago. `--joined`: Matches users that joined less than specified minutes ago. `--joined-before`: Matches users who joined before the member ID given. `--joined-after`: Matches users who joined after the member ID given. `--no-avatar`: Matches users who have no avatar. (no arguments) `--no-roles`: Matches users that have no role. (no arguments) `--show`: Show members instead of banning them (no arguments). Message history filters (Requires `--channel`): `--contains`: A substring to search for in the message. `--starts`: A substring to search if the message starts with. `--ends`: A substring to search if the message ends with. `--match`: A regex to match the message content to. `--search`: How many messages to search. Default 100. Max 2000. `--after`: Messages must come after this message ID. `--before`: Messages must come before this message ID. `--files`: Checks if the message has attachments (no arguments). `--embeds`: Checks if the message has embeds (no arguments). """ # For some reason there are cases due to caching that ctx.author # can be a User even in a guild only context # Rather than trying to work out the kink with it # Just upgrade the member itself. if not isinstance(ctx.author, disnake.Member): try: author = await ctx.guild.fetch_member(ctx.author.id) except disnake.HTTPException: return await ctx.send( 'Somehow, Discord does not seem to think you are in this server.' ) else: author = ctx.author parser = Arguments(add_help=False, allow_abbrev=False) parser.add_argument('--channel', '-c') parser.add_argument('--reason', '-r') parser.add_argument('--search', type=int, default=100) parser.add_argument('--regex') parser.add_argument('--no-avatar', action='store_true') parser.add_argument('--no-roles', action='store_true') parser.add_argument('--created', type=int) parser.add_argument('--joined', type=int) parser.add_argument('--joined-before', type=int) parser.add_argument('--joined-after', type=int) parser.add_argument('--contains') parser.add_argument('--starts') parser.add_argument('--ends') parser.add_argument('--match') parser.add_argument('--show', action='store_true') parser.add_argument('--embeds', action='store_const', const=lambda m: len(m.embeds)) parser.add_argument('--files', action='store_const', const=lambda m: len(m.attachments)) parser.add_argument('--after', type=int) parser.add_argument('--before', type=int) try: args = parser.parse_args(shlex.split(args)) except Exception as e: return await ctx.send(str(e)) members = [] if args.channel: channel = await commands.TextChannelConverter().convert( ctx, args.channel) before = args.before and disnake.Object(id=args.before) after = args.after and disnake.Object(id=args.after) predicates = [] if args.contains: predicates.append(lambda m: args.contains in m.content) if args.starts: predicates.append(lambda m: m.content.startswith(args.starts)) if args.ends: predicates.append(lambda m: m.content.endswith(args.ends)) if args.match: try: _match = re.compile(args.match) except re.error as e: return await ctx.send( f'Invalid regex passed to `--match`: {e}') else: predicates.append(lambda m, x=_match: x.match(m.content)) if args.embeds: predicates.append(args.embeds) if args.files: predicates.append(args.files) async for message in channel.history(limit=min( max(1, args.search), 2000), before=before, after=after): if all(p(message) for p in predicates): members.append(message.author) else: if ctx.guild.chunked: members = ctx.guild.members else: async with ctx.typing(): await ctx.guild.chunk(cache=True) members = ctx.guild.members # member filters predicates = [ lambda m: isinstance(m, disnake.Member) and can_execute_action( ctx, author, m), # Only if applicable lambda m: not m.bot, # No bots lambda m: m.discriminator != '0000', # No deleted users ] converter = commands.MemberConverter() if args.regex: try: _regex = re.compile(args.regex) except re.error as e: return await ctx.send(f'Invalid regex passed to `--regex`: {e}' ) else: predicates.append(lambda m, x=_regex: x.match(m.name)) if args.no_avatar: predicates.append(lambda m: m.avatar is None) if args.no_roles: predicates.append(lambda m: len(getattr(m, 'roles', [])) <= 1) now = disnake.utils.utcnow() if args.created: def created(member, *, offset=now - datetime.timedelta(minutes=args.created)): return member.created_at > offset predicates.append(created) if args.joined: def joined(member, *, offset=now - datetime.timedelta(minutes=args.joined)): if isinstance(member, disnake.User): # If the member is a user then they left already return True return member.joined_at and member.joined_at > offset predicates.append(joined) if args.joined_after: _joined_after_member = await converter.convert( ctx, str(args.joined_after)) def joined_after(member, *, _other=_joined_after_member): return member.joined_at and _other.joined_at and member.joined_at > _other.joined_at predicates.append(joined_after) if args.joined_before: _joined_before_member = await converter.convert( ctx, str(args.joined_before)) def joined_before(member, *, _other=_joined_before_member): return member.joined_at and _other.joined_at and member.joined_at < _other.joined_at predicates.append(joined_before) members = {m for m in members if all(p(m) for p in predicates)} if len(members) == 0: return await ctx.send('No members found matching criteria.') if args.show: members = sorted(members, key=lambda m: m.joined_at or now) fmt = "\n".join( f'{m.id}\tJoined: {m.joined_at}\tCreated: {m.created_at}\t{m}' for m in members) content = f'Current Time: {disnake.utils.utcnow()}\nTotal members: {len(members)}\n{fmt}' file = disnake.File(io.BytesIO(content.encode('utf-8')), filename='members.txt') return await ctx.send(file=file) if args.reason is None: return await ctx.send('--reason flag is required.') else: reason = await ActionReason().convert(ctx, args.reason) confirm = await ctx.prompt( f'This will ban **{plural(len(members)):member}**. Are you sure?') if not confirm: return await ctx.send('Aborting.') count = 0 for member in members: try: await ctx.guild.ban(member, reason=reason) except disnake.HTTPException: pass else: count += 1 await ctx.send(f'Banned {count}/{len(members)}')
async def censor_invite(self, member, message_id, channel, code, server_name, content, edit, reply, attachments): # Allow for users with a trusted role, or trusted users, to post invite links if Configuration.get_var(member.guild.id, "CENSORING", "ALLOW_TRUSTED_BYPASS" ) and Permissioncheckers.is_trusted(member): return e = '_edit' if edit else '' self.bot.deleted_messages.append(message_id) clean_message = await Utils.clean(content, member.guild) clean_name = Utils.clean_user(member) reply_str = "" if reply is not None: reply_str = f"\n**{Translator.translate('in_reply_to', member.guild.id)}: **<{assemble_jumplink(member.guild.id, channel.id, reply)}>" if attachments is None: attachments = await LoggedAttachment.filter(message=message_id) if len(attachments) > 0: attachments_str = f"**{Translator.translate('attachments', member.guild.id, count=len(attachments))}:** " attachments_str += ', '.join( Utils.assemble_attachment( channel.id, attachment.id, attachment.filename if hasattr( attachment, "filename") else attachment.name) for attachment in attachments) else: attachments_str = "" clean_message = Utils.trim_message( clean_message, 1600 - len(attachments_str) - len(reply_str)) try: if channel.permissions_for(channel.guild.me).manage_messages: await channel.delete_messages([disnake.Object(message_id)]) GearbotLogging.log_key(member.guild.id, f'censored_invite{e}', user=clean_name, code=code, message=clean_message, server_name=server_name, user_id=member.id, channel=channel.mention, attachments=attachments_str, reply=reply_str) else: GearbotLogging.log_key(member.guild.id, f'invite_censor_forbidden{e}', user=clean_name, code=code, message=clean_message, server_name=server_name, user_id=member.id, channel=channel.mention, attachments=attachments_str, reply=reply_str) if message_id in self.bot.deleted_messages: self.bot.deleted_messages.remove(message_id) except disnake.NotFound: # we failed? guess we lost the race, log anyways GearbotLogging.log_key(member.guild.id, f'invite_censor_fail{e}', user=clean_name, code=code, message=clean_message, server_name=server_name, user_id=member.id, channel=channel.mention, attachments=attachments_str, reply=reply_str) if message_id in self.bot.deleted_messages: self.bot.deleted_messages.remove(message_id) self.bot.dispatch( "user_censored", messageholder(message_id, member, channel, channel.guild))
async def censor_message(self, message_id, content, channel, member, bad, key="", edit=False, reply="", attachments=""): if Configuration.get_var(member.guild.id, "CENSORING", "ALLOW_TRUSTED_CENSOR_BYPASS" ) and Permissioncheckers.is_trusted(member): return e = '_edit' if edit else '' clean_message = await Utils.clean(content, channel.guild, markdown=False) reply_str = "" if reply is not None: reply_str = f"\n**{Translator.translate('in_reply_to', member.guild.id)}: **<{assemble_jumplink(member.guild.id, channel.id, reply)}>" if attachments is None: attachments = await LoggedAttachment.filter(message=message_id) if len(attachments) > 0: attachments_str = f"**{Translator.translate('attachments', member.guild.id, count=len(attachments))}:** " attachments_str += ', '.join( Utils.assemble_attachment( channel.id, attachment.id, attachment.filename if hasattr( attachment, "filename") else attachment.name) for attachment in attachments) else: attachments_str = "" clean_message = Utils.trim_message( clean_message, 1600 - len(attachments_str) - len(reply_str)) p = channel.permissions_for(channel.guild.me) if p.manage_messages or p.administrator: try: self.bot.deleted_messages.append(message_id) await channel.delete_messages([disnake.Object(message_id)]) except disnake.NotFound as ex: pass else: GearbotLogging.log_key(channel.guild.id, f'censored_message{key}{e}', user=member, user_id=member.id, message=clean_message, sequence=bad, channel=channel.mention, reply=reply_str, attachments=attachments_str) else: GearbotLogging.log_key( channel.guild.id, f'censored_message_failed{key}{e}', user=member, user_id=member.id, message=clean_message, sequence=bad, link='https://discord.com/channels/{0}/{1}/{2}'.format( channel.guild.id, channel.id, message_id), reply=reply_str, attachments=attachments_str) self.bot.dispatch( "user_censored", messageholder(message_id, member, channel, channel.guild))