def command_markov(self, event, entity): if isinstance(entity, DiscoUser): q = Message.select().where(Message.author_id == entity.id).limit(500000) else: q = Message.select().where(Message.channel_id == entity.id).limit(500000) text = [msg.content for msg in q] self.models[entity.id] = markovify.NewlineText('\n'.join(text)) event.msg.reply(u':ok_hand: created markov model for {} using {} messages'.format(entity, len(text)))
def clean(self, event, user=None, size=25, typ=None, mode='all'): """ Removes messages """ if 0 > size >= 10000: raise CommandFail('too many messages must be between 1-10000') lock = rdb.lock('clean-{}'.format(event.channel.id)) if not lock.acquire(blocking=False): raise CommandFail('already running a clean on this channel') try: query = Message.select().where( (Message.deleted >> False) & (Message.channel_id == event.channel.id) & (Message.timestamp > (datetime.utcnow() - timedelta(days=13)) )).join(User).order_by(Message.timestamp.desc()).limit(size) if mode == 'bots': query = query.where((User.bot >> True)) elif mode == 'user': query = query.where((User.user_id == user.id)) msgs = list(reversed(query)) event.channel.delete_messages(msgs) event.msg.reply(':wastebasket: Ok, deleted {} messages'.format( len(msgs))).after(5).delete() finally: lock.release()
def generate_first_message_id(cls, channel_id): try: return Message.select(Message.id).where( (Message.channel_id == channel_id) ).order_by(Message.id.asc()).limit(1).get().id except Message.DoesNotExist: return None
def check_duplicate_messages(self, event, member, rule): q = [(Message.guild_id == event.guild.id), (Message.timestamp > (datetime.utcnow() - timedelta(seconds=rule.max_duplicates.interval)))] # If we're not checking globally, include the member id if not rule.max_duplicates.meta or not rule.max_duplicates.meta.get( 'global'): q.append((Message.author_id == member.id)) # Grab the previous messages the user sent in this server msgs = list( Message.select( Message.id, Message.content, ).where(reduce(operator.and_, q)).order_by( Message.timestamp.desc()).limit(50).tuples()) # Group the messages by their content dupes = defaultdict(int) for mid, content in msgs: if content: dupes[content] += 1 # If any of them are above the max dupes count, trigger a violation dupes = [ v for k, v in list(dupes.items()) if v > rule.max_duplicates.count ] if dupes: raise Violation( rule, rule.max_duplicates, event, member, 'MAX_DUPLICATES', 'Too Many Duplicated Messages ({} / {})'.format( sum(dupes), len(dupes)))
def archive(self, event, size=50, mode=None, user=None, channel=None): if 0 > size >= 15000: raise CommandFail('too many messages must be between 1-15000') q = Message.select(Message.id).join(User).order_by(Message.id.desc()).limit(size) if mode in ('all', 'channel'): cid = event.channel.id if channel: cid = channel if isinstance(channel, (int, long)) else channel.id channel = event.guild.channels.get(cid) if not channel: raise CommandFail('channel not found') perms = channel.get_permissions(event.author) if not (perms.administrator or perms.read_messages): raise CommandFail('invalid permissions') q = q.where(Message.channel_id == cid) else: user_id = user if isinstance(user, (int, long)) else user.id if event.author.id != user_id: self.can_act_on(event, user_id) q = q.where( (Message.author_id == user_id) & (Message.guild_id == event.guild.id) ) archive = MessageArchive.create_from_message_ids([i.id for i in q]) event.msg.reply('OK, archived {} messages at {}'.format(len(archive.message_ids), archive.url))
def dash_index(): if g.user: if g.user.admin: stats = json.loads(rdb.get('web:dashboard:stats') or '{}') if not stats or 'refresh' in request.args: stats['messages'] = pretty_number(Message.select().count()) stats['guilds'] = pretty_number(Guild.select().count()) stats['users'] = pretty_number(User.select().count()) stats['channels'] = pretty_number(Channel.select().count()) rdb.setex('web:dashboard:stats', json.dumps(stats), 300) guilds = Guild.select().order_by(Guild.guild_id) else: stats = {} guilds = Guild.select( Guild, Guild.config['web'][str(g.user.user_id)].alias('role') ).where( (Guild.enabled == 1) & (~(Guild.config['web'][str(g.user.user_id)] >> None)) ) return render_template( 'dashboard.html', stats=stats, guilds=guilds, ) return render_template('login.html')
def clean(self, event, user=None, size=25, typ=None, mode='all'): """ Removes messages """ if 0 > size >= 10000: raise CommandFail('too many messages must be between 1-10000') if event.channel.id in self.cleans: raise CommandFail('a clean is already running on this channel') query = Message.select(Message.id).where( (Message.deleted >> False) & (Message.channel_id == event.channel.id) & (Message.timestamp > (datetime.utcnow() - timedelta(days=13)))).join(User).order_by( Message.timestamp.desc()).limit(size) if mode == 'bots': query = query.where((User.bot >> True)) elif mode == 'user': query = query.where((User.user_id == user.id)) messages = [i[0] for i in query.tuples()] if len(messages) > 100: msg = event.msg.reply( 'Woah there, that will delete a total of {} messages, please confirm.' .format(len(messages))) msg.chain(False).\ add_reaction(GREEN_TICK_EMOJI).\ add_reaction(RED_TICK_EMOJI) try: mra_event = self.wait_for_event( 'MessageReactionAdd', message_id=msg.id, conditional=lambda e: (e.emoji.id in (GREEN_TICK_EMOJI_ID, RED_TICK_EMOJI_ID) and e.user_id == event.author.id)).get(timeout=10) except gevent.Timeout: return finally: msg.delete() if mra_event.emoji.id != GREEN_TICK_EMOJI_ID: return event.msg.reply( ':wastebasket: Ok please hold on while I delete those messages...' ).after(5).delete() def run_clean(): for chunk in chunks(messages, 100): self.client.api.channels_messages_delete_bulk( event.channel.id, chunk) self.cleans[event.channel.id] = gevent.spawn(run_clean) self.cleans[event.channel.id].join() del self.cleans[event.channel.id]
def violate(self, violation): key = 'lv:{e.member.guild_id}:{e.member.id}'.format(e=violation.event) last_violated = int(rdb.get(key) or 0) rdb.setex( 'lv:{e.member.guild_id}:{e.member.id}'.format(e=violation.event), int(time.time()), 60) if not last_violated > time.time() - 10: self.call('ModLogPlugin.log_action_ext', Actions.SPAM, violation.event.guild.id, v=violation) punishment = violation.check.punishment or violation.rule.punishment punishment_duration = violation.check.punishment_duration or violation.rule.punishment_duration if punishment == PunishmentType.MUTE: Infraction.mute( self, violation.event, violation.member, 'Spam Detected', ) elif punishment == PunishmentType.TEMPMUTE: Infraction.tempmute( self, violation.event, violation.member, 'Spam Detected', datetime.utcnow() + timedelta(seconds=punishment_duration)) elif punishment == PunishmentType.KICK: Infraction.kick(self, violation.event, violation.member, 'Spam Detected') elif punishment == PunishmentType.TEMPBAN: Infraction.tempban( self, violation.event, violation.member, 'Spam Detected', datetime.utcnow() + timedelta(seconds=punishment_duration)) elif punishment == PunishmentType.BAN: Infraction.ban(self, violation.event, violation.member, 'Spam Detected', violation.event.guild) # Clean messages if requested if punishment != PunishmentType.NONE and violation.rule.clean: msgs = Message.select(Message.id, Message.channel_id).where( (Message.guild_id == violation.event.guild.id) & (Message.author_id == violation.member.id) & (Message.timestamp > (datetime.utcnow() - timedelta( seconds=violation.rule.clean_duration)))).limit( violation.rule.clean_count).tuples() channels = defaultdict(list) for mid, chan in msgs: channels[chan].append(mid) for channel, messages in list(channels.items()): channel = self.state.channels.get(channel) if not channel: continue channel.delete_messages(messages)
def seen(self, event, user: User): try: msg = Message.select(Message.timestamp).where( Message.author_id == user.id).order_by( Message.timestamp.desc()).limit(1).get() except Message.DoesNotExist: raise CommandFail("I've never seen {}".format(user)) raise CommandSuccess('I last saw {} {}'.format( user, int(msg.timestamp.timestamp())))
def seen(self, event, user): try: msg = Message.select(Message.timestamp).where( Message.author_id == user.id).order_by( Message.timestamp.desc()).limit(1).get() except Message.DoesNotExist: return event.msg.reply(u"I've never seen {}".format(user)) event.msg.reply(u'I last saw {} {} ago (at {})'.format( user, humanize.naturaldelta(datetime.utcnow() - msg.timestamp), msg.timestamp))
def cmd_remind_list(self, event, limit=None, mode='server'): user = event.msg.author count = Reminder.count_for_user(user.id, event.guild.id) total_count = Reminder.count_for_user(user.id) embed = MessageEmbed() embed.title = '{} reminder{} ({} total)'.format( count if mode == 'server' else total_count, 's' if (count != 1 and mode == 'server') or (total_count != 1 and mode == 'global') else '', total_count) embed.set_author(name=u'{}#{}'.format( user.username, user.discriminator, ), icon_url=user.avatar_url) embed.color = get_dominant_colors_user(user, user.get_avatar_url('png')) embed.set_footer(text='You can cancel reminders with !r clear [ID]') if (count == 0 and mode == 'server') or total_count == 0: embed.description = 'You have no upcoming reminders{}.'.format( ' in this server. Use `!r list global` to list all your upcoming reminders' if total_count > 0 else '') else: query = Reminder.select(Reminder).where( (Reminder.message_id << Reminder.with_message_join( (Message.id, )).where( (Message.author_id == event.author.id) & (Message.guild_id == event.guild.id if mode == 'server' else True))) & (Reminder.remind_at > (datetime.utcnow() + timedelta(seconds=1)))).order_by( Reminder.remind_at).limit(limit) for reminder in query: time = humanize_duration(reminder.remind_at - datetime.utcnow()) channel = Message.select().where( Message.id == reminder.message_id).get().channel_id channel = self.state.channels.get(channel) embed.add_field( name=u'#{} in {}'.format(reminder.id, time), value=u'[`#{}`](https://discordapp.com/channels/{}/{}/{}) {}' .format( channel.name if channel.type != ChannelType.DM else 'Jetski', channel.guild_id if channel.type != ChannelType.DM else '@me', channel.id, reminder.message_id, S(reminder.content))) return event.msg.reply(embed=embed)
def seen(self, event, user): try: msg = Message.select(Message.timestamp).where( Message.author_id == user.id).order_by( Message.timestamp.desc()).limit(1).get() except Message.DoesNotExist: raise CommandFail("I've never seen {}".format(user)) raise CommandSuccess( 'I last saw {} {} (at {})'. format( #TODO: Prettify this timestamp response like in inf latest user, humanize.naturaltime(datetime.utcnow() - msg.timestamp), msg.timestamp))
def on_message_reaction_add(self, event): try: # Grab the message, and JOIN across blocks to check if a block exists # for either the message author or the reactor. msg = Message.select( Message, StarboardBlock ).join( StarboardBlock, join_type=JOIN.LEFT_OUTER, on=( ( (Message.author_id == StarboardBlock.user_id) | (StarboardBlock.user_id == event.user_id) ) & (Message.guild_id == StarboardBlock.guild_id) ) ).where( (Message.id == event.message_id) ).get() except Message.DoesNotExist: return # If either the reaction or message author is blocked, prevent this action if msg.starboardblock.user_id: event.delete() return # Check if the board prevents self stars sb_id, board = event.config.get_board(event.channel_id) if not sb_id: return if board.prevent_self_star and msg.author_id == event.user_id: event.delete() return try: StarboardEntry.add_star(event.message_id, event.user_id) except peewee.IntegrityError: msg = self.client.api.channels_messages_get( event.channel_id, event.message_id) if msg: Message.from_disco_message(msg) StarboardEntry.add_star(event.message_id, event.user_id) else: return self.queue_update(event.guild.id, event.config)
def archive(self, event, size=50, mode=None, user=None, channel=None): if 0 > size >= 15000: raise CommandFail('too many messages must be between 1-15000') q = Message.select(Message.id).join(User).order_by(Message.id.desc()).limit(size) if mode in ('all', 'channel'): q = q.where((Message.channel_id == (channel or event.channel).id)) else: q = q.where( (Message.author_id == (user if isinstance(user, (int, long)) else user.id)) & (Message.guild_id == event.guild.id) ) archive = MessageArchive.create_from_message_ids([i.id for i in q]) event.msg.reply('OK, archived {} messages at {}'.format(len(archive.message_ids), archive.url))
def stats(): stats = json.loads(rdb.get('web:dashboard:stats') or '{}') if not stats or 'refresh' in request.args: # stats['messages'] = pretty_number(Message.select().count()) # stats['guilds'] = pretty_number(Guild.select().count()) # stats['users'] = pretty_number(User.select().count()) # stats['channels'] = pretty_number(Channel.select().count()) stats['messages'] = Message.select().count() stats['guilds'] = Guild.select().count() stats['users'] = User.select().count() stats['channels'] = Channel.select().count() rdb.setex('web:dashboard:stats', json.dumps(stats), 300) return jsonify(stats)
def cmd_remind_list(self, event, limit=None, mode=None): user = event.msg.author count = Reminder.count_for_user(user.id) avatar = u'https://cdn.discordapp.com/avatars/{}/{}.png'.format( user.id, user.avatar, ) embed = MessageEmbed() embed.title = '{} reminder{}'.format(count, '' if count == 1 else 's') embed.set_author(name=u'{}#{}'.format( user.username, user.discriminator, ), icon_url=avatar) embed.color = get_dominant_colors_user(user, avatar) embed.set_footer(text='You can cancel reminders with !r clear [ID]') if count == 0: embed.description = 'You have no upcoming reminders.' else: query = Reminder.select(Reminder).where( (Reminder.message_id << Reminder.with_message_join((Message.id, )).where( Message.author_id == event.author.id )) & (Reminder.remind_at > (datetime.utcnow() + timedelta(seconds=1))) ).order_by(Reminder.remind_at).limit(limit) for reminder in query: time = humanize_duration(reminder.remind_at - datetime.utcnow()) channel = Message.select().where(Message.id == reminder.message_id).get().channel_id channel = self.state.channels.get(channel) embed.add_field( name=u'#{} in {}'.format( reminder.id, time ), value=u'[`#{}`](https://discordapp.com/channels/{}/{}/{}) {}'.format( channel.name if channel.type != ChannelType.DM else 'Jetski', channel.guild_id if channel.type != ChannelType.DM else '@me', channel.id, reminder.message_id, S(reminder.content) ) ) return event.msg.reply(embed=embed)
def xp_message_send(self, event): if event.author.bot: return config = self.call('CorePlugin.get_config', event.guild.id) if config and config.commands: commands = list( self.bot.get_commands_for_message(config.commands.mention, {}, config.commands.prefix, event.message)) if commands: return # No XP for commands try: user = GuildMemberLevel.select(GuildMemberLevel, XPBlock).join( XPBlock, on=((GuildMemberLevel.guild_id == XPBlock.guild_id) & (GuildMemberLevel.user_id == XPBlock.user_id))).where( (GuildMemberLevel.user_id == event.author.id) & (GuildMemberLevel.guild_id == event.guild.id)).get() last_message = Message.select(Message.timestamp).where( (Message.author_id == event.author.id) & (Message.guild_id == event.guild.id)).order_by( Message.timestamp.desc()).limit(1).get() if user.xpblock: return # No XP for blocked meanies >:( elif last_message.timestamp < datetime.timedelta(seconds=60): return # Too fast. except GuildMemberLevel.DoesNotExist: user = GuildMemberLevel.create_new( event.guild.get_member(event.author.id)) # lol pre_level = self.level_from_xp(user.xp) new_xp = randint(15, 25) user.add_xp(event.guild.id, event.author.id, new_xp) new_level = self.level_from_xp(user.xp + new_xp) if new_level > pre_level: self.try_levelup(event, new_level)
def start(self): # First, generate a starting point self.log.info('Starting %s backfill on %s going %s', self.mode, self.channel, self.direction) start = None if self.mode in (Backfill.Mode.FULL, Backfill.Mode.SPARSE): # If we are going newest - oldest if self.direction is Backfill.Direction.UP: start = self.channel.last_message_id if not start: self.log.warning('Invalid last_message_id for {}'.format(self.channel)) return else: start = 0 elif self.mode is Backfill.Mode.BACKFILL: q = Message.for_channel(self.channel) if self.direction is Backfill.Direction.UP: q = q.order_by(Message.id.asc()).limit(1).get().id else: q = q.order_by(Message.id.desc()).limit(1).get().id if self.direction is Backfill.Direction.UP: msgs = self.channel.messages_iter(bulk=True, before=start) else: msgs = self.channel.messages_iter(bulk=True, after=start) for chunk in msgs: self.scanned += len(chunk) existing = {i.id for i in Message.select(Message.id).where((Message.id << [i.id for i in chunk]))} if len(existing) < len(chunk): Message.from_disco_message_many([i for i in chunk if i.id not in existing]) self.inserted += len(chunk) - len(existing) if len(existing) and self.mode is Backfill.Mode.BACKFILL: self.log.info('Found %s existing messages, breaking', len(existing)) break if len(existing) == len(chunk) and self.mode is Backfill.Mode.Sparse: self.log.info('Found %s existing messages, breaking', len(existing)) break
def info(self, event, user=None): if user is None: user = event.author user_id = 0 if isinstance(user, (int, long)): user_id = user user = self.state.users.get(user) if user and not user_id: user = self.state.users.get(user.id) if not user: if user_id: user = self.fetch_user(user_id) User.from_disco_user(user) else: raise CommandFail('unknown user') content = [] content.append(u'**\u276F User Information**') content.append(u'ID: {}'.format(user.id)) content.append(u'Profile: <@{}>'.format(user.id)) if user.presence: emoji, status = get_status_emoji(user.presence) content.append('Status: {} <{}>'.format(status, emoji)) game = user.presence.game if game and game.name: activity = ['Playing', 'Stream'][int(game.type)] if game.type < 2 else None if not game.type: if game.name == 'Spotify': activity = 'Listening to' else: activity = None if activity: content.append(u'{}: {}'.format(activity, u'[{}]({})'.format(game.name, game.url) if game.url else game.name )) created_dt = to_datetime(user.id) content.append('Created: {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - created_dt), created_dt.isoformat() )) for i in self.server_owners: if i == str(user.id): content.append('Server Ownership: {}'.format(self.server_owners[i])) for i in self.server_managers: if i == str(user.id): content.append('Community Manager: {}'.format(self.server_managers[i])) if user.id == self.state.me.id: content.append('Documentation: https://aetherya.stream/') elif rdb.sismember('global_admins', user.id): content.append('Airplane Staff: Global Administrator') elif rdb.sismember('server_managers', user.id): content.append('Server Manager') elif rdb.sismember('server_owner', user.id): content.append('Server Owner') member = event.guild.get_member(user.id) if event.guild else None if member: content.append(u'\n**\u276F Member Information**') if member.nick: content.append(u'Nickname: {}'.format(member.nick)) content.append('Joined: {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - member.joined_at), member.joined_at.isoformat(), )) if member.roles: roles = [] for r in member.roles: roles.append(member.guild.roles.get(r)) roles = sorted(roles, key=lambda r: r.position, reverse=True) total = len(member.roles) roles = roles[:20] content.append(u'Roles ({}): {}{}'.format( total, ' '.join(r.mention for r in roles), ' (+{})'.format(total-20) if total > 20 else '' )) # Execute a bunch of queries async newest_msg = Message.select(Message.timestamp).where( (Message.author_id == user.id) & (Message.guild_id == event.guild.id) ).order_by(Message.timestamp.desc()).limit(1).async() # oldest_msg = Message.select(Message.timestamp).where( # (Message.author_id == user.id) & # (Message.guild_id == event.guild.id) # ).order_by(Message.timestamp.asc()).limit(1).async() infractions = Infraction.select( Infraction.guild_id, fn.COUNT('*') ).where( (Infraction.user_id == user.id) & (Infraction.type_ != 6) & # Unban (~(Infraction.reason ** '[NOTE]%')) ).group_by(Infraction.guild_id).tuples().async() voice = GuildVoiceSession.select( GuildVoiceSession.user_id, fn.COUNT('*'), fn.SUM(GuildVoiceSession.ended_at - GuildVoiceSession.started_at) ).where( (GuildVoiceSession.user_id == user.id) & (~(GuildVoiceSession.ended_at >> None)) ).group_by(GuildVoiceSession.user_id).tuples().async() # Wait for them all to complete (we're still going to be as slow as the # slowest query, so no need to be smart about this.) try: wait_many(newest_msg, infractions, voice, timeout=3) except gevent.Timeout: pass tags = to_tags(guild_id=event.msg.guild.id) if newest_msg.value: content.append(u'\n **\u276F Activity**') statsd.timing('sql.duration.newest_msg', newest_msg.value._query_time, tags=tags) newest_msg = newest_msg.value.get() content.append('Last Message: {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - newest_msg.timestamp), newest_msg.timestamp.isoformat(), )) # if oldest_msg.value: # statsd.timing('sql.duration.oldest_msg', oldest_msg.value._query_time, tags=tags) # oldest_msg = oldest_msg.value.get() # content.append('First Message: {} ago ({})'.format( # humanize.naturaldelta(datetime.utcnow() - oldest_msg.timestamp), # oldest_msg.timestamp.isoformat(), # )) if infractions.value: statsd.timing('sql.duration.infractions', infractions.value._query_time, tags=tags) infractions = list(infractions.value) total = sum(i[1] for i in infractions) content.append(u'\n**\u276F Infractions**') content.append('Total Infractions: {}'.format(total)) content.append('Unique Servers: {}'.format(len(infractions))) if voice.value: statsd.timing('plugin.utilities.info.sql.voice', voice.value._query_time, tags=tags) voice = list(voice.value) content.append(u'\n**\u276F Voice**') content.append(u'Sessions: {}'.format(voice[0][1])) content.append(u'Time: {}'.format(humanize.naturaldelta( voice[0][2] ))) embed = MessageEmbed() avatar = user.avatar if avatar: avatar = u'https://cdn.discordapp.com/avatars/{}/{}.{}'.format( user.id, avatar, u'gif' if avatar.startswith('a_') else u'png' ) else: avatar = u'https://cdn.discordapp.com/embed/avatars/{}.png'.format( int(user.discriminator) % 5 ) embed.set_author(name=u'{}#{}'.format( user.username, user.discriminator, ), icon_url=avatar) embed.set_thumbnail(url=avatar) embed.description = '\n'.join(content) try: embed.color = get_dominant_colors_user(user, avatar) except: pass event.msg.reply('', embed=embed)
def info(self, event, user=None): if not user: user = event.author else: if not isinstance(user, DiscoUser): try: user = self.state.guilds[event.guild.id].members[user].user except KeyError: try: user = self.state.users[user] except KeyError: try: user = self.bot.client.api.users_get(user) except APIException: return event.msg.reply( 'User not found :eyes:').after(3).delete() self.client.api.channels_typing(event.channel.id) content = [] content.append('**\u276F User Information**') content.append('Profile: <@{}>'.format(user.id)) created_dt = to_datetime(user.id) content.append('Created: {} ({})'.format( humanize.naturaltime(datetime.utcnow() - created_dt), created_dt.strftime("%b %d %Y %H:%M:%S"))) member = event.guild.get_member(user.id) if event.guild else None if user.presence: #I couldn't get this to work w/o it lol emoji, status = get_status_emoji(user.presence) content.append('Status: <{}> {}'.format(emoji, status)) if user.presence.game and user.presence.game.name: if user.presence.game.type == ActivityTypes.DEFAULT: content.append('{}'.format(user.presence.game.name)) if user.presence.game.type == ActivityTypes.CUSTOM: content.append('Custom Status: {}'.format( user.presence.game.state)) if user.presence.game.type == ActivityTypes.LISTENING: content.append('Listening to {} on Spotify'.format( user.presence.game.details) ) #In the embed, details is the songname. if user.presence.game.type == ActivityTypes.STREAMING: content.append('Streaming: [{}]({})'.format( user.presence.game.name, user.presence.game.url)) if user.public_flags: badges = '' user_badges = list(UserFlags(user.public_flags)) for badge in user_badges: badges += '<{}> '.format(BADGE_EMOJI[badge]) content.append('Badges: {}'.format(badges)) if member: content.append('\n**\u276F Member Information**') if member.nick: content.append('Nickname: {}'.format(member.nick)) content.append('Joined: {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - member.joined_at), member.joined_at.strftime("%b %d %Y %H:%M:%S"), )) if member.roles: content.append('Roles: {}'.format(', '.join( ('<@&{}>'.format(member.guild.roles.get(r).id) for r in member.roles)))) # Execute a bunch of queries newest_msg = Message.select(fn.MAX(Message.id)).where( (Message.author_id == user.id) & (Message.guild_id == event.guild.id)).tuples()[0][0] oldest_msg = Message.select(fn.MIN(Message.id)).where( (Message.author_id == user.id) & (Message.guild_id == event.guild.id)).tuples()[0][0] #Slow Query voice = GuildVoiceSession.select( fn.COUNT(GuildVoiceSession.user_id), fn.SUM(GuildVoiceSession.ended_at - GuildVoiceSession.started_at) ).where((GuildVoiceSession.user_id == user.id) & (~(GuildVoiceSession.ended_at >> None)) & (GuildVoiceSession.guild_id == event.guild.id)).tuples()[0] infractions = Infraction.select(Infraction.id).where( (Infraction.user_id == user.id) & (Infraction.guild_id == event.guild.id)).tuples() if newest_msg and oldest_msg: content.append('\n **\u276F Activity**') content.append('Last Message: {} ({})'.format( humanize.naturaltime(datetime.utcnow() - to_datetime(newest_msg)), to_datetime(newest_msg).strftime("%b %d %Y %H:%M:%S"), )) content.append('First Message: {} ({})'.format( humanize.naturaltime(datetime.utcnow() - to_datetime(oldest_msg)), to_datetime(oldest_msg).strftime("%b %d %Y %H:%M:%S"), )) if len(infractions) > 0: content.append('\n**\u276F Infractions**') total = len(infractions) content.append('Total Infractions: **{:,}**'.format(total)) if voice[0]: content.append('\n**\u276F Voice**') content.append('Sessions: `{:,}`'.format(voice[0])) content.append('Time: `{}`'.format( str(humanize.naturaldelta(voice[1])).title())) embed = MessageEmbed() try: avatar = User.with_id(user.id).get_avatar_url() except: avatar = user.get_avatar_url( ) # This fails if the user has never been seen by speedboat. embed.set_author(name='{}#{} ({})'.format( user.username, user.discriminator, user.id, ), icon_url=avatar) embed.set_thumbnail(url=avatar) embed.description = '\n'.join(content) embed.color = get_dominant_colors_user(user, avatar) event.msg.reply('', embed=embed)
def check_advanced(self, event, member, rule): scores = [] marks = [] def mark(amount, reason): scores.append(amount) marks.append(reason) # CHECK 1 # Check if the user just exited their quiescent period from guild verification # which means they may have been waiting to spam duration_before_talk = 0 if event.guild.verification_level == VerificationLevel.MEDIUM: duration_before_talk = 60 * 5 elif event.guild.verification_level == VerificationLevel.HIGH: duration_before_talk = 60 * 10 if duration_before_talk: duration = (datetime.utcnow() - member.joined_at).seconds if duration >= duration_before_talk: if (duration - duration_before_talk) < 10: mark(10, 'check1.talk_within_ten_seconds') elif (duration - duration_before_talk) < 60: mark(5, 'check1.talk_within_sixty_seconds') elif (duration - duration_before_talk) < 120: mark(3, 'check1.talk_within_two_minutes') elif (duration - duration_before_talk) < 300: mark(1, 'check1.talk_within_five_minutes') # CHECK 2 # Check if the users account was created recently, which means they may # have made it just to spam. account_age = (datetime.utcnow() - to_datetime(event.author.id)).seconds if account_age < 15 * 60: mark(5, 'check2.account_age_less_than_fifteen_minutes') elif account_age < 30 * 60: mark(3, 'check2.account_age_less_than_thirty_minutes') elif account_age < 60 * 60: mark(1, 'check2.account_age_less_than_one_hour') # CHECK 3 # Check if this is the first message sent by the user, perhaps signaling # they just joined to spam sent_messages = Message.select().where( (Message.guild_id == event.guild.id) & (Message.author_id == event.author.id)).count() if sent_messages == 0: mark(10, 'check3.first_message_in_server') elif sent_messages < 10: mark(3, 'check3.first_ten_messages_in_server') # CHECK 4 # For every user mentioned in their message, determine how "important" # or likely to be spammed they are. for mention in event.mentions.values(): member = event.guild.get_member(mention) # If the user is an admin of the server, they are likely to be a victim if member.owner: mark(7, 'check4.mentions_owner') if member.permissions.administrator or member.permissions.manage_guild: mark(5, 'check4.mentions_administrator') elif member.permissions.ban_members or member.permissions.kick_members: mark(1, 'check4.mentions_moderator') # If the user is hoisted, they are likely to be a victim if any(i.hoist for i in map(event.guild.roles.get, member.roles)): mark(7, 'check4.mentions_hoisted_user') # CHECK 5 # Check how many bad words are in the message, generally low-effort spammers # just shove "shock" value content in their message. num_bad_words = sum(1 for word in event.content.split(' ') if word in BAD_WORDS) if num_bad_words: mark(num_bad_words, 'check5.has_bad_words_%s' % num_bad_words) TempSpamScore.track(event.id, sum(scores), marks)
def violate(self, violation): key = 'lv:{e.member.guild_id}:{e.member.id}'.format(e=violation.event) last_violated = int(rdb.get(key) or 0) rdb.setex( 'lv:{e.member.guild_id}:{e.member.id}'.format(e=violation.event), int(time.time()), 60) if not last_violated > time.time() - 10: self.call('ModLogPlugin.log_action_ext', Actions.SPAM_DEBUG, violation.event.guild.id, v=violation) with self.bot.plugins.get( 'CorePlugin').send_spam_control_message() as embed: embed.title = '{} Violated'.format(violation.label) embed.color = 0xfdfd96 embed.description = violation.msg embed.add_field(name='Guild', value=violation.event.guild.name, inline=True) embed.add_field(name='Guild ID', value=violation.event.guild.id, inline=True) embed.add_field(name=ZERO_WIDTH_SPACE, value=ZERO_WIDTH_SPACE, inline=True) embed.add_field(name='User', value=unicode(violation.member), inline=True) embed.add_field(name='User ID', value=violation.event.member.id, inline=True) embed.add_field(name=ZERO_WIDTH_SPACE, value=ZERO_WIDTH_SPACE, inline=True) punishment = violation.check.punishment or violation.rule.punishment punishment_duration = violation.check.punishment_duration or violation.rule.punishment_duration if punishment == PunishmentType.MUTE: if violation.rule.punishment_dms: try: infractions, embed = infraction_message( violation.event, violation.member.id, 'mute', violation.event.guild.name, str(self.state.me), 'Spam Detected', auto=True) dm = self.client.api.users_me_dms_create( violation.member.id) dm.send_message('You\'ve been {} in **{}**.'.format( 'muted', violation.event.guild.name), embed=embed) except APIException: pass Infraction.mute(self, violation.event, violation.member, 'Spam Detected') elif punishment == PunishmentType.TEMPMUTE: expiration_date = datetime.utcnow() + timedelta( seconds=punishment_duration) if violation.rule.punishment_dms: try: infractions, embed = infraction_message( violation.event, violation.member.id, 'tempmute', violation.event.guild.name, str(self.state.me), 'Spam Detected', expires=expiration_date, auto=True) dm = self.client.api.users_me_dms_create( violation.member.id) dm.send_message('You\'ve been {} in **{}**.'.format( 'temporarily muted', violation.event.guild.name), embed=embed) except APIException: pass Infraction.tempmute(self, violation.event, violation.member, 'Spam Detected', expiration_date) elif punishment == PunishmentType.KICK: if violation.rule.punishment_dms: try: infractions, embed = infraction_message( violation.event, violation.member.id, 'kick', violation.event.guild.name, str(self.state.me), 'Spam Detected', auto=True) dm = self.client.api.users_me_dms_create( violation.member.id) dm.send_message('You\'ve been {} from **{}**.'.format( 'kicked', violation.event.guild.name), embed=embed) except APIException: pass Infraction.kick(self, violation.event, violation.member, 'Spam Detected') elif punishment == PunishmentType.TEMPBAN: expiration_date = datetime.utcnow() + timedelta( seconds=punishment_duration) if violation.rule.punishment_dms: try: infractions, embed = infraction_message( violation.event, violation.member.id, 'tempban', violation.event.guild.name, str(self.state.me), 'Spam Detected', expires=expiration_date, auto=True) dm = self.client.api.users_me_dms_create( violation.member.id) dm.send_message('You\'ve been {} from **{}**.'.format( 'temporarily banned', violation.event.guild.name), embed=embed) except APIException: pass Infraction.tempban(self, violation.event, violation.member, 'Spam Detected', expiration_date) elif punishment == PunishmentType.BAN: if violation.rule.punishment_dms: try: infractions, embed = infraction_message( violation.event, violation.member.id, 'ban', violation.event.guild.name, str(self.state.me), 'Spam Detected', auto=True) dm = self.client.api.users_me_dms_create( violation.member.id) dm.send_message('You\'ve been {} from **{}**.'.format( 'banned', violation.event.guild.name), embed=embed) except APIException: pass Infraction.ban(self, violation.event, violation.member, 'Spam Detected', violation.event.guild) # Clean messages if requested if punishment != PunishmentType.NONE and violation.rule.clean: msgs = Message.select(Message.id, Message.channel_id).where( (Message.guild_id == violation.event.guild.id) & (Message.author_id == violation.member.id) & (Message.timestamp > (datetime.utcnow() - timedelta( seconds=violation.rule.clean_duration)))).limit( violation.rule.clean_count).tuples() channels = defaultdict(list) for mid, chan in msgs: channels[chan].append(mid) for channel, messages in channels.items(): channel = self.state.channels.get(channel) if not channel: continue channel.delete_messages(messages)
def msgstats(self, event, user): # Query for the basic aggregate message statistics message_stats = Message.select( fn.Count('*'), fn.Sum(fn.char_length(Message.content)), fn.Sum(fn.array_length(Message.emojis, 1)), fn.Sum(fn.array_length(Message.mentions, 1)), fn.Sum(fn.array_length(Message.attachments, 1)), ).where( (Message.author_id == user.id) ).tuples().async() reactions_given = Reaction.select( fn.Count('*'), Reaction.emoji_id, Reaction.emoji_name, ).join( Message, on=(Message.id == Reaction.message_id) ).where( (Reaction.user_id == user.id) ).group_by( Reaction.emoji_id, Reaction.emoji_name ).order_by(fn.Count('*').desc()).tuples().async() # Query for most used emoji emojis = Message.raw(''' SELECT gm.emoji_id, gm.name, count(*) FROM ( SELECT unnest(emojis) as id FROM messages WHERE author_id=%s ) q JOIN guild_emojis gm ON gm.emoji_id=q.id GROUP BY 1, 2 ORDER BY 3 DESC LIMIT 1 ''', (user.id, )).tuples().async() deleted = Message.select( fn.Count('*') ).where( (Message.author_id == user.id) & (Message.deleted == 1) ).tuples().async() wait_many(message_stats, reactions_given, emojis, deleted, timeout=10) # If we hit an exception executing the core query, throw an exception if message_stats.exception: message_stats.get() q = message_stats.value[0] embed = MessageEmbed() embed.fields.append( MessageEmbedField(name='Total Messages Sent', value=q[0] or '0', inline=True)) embed.fields.append( MessageEmbedField(name='Total Characters Sent', value=q[1] or '0', inline=True)) if deleted.value: embed.fields.append( MessageEmbedField(name='Total Deleted Messages', value=deleted.value[0][0], inline=True)) embed.fields.append( MessageEmbedField(name='Total Custom Emojis', value=q[2] or '0', inline=True)) embed.fields.append( MessageEmbedField(name='Total Mentions', value=q[3] or '0', inline=True)) embed.fields.append( MessageEmbedField(name='Total Attachments', value=q[4] or '0', inline=True)) if reactions_given.value: reactions_given = reactions_given.value embed.fields.append( MessageEmbedField(name='Total Reactions', value=sum(i[0] for i in reactions_given), inline=True)) emoji = ( reactions_given[0][2] if not reactions_given[0][1] else '<:{}:{}>'.format(reactions_given[0][2], reactions_given[0][1]) ) embed.fields.append( MessageEmbedField(name='Most Used Reaction', value=u'{} (used {} times)'.format( emoji, reactions_given[0][0], ), inline=True)) if emojis.value: emojis = list(emojis.value) if emojis: embed.add_field( name='Most Used Emoji', value=u'<:{1}:{0}> (`{1}`, used {2} times)'.format(*emojis[0])) embed.thumbnail = MessageEmbedThumbnail(url=user.avatar_url) embed.color = get_dominant_colors_user(user) event.msg.reply('', embed=embed)
def info(self, event, user: User = None): if not user: user = event.author else: if not isinstance(user, DiscoUser): try: user = self.state.guilds[event.guild.id].members[user].user except KeyError: try: user = self.state.users[user] except KeyError: try: user = self.bot.client.api.users_get(user) except APIException: return event.msg.reply( ':eyes: User not found').after(3).delete() self.client.api.channels_typing(event.channel.id) content = [] content.append('**\u276F User Information**') content.append('Profile: <@{}>'.format(user.id)) created_dt = to_datetime(user.id) content.append('Created: <t:{0}:R> (<t:{0}:f>)'.format( int(created_dt.replace(tzinfo=pytz.UTC).timestamp()))) member = event.guild.get_member(user.id) if event.guild else None if user.public_flags: badges = '' user_badges = list(UserFlags(user.public_flags)) for badge in user_badges: badges += '<{}> '.format(BADGE_EMOJI[badge]) content.append('Badges: {}'.format(badges)) if member: content.append('\n**\u276F Member Information**') if member.nick: content.append('Nickname: {}'.format(member.nick)) content.append('Joined: <t:{0}:R> (<t:{0}:f>)'.format( int(member.joined_at.replace(tzinfo=pytz.UTC).timestamp()))) content.append('Messages: {}'.format( int( Message.select(fn.Count( Message.id)).where((Message.author_id == user.id) & (Message.guild_id == event.guild. id)).tuples()[0][0]))) if member.roles: content.append('Roles: {}'.format(', '.join( ('<@&{}>'.format(r) for r in member.roles)))) # Execute a bunch of queries newest_msg = Message.select(fn.MAX(Message.id)).where( (Message.author_id == user.id) & (Message.guild_id == event.guild.id)).tuples()[0][0] infractions = Infraction.select(Infraction.id).where( (Infraction.user_id == user.id) & (Infraction.guild_id == event.guild.id)).tuples() if newest_msg: content.append('\n **\u276F Activity**') content.append('Last Message: <t:{0}:R> (<t:{0}:f>)'.format( int((to_datetime(newest_msg).replace( tzinfo=pytz.UTC)).timestamp()))) # content.append('First Message: {} ({})'.format( # humanize.naturaltime(datetime.utcnow() - to_datetime(oldest_msg)), # to_datetime(oldest_msg).strftime("%b %d %Y %H:%M:%S"), # )) if len(infractions) > 0: content.append('\n**\u276F Infractions**') total = len(infractions) content.append('Total Infractions: **{:,}**'.format(total)) embed = MessageEmbed() try: avatar = User.with_id(user.id).get_avatar_url() except APIException: avatar = user.get_avatar_url( ) # This fails if the user has never been seen by speedboat. embed.set_author(name='{} ({})'.format( str(user), user.id, ), icon_url=avatar) embed.set_thumbnail(url=avatar) embed.description = '\n'.join(content) embed.color = get_dominant_colors_user(user, avatar) event.msg.reply('', embed=embed)
def info(self, event, user=None): if user is None: user = event.author user_id = 0 if isinstance(user, (int, long)): user_id = user user = self.state.users.get(user) if user and not user_id: user = self.state.users.get(user.id) if not user: if user_id: try: user = self.client.api.users_get(user_id) except APIException: raise CommandFail('unknown user') User.from_disco_user(user) else: raise CommandFail('unknown user') self.client.api.channels_typing(event.channel.id) content = [] content.append(u'**\u276F User Information**') content.append(u'**ID:** {}'.format(user.id)) content.append(u'**Profile:** <@{}>'.format(user.id)) if user.presence: emoji, status = get_status_emoji(user.presence) content.append('**Status:** {} <{}>'.format(status, emoji)) game = user.presence.game if game and game.name: activity = ['Playing', 'Stream', 'Listening to', 'Watching', 'Custom Status'][int(game.type or 0)] if not game.type: activity = None if activity: game_name = game.state if game.type == GameType.CUSTOM_STATUS else game.name content.append(u'**{}:** {}'.format(activity, u'[{}]({})'.format(game_name, game.url) if game.url else game_name )) if user.public_flags and user.public_flags != 0: flags = [] for flag, emoji in BADGE_EMOJI.items(): if user.public_flags.check(flag): flags.append('<{}>'.format(emoji)) if len(flags) > 0: content.append('**Badges**: {}'.format(' '.join(flags))) created_dt = to_datetime(user.id) content.append('**Created:** {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - created_dt), created_dt.isoformat() )) member = event.guild.get_member(user.id) if event.guild else None if member: content.append(u'\n**\u276F Member Information**') if member.nick: content.append(u'**Nickname:** {}'.format(member.nick)) content.append('**Joined:** {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - member.joined_at), member.joined_at.isoformat(), )) if member.roles: content.append(u'**Roles:** {}'.format( ' '.join((member.guild.roles.get(r).mention for r in sorted(member.roles, key=lambda r: member.guild.roles.get(r).position, reverse=True))) )) # "is not None" does not work with Unset types for some reason if bool(member.premium_since): content.append('**Boosting since:** {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - member.premium_since), member.premium_since.isoformat(), )) # Execute a bunch of queries async newest_msg = Message.select(Message.timestamp).where( (Message.author_id == user.id) & (Message.guild_id == event.guild.id) ).limit(1).order_by(Message.timestamp.desc()).async() oldest_msg = Message.select(Message.timestamp).where( (Message.author_id == user.id) & (Message.guild_id == event.guild.id) ).limit(1).order_by(Message.timestamp.asc()).async() infractions = Infraction.select( Infraction.guild_id, fn.COUNT('*') ).where( (Infraction.user_id == user.id) ).group_by(Infraction.guild_id).tuples().async() voice = GuildVoiceSession.select( GuildVoiceSession.user_id, fn.COUNT('*'), fn.SUM(GuildVoiceSession.ended_at - GuildVoiceSession.started_at) ).where( (GuildVoiceSession.user_id == user.id) & (~(GuildVoiceSession.ended_at >> None)) ).group_by(GuildVoiceSession.user_id).tuples().async() # Wait for them all to complete (we're still going to be as slow as the # slowest query, so no need to be smart about this.) wait_many(newest_msg, oldest_msg, infractions, voice, timeout=10) tags = to_tags(guild_id=event.msg.guild.id) if newest_msg.value and oldest_msg.value: statsd.timing('sql.duration.newest_msg', newest_msg.value._query_time, tags=tags) statsd.timing('sql.duration.oldest_msg', oldest_msg.value._query_time, tags=tags) newest_msg = newest_msg.value.get() oldest_msg = oldest_msg.value.get() content.append(u'\n **\u276F Activity**') content.append('**Last Message:** {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - newest_msg.timestamp), newest_msg.timestamp.isoformat(), )) content.append('**First Message:** {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - oldest_msg.timestamp), oldest_msg.timestamp.isoformat(), )) if infractions.value: statsd.timing('sql.duration.infractions', infractions.value._query_time, tags=tags) infractions = list(infractions.value) total = sum(i[1] for i in infractions) content.append(u'\n**\u276F Infractions**') content.append('**Total Infractions:** {:,}'.format(total)) content.append('**Unique Servers:** {}'.format(len(infractions))) if voice.value: statsd.timing('plugin.utilities.info.sql.voice', voice.value._query_time, tags=tags) voice = list(voice.value) content.append(u'\n**\u276F Voice**') content.append(u'**Sessions:** {:,}'.format(voice[0][1])) content.append(u'**Time:** {}'.format(humanize.naturaldelta( voice[0][2] ))) embed = MessageEmbed() avatar = user.avatar if avatar: avatar = user.avatar_url else: avatar = u'https://cdn.discordapp.com/embed/avatars/{}.png'.format( int(user.discriminator) % 5 ) embed.set_author(name=u'{}#{}'.format( user.username, user.discriminator, ), icon_url=avatar) embed.set_thumbnail(url=user.avatar_url if user.avatar else avatar) embed.description = '\n'.join(content) embed.color = get_dominant_colors_user(user, user.get_avatar_url('png') if user.avatar else avatar) event.msg.reply('', embed=embed)
def info(self, event, user): if isinstance(user, (int, long)): try: r = self.bot.client.api.http(Routes.USERS_GET, dict(user=user)) # hacky method cause this old version of Disco doesn't have a method for this and we're too lazy to update data = r.json() User = namedtuple('User', [ 'avatar', 'discriminator', 'id', 'username', 'presence' ]) user = User( avatar=data["avatar"], discriminator=data["discriminator"], id=int(data["id"]), username=data["username"], presence=None ) except APIException as e: raise CommandFail('invalid user') content = [] content.append(u'**\u276F User Information**') content.append(u'ID: {}'.format(user.id)) content.append(u'Profile: <@{}>'.format(user.id)) if user.presence: emoji, status = get_status_emoji(user.presence) content.append('Status: {} <{}>'.format(status, emoji)) if user.presence.game and user.presence.game.name: if user.presence.game.type == GameType.DEFAULT: content.append(u'Game: {}'.format(user.presence.game.name)) else: content.append(u'Stream: [{}]({})'.format(user.presence.game.name, user.presence.game.url)) created_dt = to_datetime(user.id) content.append('Created: {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - created_dt), created_dt.isoformat() )) member = event.guild.get_member(user.id) if event.guild else None if member: content.append(u'\n**\u276F Member Information**') if member.nick: content.append(u'Nickname: {}'.format(member.nick)) content.append('Joined: {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - member.joined_at), member.joined_at.isoformat(), )) if member.roles: content.append(u'Roles: {}'.format( ', '.join((member.guild.roles.get(r).name for r in member.roles)) )) # Execute a bunch of queries async newest_msg = Message.select(Message.timestamp).where( (Message.author_id == user.id) & (Message.guild_id == event.guild.id) ).limit(1).order_by(Message.timestamp.desc()).async() oldest_msg = Message.select(Message.timestamp).where( (Message.author_id == user.id) & (Message.guild_id == event.guild.id) ).limit(1).order_by(Message.timestamp.asc()).async() infractions = Infraction.select( Infraction.guild_id, fn.COUNT('*') ).where( (Infraction.user_id == user.id) ).group_by(Infraction.guild_id).tuples().async() voice = GuildVoiceSession.select( GuildVoiceSession.user_id, fn.COUNT('*'), fn.SUM(GuildVoiceSession.ended_at - GuildVoiceSession.started_at) ).where( (GuildVoiceSession.user_id == user.id) & (~(GuildVoiceSession.ended_at >> None)) ).group_by(GuildVoiceSession.user_id).tuples().async() # Wait for them all to complete (we're still going to be as slow as the # slowest query, so no need to be smart about this.) wait_many(newest_msg, oldest_msg, infractions, voice, timeout=10) tags = to_tags(guild_id=event.msg.guild.id) if newest_msg.value and oldest_msg.value: statsd.timing('sql.duration.newest_msg', newest_msg.value._query_time, tags=tags) statsd.timing('sql.duration.oldest_msg', oldest_msg.value._query_time, tags=tags) newest_msg = newest_msg.value.get() oldest_msg = oldest_msg.value.get() content.append(u'\n **\u276F Activity**') content.append('Last Message: {} ago ({})'.format( humanize_duration(datetime.utcnow() - newest_msg.timestamp), newest_msg.timestamp.isoformat(), )) content.append('First Message: {} ago ({})'.format( humanize_duration(datetime.utcnow() - oldest_msg.timestamp), oldest_msg.timestamp.isoformat(), )) if infractions.value: statsd.timing('sql.duration.infractions', infractions.value._query_time, tags=tags) infractions = list(infractions.value) total = sum(i[1] for i in infractions) content.append(u'\n**\u276F Infractions**') content.append('Total Infractions: {}'.format(total)) content.append('Unique Servers: {}'.format(len(infractions))) if voice.value: statsd.timing('plugin.utilities.info.sql.voice', voice.value._query_time, tags=tags) voice = list(voice.value) content.append(u'\n**\u276F Voice**') content.append(u'Sessions: {}'.format(voice[0][1])) content.append(u'Time: {}'.format(humanize.naturaldelta( voice[0][2] ))) embed = MessageEmbed() avatar = u'https://cdn.discordapp.com/avatars/{}/{}.png'.format( user.id, user.avatar, ) embed.set_author(name=u'{}#{}'.format( user.username, user.discriminator, ), icon_url=avatar) embed.set_thumbnail(url=avatar) embed.description = '\n'.join(content) embed.color = get_dominant_colors_user(user, avatar) event.msg.reply('', embed=embed)
def guild_stats_self(guild): def serialize_user(gcc): for i in gcc: user_raw = ''' SELECT username, discriminator FROM users WHERE user_id=%s AND bot=false; ''' user = list(User.raw(user_raw, i[1]).tuples()) if user: return { 'user': { 'username': user[0][0], 'discrim': str(user[0][1]), 'id': i[1] }, 'user_count': int(i[0]), } return { 'user': '******', 'user_count': 0, } def serialize_emoji(gcc): for i in gcc: emoji_raw = ''' SELECT emoji_id FROM guild_emojis WHERE emoji_id=%s AND guild_id=%s; ''' emoji = list( GuildEmoji.raw(emoji_raw, i[0], guild.guild_id).tuples()) if emoji: return str(emoji[0][0]) return '230870076126003200' data = json.loads( rdb.get('web:guild:{}:stats'.format(guild.guild_id)) or '{}') if not data: # Totals totals_messages = Message.select(Message.id).where( (Message.guild_id == guild.guild_id)).count() totals_infractions = Infraction.select(Infraction.id).where( (Infraction.guild_id == guild.guild_id)).count() # Peaks ## Messages peaks_messages_raw = ''' SELECT count(id), author_id FROM messages WHERE guild_id=%s GROUP BY author_id ORDER BY count DESC LIMIT 5; ''' peaks_messages = list( Message.raw(peaks_messages_raw, guild.guild_id).tuples()) ## Infractions peaks_infractions_raw = ''' SELECT count(id), user_id FROM infractions WHERE guild_id=%s GROUP BY user_id ORDER BY count DESC LIMIT 5; ''' peaks_infractions = list( Infraction.raw(peaks_infractions_raw, guild.guild_id).tuples()) ## Emoji peaks_emoji_raw = ''' SELECT id, count(*) FROM ( SELECT unnest(emojis) as id FROM messages WHERE guild_id=%s and cardinality(emojis) > 0 ) q GROUP BY 1 ORDER BY 2 DESC LIMIT 5 ''' peaks_emoji = list( Message.raw(peaks_emoji_raw, guild.guild_id).tuples()) ## Command peaks_command_raw = ''' SELECT count(c.command), c.command FROM commands c INNER JOIN messages m ON (c.message_id = m.id) WHERE m.guild_id=%s GROUP BY 2 ORDER BY 1 DESC LIMIT 1; ''' peaks_command = list( Command.raw(peaks_command_raw, guild.guild_id).tuples()) if totals_messages: totals_messages = totals_messages else: totals_messages = 0 if totals_infractions: totals_infractions = totals_infractions else: totals_infractions = 0 if peaks_messages: pm = serialize_user(peaks_messages) else: pm = { 'user': '******', 'user_count': 0, } if peaks_infractions: pi = serialize_user(peaks_infractions) else: pi = { 'user': '******', 'user_count': 0, } if peaks_emoji: anim = False peaks_emoji_id = serialize_emoji(peaks_emoji) url = 'https://discordapp.com/api/emojis/{}.gif'.format( peaks_emoji_id) r = requests.get(url) try: r.raise_for_status() anim = True except requests.HTTPError: pass if anim: peaks_emoji_ext = 'gif' else: peaks_emoji_ext = 'png' else: peaks_emoji_id = '230870076126003200' peaks_emoji_ext = 'png' if peaks_command: peaks_command = '{1}'.format(*peaks_command[0]) else: peaks_command = 'N/A' data = { 'totals': { 'messages': totals_messages, 'infractions': totals_infractions, }, 'peaks': { 'messages': pm, 'infractions': pi, 'emoji': { 'id': peaks_emoji_id, 'ext': peaks_emoji_ext, }, 'command': peaks_command, }, } rdb.setex('web:guild:{}:stats'.format(guild.guild_id), json.dumps(data), 600) return jsonify(data)
def info(self, event, user): content = [] content.append(u'**\u276F User Information**') if user.presence: emoji, status = get_status_emoji(user.presence) content.append('Status: {} <{}>'.format(status, emoji)) if user.presence.game and user.presence.game.name: if user.presence.game.type == GameType.DEFAULT: content.append(u'Game: {}'.format(user.presence.game.name)) else: content.append(u'Stream: [{}]({})'.format( user.presence.game.name, user.presence.game.url)) created_dt = to_datetime(user.id) content.append('Created: {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - created_dt), created_dt.isoformat())) member = event.guild.get_member(user.id) if event.guild else None if member: content.append(u'\n**\u276F Member Information**') if member.nick: content.append(u'Nickname: {}'.format(member.nick)) content.append('Joined: {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - member.joined_at), member.joined_at.isoformat(), )) if member.roles: content.append(u'Roles: {}'.format(', '.join( (member.guild.roles.get(r).name for r in member.roles)))) try: newest_msg = Message.select(Message.timestamp).where( (Message.author_id == user.id) & (Message.guild_id == event.guild.id)).order_by( Message.timestamp.desc()).get() oldest_msg = Message.select(Message.timestamp).where( (Message.author_id == user.id) & (Message.guild_id == event.guild.id)).order_by( Message.timestamp.asc()).get() content.append(u'\n **\u276F Activity**') content.append('Last Message: {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - newest_msg.timestamp), newest_msg.timestamp.isoformat(), )) content.append('First Message: {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - oldest_msg.timestamp), oldest_msg.timestamp.isoformat(), )) except Message.DoesNotExist: pass infractions = list( Infraction.select(Infraction.guild_id, fn.COUNT('*')).where( (Infraction.user_id == user.id)).group_by( Infraction.guild_id).tuples()) if infractions: total = sum(i[1] for i in infractions) content.append(u'\n**\u276F Infractions**') content.append('Total Infractions: {}'.format(total)) content.append('Unique Servers: {}'.format(len(infractions))) voice = list( GuildVoiceSession.select( GuildVoiceSession.user_id, fn.COUNT('*'), fn.SUM(GuildVoiceSession.ended_at - GuildVoiceSession.started_at)).where( (GuildVoiceSession.user_id == user.id) & (~(GuildVoiceSession.ended_at >> None))).group_by( GuildVoiceSession.user_id).tuples()) if voice: content.append(u'\n**\u276F Voice**') content.append(u'Sessions: {}'.format(voice[0][1])) content.append(u'Time: {}'.format( humanize.naturaldelta(voice[0][2]))) embed = MessageEmbed() avatar = u'https://cdn.discordapp.com/avatars/{}/{}.png'.format( user.id, user.avatar, ) embed.set_author(name=u'{}#{} (<@{}>)'.format( user.username, user.discriminator, user.id, ), icon_url=avatar) embed.set_thumbnail(url=avatar) embed.description = '\n'.join(content) embed.color = get_dominant_colors_user(user, avatar) event.msg.reply('', embed=embed)
def violate(self, violation): key = 'lv:{e.member.guild_id}:{e.member.id}'.format(e=violation.event) last_violated = int(rdb.get(key) or 0) rdb.setex( 'lv:{e.member.guild_id}:{e.member.id}'.format(e=violation.event), int(time.time()), 60) if not last_violated > time.time() - 10: self.bot.plugins.get('ModLogPlugin').log_action_ext( Actions.SPAM_DEBUG, violation.event, v=violation) with self.bot.plugins.get( 'CorePlugin').send_control_message() as embed: embed.title = '{} Violated'.format(violation.label) embed.color = 0xfdfd96 embed.description = violation.msg embed.add_field(name='Guild', value=violation.event.guild.name, inline=True) embed.add_field(name='Guild ID', value=violation.event.guild.id, inline=True) embed.add_field(name=ZERO_WIDTH_SPACE, value=ZERO_WIDTH_SPACE, inline=True) embed.add_field(name='User', value=unicode(violation.member), inline=True) embed.add_field(name='User ID', value=violation.event.member.id, inline=True) embed.add_field(name=ZERO_WIDTH_SPACE, value=ZERO_WIDTH_SPACE, inline=True) punishment = violation.check.punishment or violation.rule.punishment punishment_duration = violation.check.punishment_duration or violation.rule.punishment_duration if punishment == PunishmentType.MUTE: Infraction.mute(self, violation.event, violation.member, 'Spam Detected') elif punishment == PunishmentType.TEMPMUTE: Infraction.tempmute( self, violation.event, violation.member, 'Spam Detected', datetime.utcnow() + timedelta(seconds=punishment_duration)) elif punishment == PunishmentType.KICK: Infraction.kick(self, violation.event, violation.member, 'Spam Detected') elif punishment == PunishmentType.TEMPBAN: Infraction.tempban( self, violation.event, violation.member, 'Spam Detected', datetime.utcnow() + timedelta(seconds=punishment_duration)) elif punishment == PunishmentType.BAN: Infraction.ban(self, violation.event, violation.member, 'Spam Detected', violation.event.guild) # Clean messages if requested if punishment != PunishmentType.NONE and violation.rule.clean: msgs = Message.select(Message.id, Message.channel_id).where( (Message.guild_id == violation.event.guild.id) & (Message.author_id == violation.member.id) & (Message.timestamp > (datetime.utcnow() - timedelta( seconds=violation.rule.clean_duration)))).limit( violation.rule.clean_count).tuples() channels = defaultdict(list) for mid, chan in msgs: channels[chan].append(mid) for channel, messages in channels.items(): channel = self.state.channels.get(channel) if not channel: continue channel.delete_messages(messages)
def messageinfo(self, event, mid): try: msg = Message.select(Message).where( (Message.id == mid) ).get() except Message.DoesNotExist: raise CommandFail('the id specified does not exist in our message database.') message_content = msg.content author_id = msg.author.id guild_id = msg.guild_id channel_id = msg.channel_id deleted = msg.deleted num_edits = msg.num_edits if num_edits > 0: num_edits_bool = True else: num_edits_bool = False discrim = str(msg.author.discriminator) # if len(str(cached_name[1])) != 4: # while len(str(temp_str)) < 4: # temp_str = '0' + str(temp_str) cached_name = str(msg.author.username) + '#' + str(discrim) avatar_name = msg.author.avatar content = [] embed = MessageEmbed() member = event.guild.get_member(author_id) if not avatar_name: if member: avatar = default_color(str(member.user.default_avatar)) else: avatar = None elif avatar_name.startswith('a_'): avatar = u'https://cdn.discordapp.com/avatars/{}/{}.gif'.format(author_id, avatar_name) else: avatar = u'https://cdn.discordapp.com/avatars/{}/{}.png'.format(author_id, avatar_name) if member: embed.set_author(name='{} ({})'.format(member.user, member.id), icon_url=avatar) embed.set_thumbnail(url=avatar) else: if avatar: embed.set_author(name='{} ({})'.format(cached_name, author_id), icon_url=avatar) embed.set_thumbnail(url=avatar) else: embed.set_author(name='{} ({})'.format(cached_name, author_id)) # embed.title = "Message Content:" content.append(u'**\u276F Message Information:**') content.append(u'In channel: <#{}>'.format(channel_id)) content.append(u'Edited: **{}**'.format(num_edits_bool)) if deleted: content.append(u'Deleted: **{}**'.format(deleted)) content.append(u'Content: ```{}```'.format(message_content)) if member: content.append(u'\n**\u276F Member Information**') if member.nick: content.append(u'Nickname: {}'.format(member.nick)) content.append('Joined: {} ago ({})'.format( humanize.naturaldelta(datetime.utcnow() - member.joined_at), member.joined_at.isoformat(), )) if member.roles: roles = [] for r in member.roles: roles.append(member.guild.roles.get(r)) roles = sorted(roles, key=lambda r: r.position, reverse=True) total = len(member.roles) roles = roles[:20] content.append(u'Roles ({}): {}{}'.format( total, ' '.join(r.mention for r in roles), ' (+{})'.format(total-20) if total > 20 else '' )) embed.description = '\n'.join(content) # embed.url = 'https://discordapp.com/channels/{}/{}/{}'.format(guild_id, channel_id, mid) embed.timestamp = datetime.utcnow().isoformat() if not event.author.avatar: auth_avatar = default_color(str(member.user.default_avatar)) elif event.author.avatar.startswith('a_'): auth_avatar = u'https://cdn.discordapp.com/avatars/{}/{}.gif'.format(event.author.id, event.author.avatar) else: auth_avatar = u'https://cdn.discordapp.com/avatars/{}/{}.png'.format(event.author.id, event.author.avatar) embed.set_footer(text='Requested by {}#{} ({})'.format(event.author.username, event.author.discriminator, event.author.id), icon_url=auth_avatar) try: embed.color = get_dominant_colors_user(member.user, avatar) except: embed.color = '00000000' event.msg.reply('', embed=embed)