Ejemplo n.º 1
0
async def can_interact(bot, member, channel_id=None):
    """Checks that the given member can be interacted with.

    This ensures that the user is:
    Not a bot
    Not blocked in the server

    Additionally, if the user is a member (guild exists):
    Not in a blocked channel
    Not blacklisted by the botowners

    If given a channel ID, also checks that the bot is not muted in there

    This also checks for maintenace mode
    """
    if data.is_owner(bot, member.id):
        return True
    elif member.bot or member.id in data.get(bot, 'core', 'blacklist', default=[]):
        return False
    elif bot.maintenance_mode:
        return False

    # Guild specific check
    guild = getattr(member, 'guild', None)
    if guild:
        if data.is_mod(bot, member=member):
            return True
        guild_data = data.get(bot, 'core', None, guild.id, default={})
        if (guild_data.get('muted', False) or
                (channel_id in guild_data.get('muted_channels', [])) or
                (member.id in guild_data.get('blocked', []))):
            return False

    return True
Ejemplo n.º 2
0
def convert_tags(bot, guild):
    if not data.get(bot, 'tags.py', 'tags', guild_id=guild.id):
        logger.warn("Guild %s (%s) already had tags converted", guild.name, guild.id)
        return

    tags = data.get(bot, 'tags.py', 'tags', guild_id=guild.id, default={})
    add_tag = bot.plugins['tags.py']._add_tag
    #key,value,length,volume,name,flags,author,hits,created,last_used,last_used_by,complex,extra
    for key, tag in tags.items():
        to_insert = [
            key,                        # key
            tag['value'],               # value
            tag['length'],              # length
            tag['volume'],              # volume
            tag['name'],                # name
            tag['flags'],               # flags
            int(tag['author']),         # author
            tag['hits'],                # hits
            int(tag['created']),        # created
            int(tag['last_used']),      # last_used
            None,                       # last_used_by
            {},                         # complex
            {}                          # extra
        ]
        add_tag(bot, to_insert, guild.id)
    data.remove(bot, 'tags.py', 'tags', guild_id=guild.id, safe=True)
Ejemplo n.º 3
0
async def autolog_dump(bot, context):
    """Dumps logs into the set channel."""
    log_channel = _check_log_channel(bot, context.guild)
    logged_channel_ids = data.get(bot, __name__, 'channels', guild_id=context.guild.id, default=[])
    logged_channels = []
    if context.arguments[0]:
        for channel in context.arguments:
            if channel.id not in logged_channel_ids:
                raise CBException("{} is not being logged.".format(channel.mention))
            else:
                logged_channels.append(channel)
    else:
        for channel_id in logged_channel_ids:
            channel = data.get_channel(bot, channel_id, safe=True)
            if channel:
                logged_channels.append(channel)
            else:
                channel = discord.Object(id=channel_id)
                channel.mention = 'Deleted channel ({})'.format(channel_id)
                logged_channels.append(channel)

    # Build dump data
    logs = data.get(bot, __name__, 'logs', guild_id=context.guild.id, volatile=True)
    details = 'Manual dump by {0} (<@{0.id}>): {1}'.format(
        context.author, context.options['details'])
    dump_data = _build_dump_data(bot, logs, log_channel, details=details)

    # Upload dump data
    logged_messages = await _dump(
        bot, dump_data, log_channel, details=details, query=context.options['query'],
        moderator_id=context.author.id, logged_channels=logged_channels)
    if not logged_messages:
        raise CBException("No messages to log.")
    return Response("Messages dumped in {}".format(log_channel.mention))
Ejemplo n.º 4
0
def _set_logger(bot, channel):
    """Sets a logger for the given channel."""
    default_message_limit = configurations.get(bot, __name__, key='message_limit')
    message_limit = data.get(
        bot, __name__, 'message_limit', guild_id=channel.guild.id, default=default_message_limit)
    logs = data.get(
        bot, __name__, 'logs', guild_id=channel.guild.id,
        default={}, create=True, volatile=True)
    logs[channel.id] = collections.deque(maxlen=message_limit)
Ejemplo n.º 5
0
async def autolog_channels(bot, context):
    """Sets the channels that will be logged."""
    log_channel = _check_log_channel(bot, context.guild)

    # Toggle channels for logging
    if context.arguments[0]:
        changes = []
        for channel in context.arguments:
            appended = data.list_data_toggle(
                bot, __name__, 'channels', channel.id, guild_id=context.guild.id)
            if appended:
                changes.append('Now logging {}'.format(channel.mention))
                _set_logger(bot, channel)
            else:
                changes.append('No longer logging {}'.format(channel.mention))
                _delete_logger(bot, channel)
        embed = discord.Embed(title='Logging changes', description='\n'.join(changes))

    # Show channels that are currently logged
    else:
        default_message_limit = configurations.get(bot, __name__, key='message_limit')
        message_limit = data.get(
            bot, __name__, 'message_limit', guild_id=context.guild.id,
            default=default_message_limit)
        logged_channel_ids = data.get(bot, __name__, 'channels', guild_id=context.guild.id)
        if not logged_channel_ids:
            raise CBException("No channels are currently logged.")

        # Check logged channels
        # Removes channels that were deleted and have no logged messages
        logged_channels = []
        logs = data.get(bot, __name__, 'logs', guild_id=context.guild.id, volatile=True)
        for channel_id in logged_channel_ids:
            channel = data.get_channel(bot, channel_id, safe=True)
            if channel:
                logged_channels.append(channel)
            else:
                if len(logs[channel_id]):
                    channel = discord.Object(id=channel_id)
                    channel.mention = 'Deleted channel ({})'.format(channel_id)
                    logged_channels.append(channel)
                else:  # No logged messages for removed channel. Delete log.
                    del logs[channel_id]

        embed = discord.Embed(title='Logging info')
        embed.add_field(
            inline=False, name='Logged channels',
            value=', '.join(it.mention for it in logged_channels))
        embed.add_field(name='Dump channel', value=log_channel.mention)
        embed.add_field(name='Logged messages', value=message_limit)

    return Response(embed=embed)
Ejemplo n.º 6
0
def _awoo_check(bot, message, show_filtered=''):
    """
    Checks for awoo violations.

    Tier 1: Standard match
    Tier 2: Bypass attempt match
    Tier 3: Legalization plea
    """

    # Initial content check
    content = show_filtered or (message.clean_content.lower() if message.content else '')
    author, channel = message.author, message.channel
    if not content or author.bot or isinstance(channel, discord.abc.PrivateChannel):
        return

    # Ignore muted guilds, channels, and users
    guild_data = data.get(bot, 'core', None, message.guild.id, default={})
    if (guild_data.get('muted', False) or
            channel.id in guild_data.get('muted_channels', []) or
            author.id in guild_data.get('blocked', [])):
        return

    # Ignore disabled guilds, disabled channels and whitelisted users
    guild_awoo_data = data.get(bot, __name__, None, guild_id=message.guild.id, default={})
    if (not guild_awoo_data.get('enabled', False) or
            channel.id in guild_awoo_data.get('disabled_channels', []) or
            author.id in guild_awoo_data.get('whitelist', [])):
        return

    # Tier 3: Legalization plea
    if PLEA_MATCH.search(content):
        return 3

    # Tier 1: Basic check
    if BASIC_MATCH.search(content):
        return 1

    # Tier 2: Advanced check
    filtered = content
    for key, values in substitutions:
        for value in values:
            filtered = filtered.replace(value, key)
    _check = lambda c: c.isalpha() or c.isspace()
    filtered = ''.join(c.lower() for c in unicodedata.normalize('NFKD', filtered) if _check(c))
    if ADVANCED_MATCH.search(filtered):
        return 2

    # Debug
    if show_filtered:
        return filtered
Ejemplo n.º 7
0
async def awoo_toggle(bot, context):
    """Toggles awoo detection for either the guild or the given channel."""
    guild_awoo_data = data.get(
        bot, __name__, None, guild_id=context.guild.id, default={}, create=True)

    # Channel
    if context.arguments[0]:
        changes = []
        for channel in context.arguments:
            if channel.id in guild_awoo_data.get('disabled_channels', []):
                action = 'is now'
                data.list_data_remove(
                    bot, __name__, 'disabled_channels',
                    value=channel.id, guild_id=context.guild.id)
            else:
                action = 'is no longer'
                data.list_data_append(
                    bot, __name__, 'disabled_channels', channel.id, guild_id=context.guild.id)
            changes.append('{} {} being monitored.'.format(channel.mention, action))
        return Response(content='\n'.join(changes))

    # Guild
    else:
        guild_awoo_data['enabled'] = not guild_awoo_data.get('enabled', False)
        return Response(content='Detection is now {}abled'.format(
            'en' if guild_awoo_data['enabled'] else 'dis'))
Ejemplo n.º 8
0
async def character_create(bot, context):
    """Creates a new character entry."""

    # Check if an entry is currently being created/edited
    tracker = data.get(bot, __name__, 'tracker', user_id=context.author.id, volatile=True)
    if tracker:
        return Response(
            content=(
                "You are currently already creating or editing a character entry. "
                "Would you like to cancel your current session?"),
            message_type=MessageTypes.INTERACTIVE,
            extra_function=_cancel_menu,
            extra={'buttons': ['🇾', '🇳']})

    # 10 character limit
    cursor = data.db_select(
        bot, from_arg='characters', where_arg='owner_id=%s', input_args=[context.author.id])
    characters = cursor.fetchall() if cursor else []
    if len(characters) >= 10:
        raise CBException("Cannot create more than 10 characters.")

    # Use the provided character file
    if context.message.attachments:
        content = await _process_data(
            bot, context.author, context.message.attachments[0].url, propagate_error=True)
        return Response(content=content)

    # Use the online entry creator
    else:
        await _create_session(bot, context.author)
        if not context.direct:
            await context.message.add_reaction('📨')
Ejemplo n.º 9
0
async def tagremote_update(bot, context):
    """Renames the webhook with an updated tag list file."""

    # Check for an existing session
    session_data = data.get(bot, __name__, 'data', guild_id=context.guild.id)
    if not session_data:
        raise CBException("No session available.")
    channel = data.get_channel(bot, session_data['channel'])
    if not channel:
        await _delete_session(bot, context.guild)
        raise CBException("Failed to get the channel.")
    voice_channel = data.get_channel(bot, session_data['voice_channel'])
    if not voice_channel:
        await _delete_session(bot, context.guild)
        raise CBException("Failed to get the voice channel.")
    webhooks = await channel.webhooks()
    if not webhooks:
        await _delete_session(bot, context.guild)
        raise CBException("No webhooks available.")
    for webhook in webhooks:
        if webhook.id == session_data['webhook']:
            break
    else:
        await _delete_session(bot, context.guild)
        raise CBException("Webhook not found.")

    tag_dictionary = _get_tag_dictionary(bot, context.guild)
    session_code = await _upload_session_data(bot, channel, voice_channel, webhook, tag_dictionary)

    updated_code = session_code.split(':')[1]
    await webhook.edit(name='Tag Remote [{}]'.format(updated_code))

    return Response(
        content="Tag data refreshed. Update the remote on your phone via the options menu.")
Ejemplo n.º 10
0
async def translate(bot, context):
    """Translates the given text."""
    source = context.options.get('from', 'auto')
    if 'to' in context.options:
        destination = context.options['to']
    else:
        if context.direct:
            destination = 'en'
        else:
            destination = data.get(
                bot, __name__, 'default', guild_id=context.guild.id, default='en')

    try:
        result = await utilities.future(
            TRANSLATOR.translate, context.arguments[0], src=source, dest=destination)
    except ValueError as e:
        if 'source' in e.args[0]:
            issue, language = 'source', source
        else:
            issue, language = 'destination', destination
        raise CBException("Invalid {} language (`{}`).\n{}".format(issue, language, LANGUAGE_LINK))
    except Exception as e:
        raise CBException("Failed to translate the text.", e)

    full_source = googletrans.constants.LANGUAGES[result.src.lower()].title()
    full_destination = googletrans.constants.LANGUAGES[result.dest.lower()].title()
    embed = discord.Embed(
        title=':arrows_counterclockwise: Google Translate', color=discord.Color(0x3b88c3))
    embed.add_field(name=full_source, value=context.arguments[0], inline=False)
    embed.add_field(name=full_destination, value=result.text, inline=False)
    return Response(embed=embed)
Ejemplo n.º 11
0
def _update_current_game(bot, safe=False, include_setup_status=False):
    """Updates the index of the latest/current game."""
    schedule_data = data.get(bot, __name__, 'schedule', volatile=True, default=[])
    current_time = datetime.datetime.utcnow()
    for index, game in enumerate(schedule_data):
        start_time, end_time = game['scheduled'], game['end']
        if start_time <= current_time < end_time:  # Update latest index
            data.add(bot, __name__, 'current_index', index, volatile=True)
            data.add(bot, __name__, 'current_game', game, volatile=True)
            if include_setup_status:
                setup_time = datetime.timedelta(seconds=game['setup_seconds'])
                return index, (current_time < start_time + setup_time)
            else:
                return index
        elif current_time < start_time:
            logger.debug("The current time is less than the start time. Index: %s", index)
            break
    else:  # GDQ over, or past schedule
        game, index = None, 999
    if safe:
        data.add(bot, __name__, 'current_index', index, volatile=True)
        data.add(bot, __name__, 'current_game', game, volatile=True)
        if include_setup_status:
            return index, True
        else:
            return index
    raise CBException("No current game was found.")
Ejemplo n.º 12
0
def _delete_logger(bot, channel):
    """Removes a logger for the given channel."""
    logs = data.get(
        bot, __name__, 'logs', guild_id=channel.guild.id,
        default={}, create=True, volatile=True)
    if channel.id in logs:
        del logs[channel.id]
Ejemplo n.º 13
0
async def role_joinleave(bot, context):
    """Adds/removes the given role(s) to/from the member."""

    # Check for a verified role
    verified_role = data.get_custom_role(bot, __name__, 'verified', context.guild)
    if verified_role:
        if not data.has_custom_role(bot, __name__, 'verified', member=context.author):
            raise CBException("You must have the role {} in order to self-assign roles.".format(
                verified_role.mention))

    joining = context.id == 'join'
    available_role_ids = data.get(bot, __name__, 'roles', guild_id=context.guild.id, default=[])
    for role in context.arguments:
        if role.id not in available_role_ids:
            raise CBException("The role {} is not self-assignable.".format(role.mention))
    try:
        action_function = context.author.add_roles if joining else context.author.remove_roles
        await action_function(*context.arguments, reason="Self-assignable role")
    except discord.Forbidden:
        if not context.guild.me.guild_permissions.manage_roles:
            raise CBException("The bot is missing the `Manage Roles` permission.")
        _check_roles(bot, context.guild)
        action = 'assign' if joining else 'remov'
        raise CBException("The role(s) could not be {}ed due to a hierarchy issue.".format(action))

    embed = discord.Embed(
        title='You have {} the role{}:'.format(
            'joined' if joining else 'left', '' if len(context.arguments) == 1 else 's'),
        description='\n'.join(it.mention for it in context.arguments))
    return Response(embed=embed)
Ejemplo n.º 14
0
def get_permission_bits(bot):
    """Calculates all of the permissions for each plugin."""
    dummy = discord.Permissions()
    for plugin in bot.plugins.keys():
        for permission in data.get(
                bot, plugin, 'permissions', volatile=True, default={}):
            setattr(dummy, permission.lower(), True)
    return dummy.value
Ejemplo n.º 15
0
async def check_webhook_messages(bot, message):
    """Intercepts webhook messages to the data channel.
    
    There are 3 separate stages:
    0 - Starting stage (webhook exists)
    1 - User has submitted the file, edit webhook name with return code
    2 - User acknowledges result, requests that the webhook be deleted
    """
    if message.channel != DATA_CHANNEL:
        return

    # Check for valid webhook messages
    webhook_id = message.author.id
    if webhook_id not in DATA_CHANNEL_WEBHOOK_IDS:
        return

    stage = data.get(bot, __name__, 'stage', user_id=webhook_id, volatile=True)
    if stage is not None:

        if message.content == '1' and stage == 0:  # Progress to stage 1
            owner = data.get(bot, __name__, 'owner', user_id=webhook_id, volatile=True)
            webhook = data.get(bot, __name__, 'tracker', user_id=owner.id, volatile=True)
            result = await _process_data(bot, owner, message.attachments[0].url)

            # Parse result
            data.add(bot, __name__, 'stage', 1, user_id=webhook_id, volatile=True)
            await webhook.edit(name='ok' if result == 0 else 'err:{}'.format(result))

        elif message.content == '2' and stage == 1:  # Progress to stage 2
            await _clear_webhook(bot, webhook_id)

        else:  # Invalid state progression detected (likely duplicate)
            logger.warn("Invalid state progression detected. Message content: %s", message.content)
            await _clear_webhook(bot, webhook_id)
            pass  # TODO: Consider notifying user?

    else:  # Desync

        logger.warn("Webhook state desynchronization detected.")
        await _clear_webhook(bot, webhook_id)
        webhooks = await DATA_CHANNEL.webhooks()
        for webhook in webhooks:  # In case the webhook ID was invalid
            if webhook.id == webhook_id:
                await webhook.delete()
                break
Ejemplo n.º 16
0
def _get_message_logger(bot, channel=None):
    """Gets the message logger if it exists."""
    if isinstance(channel, discord.abc.PrivateChannel):
        return None
    message_loggers = data.get(
        bot, __name__, 'logs', guild_id=channel.guild.id, volatile=True, default={})
    if channel:
        return message_loggers.get(channel.id, None)
    return message_loggers
Ejemplo n.º 17
0
async def set_ip_address(bot, context):
    if context.arguments[0]:
        data.add(bot, __name__, 'server_ip', context.arguments[0], guild_id=context.guild.id)
        response = "IP address set!"
    else:  # Get current IP
        default_ip = configurations.get(bot, __name__, key='default_ip')
        response = "The current IP address is: {}".format(
            data.get(bot, __name__, 'server_ip', guild_id=context.guild.id, default=default_ip))
    return Response(content=response)
Ejemplo n.º 18
0
async def autolog_messages(bot, context):
    """Sets the number of messages to log in each channel."""
    _check_log_channel(bot, context.guild)
    data.add(bot, __name__, 'message_limit', context.arguments[0], guild_id=context.guild.id)
    logged_channels = data.get(bot, __name__, 'channels', guild_id=context.guild.id, default=[])
    for channel_id in logged_channels:
        channel = context.guild.get_channel(channel_id)
        _set_logger(bot, channel)
    return Response("{} messages will be logged for each channel.".format(context.arguments[0]))
Ejemplo n.º 19
0
def _get_verified_role(bot, guild, member=None):
    """Checks for the verified role and returns it unless the member has the role."""
    role_id = data.get(bot, __name__, 'verification_role', guild_id=guild.id)
    verified_role = data.get_role(bot, role_id, guild=guild, safe=True)
    if not (role_id or verified_role):
        raise CBException_vc("The verified role has not been set.")
    if member and verified_role in member.roles:
        raise CBException_vc("{} already has the role {}.".format(
            member.mention, verified_role.mention))
    return verified_role
Ejemplo n.º 20
0
async def setup_loggers(bot):
    """Sets up the loggers for each guild."""
    for guild in bot.guilds:
        logged_channels = data.get(bot, __name__, 'channels', guild_id=guild.id, default=[])
        for channel_id in logged_channels[:]:
            channel = guild.get_channel(channel_id)
            if channel:
                _set_logger(bot, channel)
            else:
                logged_channels.remove(channel_id)
Ejemplo n.º 21
0
def add_bot_permissions(bot, plugin_name, **permissions):
    """Adds the given permissions to the bot for authentication generation."""
    dummy = discord.Permissions()
    for permission in permissions:
        try:
            getattr(dummy, permission.lower())
        except:  # Permission not found
            raise CBException("Permission '{}' does not exist".format(permission))
    current = data.get(
        bot, plugin_name, 'permissions', create=True, volatile=True)
    if current is None:
        data.add(bot, plugin_name, 'permissions', permissions, volatile=True)
Ejemplo n.º 22
0
def _get_next_games(bot, retrieve, guild_id):
    """Gets the current/next game(s) and the defined number of extra games."""
    latest_index, in_setup = _update_current_game(bot, safe=True, include_setup_status=True)
    schedule_data = data.get(bot, __name__, 'schedule', volatile=True, default=[])
    if not in_setup:
        latest_index += 1
    games_list = schedule_data[latest_index:latest_index + retrieve]
    embed_data = _embed_games_information(bot, games_list, guild_id)
    if embed_data:
        return embed_data
    else:
        raise CBException("Game information not found.")
Ejemplo n.º 23
0
def get_advertisement(bot, location):
    """Retrieves an advertisement if one is scheduled."""
    all_uses = data.get(bot, __name__, 'uses', volatile=True)
    ad_uses = configurations.get(bot, __name__, 'ad_uses')
    ad_uses = ad_uses if ad_uses > 0 else 30
    current_uses = all_uses.get(location.id, 0)
    if current_uses >= ad_uses - 1:  # Show advertisement
        if location.id in all_uses:
            del all_uses[location.id]
        content = random.choice((
            "Consider supporting Wolfram|Alpha by trying out Wolfram|Alpha "
            "Pro! It helps keep Wolfram|Alpha free, and provides you with "
            "a much more complete knowledge database experience.",
            "Do you work/study in a STEM field? Wolfram|Alpha Pro can help!",
            "Need help with STEM homework? Wolfram|Alpha Pro has you covered "
            "with step-by-step instructions on how to solve almost any "
            "calculus problems.",
            "Experience professional-grade computational knowledge with "
            "Wolfram|Alpha Pro.",
            "Student or educator in STEM? Wolfram|Alpha brings you the "
            "professional features you need to excel.",
            "Love Wolfram|Alpha? Get more out of your Wolfram|Alpha "
            "experience by going pro!",
            "Need beautifully crafted interactive data visuals? Wolfram|Alpha "
            "Pro can do that for you!",
            "Professional-grade data analysis and visualization can "
            "greatly expedite completing projects and presentations.",
            "Need help with math homework? Get step-by-step solutions for "
            "complexity ranging from arithmetic to calculus and beyind!",
            "Having trouble with learning mathematics? It doesn't matter "
            "if it's algebra or differential equations, Wolfram|Alpha Pro "
            "gives you step-by-step solutions.",
            "Need extra math practice? Wolfram|Alpha Pro can generate an "
            "infinite number of practice problems with step-by-step "
            "solutions to help you ace your exams.",
            "Frequent Wolfram|Alpha user? Tailor your experience for your own "
            "needs with Wolfram|Alpha Pro!",
            "Are your queries timing out? Wolfram|Alpha Pro extends "
            "computation times.",
            "Need powerful visualization and analysis tools for your data? "
            "Wolfram|Alpha Pro is for you!",
            "Directly interact with and download computed data with "
            "Wolfram|Alpha Pro."
            ))
        link = random.choice((
            'See more at', 'For more information, visit',
            'See what upgrading can do at', 'Interested? Check out',
            'Click here for more:', 'Ready to upgrade? See',
            'Curious? Learn more at',
            'Check it out at')) + ' <https://www.wolframalpha.com/pro/>'
        return content + ' ' + link
    else:
        all_uses[location.id] = current_uses + 1
Ejemplo n.º 24
0
async def set_units(bot, context):
    default_units = configurations.get(bot, __name__, key='default_units')
    units = data.get(
        bot, __name__, 'server_units', guild_id=context.guild.id, default=default_units)
    if units == 'metric':
        new_units = 'nonmetric'
        response = 'US standard'
    else:
        new_units = 'metric'
        response = 'Metric'
    data.add(bot, __name__, 'server_units', new_units, guild_id=context.guild.id)
    return Response(content=response + ' units are now set as the default.')
Ejemplo n.º 25
0
def _check_roles(bot, guild):
    """Checks/ensures the validity of available self-assignable roles in the guild."""
    available_role_ids = data.get(bot, __name__, 'roles', guild_id=guild.id, default=[])
    guild_roles = dict((it.id, it) for it in guild.roles)
    top_role = guild.me.top_role
    remaining_roles = []
    for role_id in available_role_ids:
        if role_id not in guild_roles or guild_roles[role_id] > top_role:
            data.list_data_remove(bot, __name__, 'roles', role_id, guild_id=guild.id)
        else:
            remaining_roles.append(guild_roles[role_id])
    return remaining_roles
Ejemplo n.º 26
0
async def _get_buffered_donation_stats(bot):
    """Pulls buffered donation information if it is 1 minute old or less."""
    last_pull = data.get(bot, __name__, 'last_pull', volatile=True, default=0)
    buffer_time = configurations.get(bot, __name__, 'stats_buffer_time')
    if time.time() - last_pull > buffer_time:  # Pull information
        data.add(bot, __name__, 'last_pull', time.time(), volatile=True)
        tracker_url = configurations.get(bot, __name__, 'tracker_url')
        try:
            donate_html = (await utilities.future(requests.get, tracker_url)).text
            soup = BeautifulSoup(donate_html, 'html.parser')
            donation_text = soup.find('small').text.splitlines()[1:]
            total_raised, total_donations, _unused = donation_text[1].split()
            total_donations = total_donations.strip('()')
            max_average = donation_text[3]
        except Exception as e:
            raise CBException("Failed to retrieve donation data.", e=e)
        donation_stats = [total_raised, total_donations, max_average]
        data.add(bot, __name__, 'donation_stats', donation_stats, volatile=True)
    else:
        donation_stats = data.get(bot, __name__, 'donation_stats', volatile=True)
    return donation_stats
Ejemplo n.º 27
0
async def role_delete(bot, context):
    """Deletes the given roles."""
    available_role_ids = data.get(bot, __name__, 'roles', guild_id=context.guild.id, default=[])
    for role in context.arguments:
        if role.id not in available_role_ids:
            raise CBException("The role {} is not self-assignable.".format(role.mention))
    try:
        for role in context.arguments:
            await role.delete(reason='Deleted by {0} ({0.id})'.format(context.author))
    except discord.Forbidden:
        raise CBException("The bot is missing the `Manage Roles` permission.")

    return Response(embed=discord.Embed(description='Roles deleted.'))
Ejemplo n.º 28
0
async def join_and_ready(bot, voice_channel, is_mod=False, reconnect=False):
    """Joins the voice channel and stops any audio playing.

    Returns the voice_client object from voice_channel.connect()
    """
    guild = voice_channel.guild
    muted_channels = data.get(bot, 'core', 'muted_channels', guild_id=guild.id, default=[])
    if voice_channel == guild.afk_channel:
        raise CBException("This is the AFK channel.")
    if voice_channel.id in muted_channels and not is_mod:
        raise CBException("The bot is muted in this voice channel.")
    if reconnect:
        try:
            await stop_audio(bot, guild)
        except:
            pass

    voice_client = guild.voice_client
    if not voice_client:
        try:
            voice_client = await asyncio.wait_for(
                voice_channel.connect(timeout=5.0, reconnect=False),
                timeout=10.0, loop=bot.loop)
        except asyncio.TimeoutError as e:
            try:
                await stop_audio(bot, guild, force=True)
            except:
                pass
            raise CBException("Timed out trying to join the voice channel.")
        except Exception as e:
            try:
                await stop_audio(bot, guild)
            except:
                pass
            raise CBException("Failed to join the voice channel.", e=e)
        if voice_client.is_playing():
            voice_client.stop()
    else:
        if voice_client.is_playing():
            voice_client.stop()
        if voice_client.channel != voice_channel:
            try:
                await voice_client.move_to(voice_channel)
            except Exception as e:
                try:
                    await stop_audio(bot, guild)
                except:
                    pass
                raise CBException("Failed to move to the voice channel.", e=e)

    return voice_client
Ejemplo n.º 29
0
async def gdq_menu(bot, context, response, result, timed_out):
    if timed_out:
        response.update_stats = False
        if response.update_task:
            response.update_task.cancel()
        return
    if not result and not response.update_task:
        response.update_task = asyncio.ensure_future(_update_menu(bot, response))
        return
    selection = ['⬅', '⏺', '➡'].index(result[0].emoji)
    schedule_data = data.get(bot, __name__, 'schedule', volatile=True)
    guild_id = context.guild.id if context.guild else None

    if selection in (0, 2):  # Page navigation
        offset = -5 if selection == 0 else 5
        response.game_index = max(min(response.game_index + offset, len(schedule_data) - 5), 0)
    else:
        response.game_index = data.get(bot, __name__, 'current_index', volatile=True, default=0)
    games_list = schedule_data[response.game_index:response.game_index + 5]
    game_data = _embed_games_information(bot, games_list, guild_id)
    values = [(it + response.game_index + 1, *c) for it, c in enumerate(game_data)]
    value = '\n\n'.join('**`[{}]` {}**\n{}'.format(*it) for it in values)
    response.embed.set_field_at(1, name='Schedule', value=value, inline=False)
    await response.message.edit(embed=response.embed)
Ejemplo n.º 30
0
def convert_core(bot, guild):
    if data.get(bot, 'core', None, guild_id=guild.id):
        logger.warn("Guild %s (%s) already had core converted", guild.name, guild.id)
        return
    base_data = data.get(bot, 'base', None, guild_id=guild.id, default={})
    if 'disabled' in base_data:
        # TODO: Iterate through toggled commands
        pass
    if 'blocked' in base_data:
        replacement = []
        for entry in base_data['blocked']:
            replacement.append(int(entry))
        base_data['blocked'] = replacement
    if 'muted_channels' in base_data:
        replacement = []
        for entry in base_data['muted_channels']:
            replacement.append(int(entry))
        base_data['muted_channels'] = replacement
    if 'moderators' in base_data:
        del base_data['moderators']
    if base_data:
        for key, value in base_data.items():
            data.add(bot, 'core', key, value, guild_id=guild.id)
        data.remove(bot, 'base', None, guild_id=guild.id)
Ejemplo n.º 31
0
async def wolfram_alpha_query(bot,
                              query,
                              user_ip,
                              indices='',
                              format_param='plaintext,image',
                              units='metric'):
    """Returns a query result from Wolfram|Alpha."""
    client = data.get(bot, __name__, 'client', volatile=True)
    client_query = client.GetQuery(query=query)
    client_query.ToURL()
    if indices:
        client_query.AddPodIndex(podindex=indices)
    # client_query.AddFormat(format_param=format_param)  # need both
    client_query.AddIp(ip=user_ip)
    client_query.AddUnits(units=units)
    #query_result = await async_query(client, client_query.Query)
    try:
        query_result = await utilities.future(client.PerformQuery,
                                              client_query.Query)
    except Exception as e:
        raise CBException("The query could not be processed.", e=e)
    result = wap.WolframAlphaQueryResult(query_result)
    element = ElementTree.fromstring(result.XmlResult)
    return ElementTree.ElementTree(element=element).getroot()
Ejemplo n.º 32
0
async def autolog_channels(bot, context):
    """Sets the channels that will be logged."""
    log_channel = _check_log_channel(bot, context.guild)

    # Toggle channels for logging
    if context.arguments[0]:
        changes = []
        for channel in context.arguments:
            appended = data.list_data_toggle(bot,
                                             __name__,
                                             'channels',
                                             channel.id,
                                             guild_id=context.guild.id)
            if appended:
                changes.append('Now logging {}'.format(channel.mention))
                _set_logger(bot, channel)
            else:
                changes.append('No longer logging {}'.format(channel.mention))
                _delete_logger(bot, channel)
        embed = discord.Embed(title='Logging changes',
                              description='\n'.join(changes))

    # Show channels that are currently logged
    else:
        default_message_limit = configurations.get(bot,
                                                   __name__,
                                                   key='message_limit')
        message_limit = data.get(bot,
                                 __name__,
                                 'message_limit',
                                 guild_id=context.guild.id,
                                 default=default_message_limit)
        logged_channel_ids = data.get(bot,
                                      __name__,
                                      'channels',
                                      guild_id=context.guild.id)
        if not logged_channel_ids:
            raise CBException("No channels are currently logged.")

        # Check logged channels
        # Removes channels that were deleted and have no logged messages
        logged_channels = []
        logs = data.get(bot,
                        __name__,
                        'logs',
                        guild_id=context.guild.id,
                        volatile=True)
        for channel_id in logged_channel_ids:
            channel = data.get_channel(bot, channel_id, safe=True)
            if channel:
                logged_channels.append(channel)
            else:
                if len(logs[channel_id]):
                    channel = discord.Object(id=channel_id)
                    channel.mention = 'Deleted channel ({})'.format(channel_id)
                    logged_channels.append(channel)
                else:  # No logged messages for removed channel. Delete log.
                    del logs[channel_id]

        embed = discord.Embed(title='Logging info')
        embed.add_field(inline=False,
                        name='Logged channels',
                        value=', '.join(it.mention for it in logged_channels))
        embed.add_field(name='Dump channel', value=log_channel.mention)
        embed.add_field(name='Logged messages', value=message_limit)

    return Response(embed=embed)
Ejemplo n.º 33
0
async def _violation_notification(bot, message, awoo_tier, send_message=True):
    """
    Logs the violation and (optionally) sends the user a notification.

    Standard notification: once per violation, up to 1 time
    None: 2 violations
    Silence notification: 1 violation

    Reset period for notifications is 1 minute.

    Stress indicates a number of users making a violation within a 60 second period.
    Tier 1: 3 members
    Tier 2: 5 members
    Tier 3: 8 members
    """

    author, channel = message.author, message.channel
    current_time = time.time()
    violation_data = data.get(bot,
                              __name__,
                              'user_violation',
                              user_id=author.id,
                              volatile=True)
    channel_violation_data = data.get(bot,
                                      __name__,
                                      'channel_violation',
                                      channel_id=channel.id,
                                      volatile=True)
    if not violation_data or current_time - violation_data['time'] >= 60:
        violation_data = {'time': 0, 'violations': 0}
        data.add(bot,
                 __name__,
                 'user_violation',
                 violation_data,
                 user_id=author.id,
                 volatile=True)
    if not channel_violation_data or current_time - channel_violation_data[
            'time'] >= 60:
        channel_violation_data = {
            'time': 0,
            'violators': set(),
            'sent_tier': 0
        }
        data.add(bot,
                 __name__,
                 'channel_violation',
                 channel_violation_data,
                 channel_id=channel.id,
                 volatile=True)
    violation_data['violations'] += 1
    violation_data['time'] = current_time
    channel_violation_data['violators'].add(author.id)
    channel_violation_data['time'] = current_time

    # Update table
    set_arg = 'debt = debt+%s, violations = violations+1'
    if awoo_tier == 2:
        set_arg += ', sneaky = sneaky+1'
    cursor = data.db_select(bot,
                            from_arg='awoo',
                            where_arg='user_id=%s',
                            input_args=[author.id])
    entry = cursor.fetchone() if cursor else None
    if entry:
        data.db_update(bot,
                       'awoo',
                       set_arg=set_arg,
                       where_arg='user_id=%s',
                       input_args=[fine, author.id])
    else:
        data.db_insert(
            bot,
            'awoo',
            input_args=[author.id, fine, 1, 1 if awoo_tier == 2 else 0])

    # Add a snarky message depending on the tier
    if awoo_tier == 2:  # Attempted bypass
        snark = random.choice(statements['bypass']) + '\n'
    elif awoo_tier == 3:  # Legalization plea
        snark = random.choice(statements['legalize']) + '\n'
    else:
        snark = ''

    # Notify user
    logger.debug("Violations: %s", violation_data['violations'])
    text = ''
    if violation_data['violations'] <= 1:
        text = "{}{} has been fined ${} for an awoo violation.".format(
            snark, author.mention, fine)
    elif violation_data['violations'] == 4:
        text = "{} {}".format(author.mention,
                              random.choice(statements['silence']))
    elif awoo_tier == 3 and violation_data[
            'violations'] <= 3:  # Legalization plea, but silent
        text = snark
    if send_message and text:
        await channel.send(content=text)
    else:
        await message.add_reaction(
            random.choice(['🚩', '🛑', '�', '⛔', '🚫']))

    # Stress
    violators, sent_tier = channel_violation_data[
        'violators'], channel_violation_data['sent_tier']
    if (len(violators) == 3 and sent_tier == 0
            or len(violators) == 5 and sent_tier == 1
            or len(violators) == 8 and sent_tier == 2):
        if send_message:
            await message.channel.send(
                random.choice(statements['stress'][sent_tier]))
        channel_violation_data['sent_tier'] += 1
Ejemplo n.º 34
0
async def get_query_result(bot,
                           guild,
                           query,
                           text_result=False,
                           result_lines=0):
    """Gets a query result and formats it."""
    default_ip = configurations.get(bot, __name__, key='default_ip')
    default_units = configurations.get(bot, __name__, key='default_units')
    if guild is None:
        server_ip = default_ip
        units = default_units
    else:
        server_ip = data.get(bot,
                             __name__,
                             'server_ip',
                             guild_id=guild.id,
                             default=default_ip)
        units = data.get(bot,
                         __name__,
                         'server_units',
                         guild_id=guild.id,
                         default=default_units)

    indices = ','.join((str(index) for index in range(1, result_lines + 2)))
    format_param = 'plaintext' + ('' if text_result else ',image')
    root = await wolfram_alpha_query(bot,
                                     query,
                                     server_ip,
                                     indices=indices,
                                     format_param=format_param,
                                     units=units)
    pods = root.findall('pod')
    warning = None
    query_url = 'http://www.wolframalpha.com/input/?i={}'.format(
        urllib.parse.quote_plus(query))

    # Error handling
    if root.get('success') == 'false':

        suggestions = root.find('didyoumeans')
        if suggestions:
            suggestions = suggestions.findall('didyoumean')
            suggestion_text = [suggestion.text for suggestion in suggestions]
        raise CBException(
            "Wolfram|Alpha could not interpret your query.{}".format(
                '' if suggestions is None else ' Suggestion(s): {}'.
                format(', '.join(suggestion_text[:3]))))
    elif root.get('timedout'):
        if len(pods) == 0:
            raise CBException("Query timed out.", query_url)
        elif len(pods) < result_lines:
            warning = "Query timed out but returned some results"
    elif len(pods) == 0:
        raise CBException("No result given (general error).", query_url)

    # Format answer
    result_list = []
    if root.find('pod').get('id') != 'Input':
        result_lines -= 1
    if root.find('warnings') is not None:
        spellchecks = root.find('warnings').findall('spellcheck')
        for spellcheck in spellchecks:
            result_list.append(('spellcheck', None, spellcheck.get('text')))
    for pod in root.findall('pod')[:1 + result_lines]:
        for index, sub_pod in enumerate(pod.findall('subpod')):
            image = sub_pod.find('img')
            image_url = '' if image is None else image.get('src')
            text = sub_pod.find('plaintext').text
            title = pod.get('title')
            if index > 0:
                title = None
            result_list.append((title, image_url, text))

    if text_result:
        result = []
        for query_result in result_list:
            text = query_result[2]
            if text:
                if query_result[0] == 'spellcheck':
                    result.append(('Spell check', text))
                elif query_result[0]:
                    result.append((query_result[0], text))
                else:
                    result.append(('\u200b', text))
            else:
                result.append(
                    (query_result[0], '[`Image`]({})'.format(query_result[1])))
    else:  # Get the image
        result = await get_result_as_image(bot, result_list)

    return query_url, result, warning
Ejemplo n.º 35
0
Archivo: core.py Proyecto: sasma/JshBot
        async def on_message(self, message, replacement_message=None):
            # Ensure bot can respond properly
            try:
                initial_data = self.can_respond(message)
            except Exception as e:  # General error
                logger.error(e)
                logger.error(traceback.format_exc())
                self.last_exception = e
                return
            if not initial_data:
                return

            # Ensure command is valid
            content = initial_data[0]
            elevation = 3 - (initial_data[4:0:-1] + [True]).index(True)
            split_content = content.split(' ', 1)
            if len(split_content) == 1:  # No spaces
                split_content.append('')
            base, parameters = split_content
            base = base.lower()
            try:
                command = self.commands[base]
            except KeyError:
                logger.debug("Suitable command not found: " + base)
                return

            # Check that user is not spamming
            author_id = message.author.id
            direct = isinstance(message.channel, PrivateChannel)
            spam_value = self.spam_dictionary.get(author_id, 0)
            if elevation > 0 or direct:  # Moderators ignore custom limit
                spam_limit = self.spam_limit
            else:
                spam_limit = min(
                    self.spam_limit,
                    data.get(self,
                             'core',
                             'spam_limit',
                             guild_id=message.guild.id,
                             default=self.spam_limit))
            if spam_value >= spam_limit:
                if spam_value == spam_limit:
                    self.spam_dictionary[author_id] = spam_limit + 1
                    plugins.broadcast_event(self, 'bot_on_user_ratelimit',
                                            message.author)
                    await self.send_message(
                        message.channel,
                        "{0}, you appear to be issuing/editing "
                        "commands too quickly. Please wait {1} seconds.".
                        format(message.author.mention, self.spam_timeout))
                return

            # Parse command and reply
            try:
                context = None
                with message.channel.typing():
                    logger.debug(message.author.name + ': ' + message.content)
                    subcommand, options, arguments = parser.parse(
                        self, command, parameters, message)
                    context = self.Context(
                        message, base, subcommand, options, arguments,
                        subcommand.command.keywords, initial_data[0],
                        elevation, message.guild, message.channel,
                        message.author, direct, subcommand.index,
                        subcommand.id, self)
                    plugins.broadcast_event(self, 'bot_on_command', context)
                    logger.info([subcommand, options, arguments])
                    response = await commands.execute(self, context)
                    if response is None:
                        response = Response()
                    if self.selfbot and response.content:
                        response.content = '\u200b' + response.content
            except Exception as e:  # General error
                response = Response()
                destination = message.channel
                message_reference = await self.handle_error(
                    e,
                    message,
                    context,
                    response,
                    edit=replacement_message,
                    command_editable=True)

            else:  # Attempt to respond
                send_arguments = response.get_send_kwargs(replacement_message)
                try:
                    destination = response.destination if response.destination else message.channel
                    message_reference = None
                    if replacement_message:
                        try:
                            await replacement_message.edit(**send_arguments)
                            message_reference = replacement_message
                        except discord.NotFound:  # Message deleted
                            response = Response()
                            message_reference = None
                    elif (not response.is_empty() and
                          not (self.selfbot and
                               response.message_type is MessageTypes.REPLACE)):
                        message_reference = await destination.send(
                            **send_arguments)
                    response.message = message_reference
                    plugins.broadcast_event(self, 'bot_on_response', response,
                                            context)
                except Exception as e:
                    message_reference = await self.handle_error(
                        e,
                        message,
                        context,
                        response,
                        edit=replacement_message,
                        command_editable=True)

            # Incremement the spam dictionary entry
            if author_id in self.spam_dictionary:
                self.spam_dictionary[author_id] += 1
            else:
                self.spam_dictionary[author_id] = 1

            # MessageTypes:
            # NORMAL - Normal. The issuing command can be edited.
            # PERMANENT - Message is not added to the edit dictionary.
            # REPLACE - Deletes the issuing command after 'extra' seconds. Defaults
            #   to 0 seconds if 'extra' is not given.
            # ACTIVE - The message reference is passed back to the function defined
            #   with 'extra_function'. If 'extra_function' is not defined, it will call
            #   plugin.handle_active_message.
            # INTERACTIVE - Assembles reaction buttons given by extra['buttons'] and
            #   calls 'extra_function' whenever one is pressed.
            # WAIT - Wait for event. Calls 'extra_function' with the result, or None
            #   if the wait timed out.
            #
            # Only the NORMAL message type can be edited.

            response.message = message_reference
            if message_reference and isinstance(message_reference.channel,
                                                PrivateChannel):
                permissions = self.user.permissions_in(
                    message_reference.channel)
            elif message_reference:
                permissions = message_reference.guild.me.permissions_in(
                    message_reference.channel)
            else:
                permissions = None
            self.last_response = message_reference

            if response.message_type is MessageTypes.NORMAL:
                # Edited commands are handled in base.py
                if message_reference is None:  # Forbidden exception
                    return
                wait_time = self.edit_timeout
                if wait_time:
                    self.edit_dictionary[str(message.id)] = message_reference
                    await asyncio.sleep(wait_time)
                    if str(message.id) in self.edit_dictionary:
                        del self.edit_dictionary[str(message.id)]
                        if message_reference.embeds:
                            embed = message_reference.embeds[0]
                            if embed.footer.text and embed.footer.text.startswith(
                                    '\u200b' * 3):
                                embed.set_footer()
                                try:
                                    await message_reference.edit(embed=embed)
                                except:
                                    pass

            elif response.message_type is MessageTypes.REPLACE:
                try:
                    if self.selfbot and not replacement_message:  # Edit instead
                        await message.edit(**send_arguments)
                    else:
                        if response.extra:
                            await asyncio.sleep(response.extra)
                        try:
                            await message.delete(reason='Automatic')
                        except:  # Ignore permissions errors
                            pass
                except Exception as e:
                    message_reference = await self.handle_error(
                        e, message, context, response, edit=message_reference)
                    self.last_response = message_reference

            elif response.message_type is MessageTypes.ACTIVE:
                if message_reference is None:  # Forbidden exception
                    return
                try:
                    await response.extra_function(self, context, response)
                except Exception as e:  # General error
                    message_reference = await self.handle_error(
                        e, message, context, response, edit=message_reference)
                    self.last_response = message_reference

            elif response.message_type is MessageTypes.INTERACTIVE:
                try:
                    buttons = response.extra['buttons']
                    kwargs = response.extra.get('kwargs', {})
                    if 'timeout' not in kwargs:
                        kwargs['timeout'] = 300
                    if 'check' not in kwargs:
                        kwargs['check'] = (lambda r, u: r.message.id ==
                                           message_reference.id and not u.bot)
                    for button in buttons:
                        await message_reference.add_reaction(button)
                    reaction_check = await destination.get_message(
                        message_reference.id)
                    for reaction in reaction_check.reactions:
                        if not reaction.me or reaction.count > 1:
                            async for user in reaction.users():
                                if user != self.user and permissions.manage_messages:
                                    asyncio.ensure_future(
                                        message_reference.remove_reaction(
                                            reaction, user))
                    await response.extra_function(self, context, response,
                                                  None, False)
                    process_result = True
                    while process_result is not False:
                        try:
                            if not permissions.manage_messages:
                                add_task = self.wait_for(
                                    'reaction_add', **kwargs)
                                remove_task = self.wait_for(
                                    'reaction_remove', **kwargs)
                                done, pending = await asyncio.wait(
                                    [add_task, remove_task],
                                    return_when=FIRST_COMPLETED)
                                result = next(iter(done)).result()
                                for future in pending:
                                    future.cancel()
                            else:  # Can remove reactions
                                result = await self.wait_for(
                                    'reaction_add', **kwargs)
                                if result[1] != self.user:
                                    asyncio.ensure_future(
                                        message_reference.remove_reaction(
                                            *result))
                                else:
                                    continue
                            is_mod = data.is_mod(self, message.guild,
                                                 result[1].id)
                            if (response.extra.get('reactionlock', True)
                                    and not result[0].me or
                                (response.extra.get('userlock', True) and
                                 not (result[1] == message.author or is_mod))):
                                continue
                        except (asyncio.futures.TimeoutError,
                                asyncio.TimeoutError):
                            await response.extra_function(
                                self, context, response, None, True)
                            process_result = False
                        else:
                            process_result = await response.extra_function(
                                self, context, response, result, False)
                    try:
                        await response.message.clear_reactions()
                    except:
                        pass
                except Exception as e:
                    message_reference = await self.handle_error(
                        e, message, context, response, edit=message_reference)
                    self.last_response = message_reference

            elif response.message_type is MessageTypes.WAIT:
                try:
                    kwargs = response.extra.get('kwargs', {})
                    if 'timeout' not in kwargs:
                        kwargs['timeout'] = 300
                    process_result = True
                    while process_result is not False:
                        try:
                            result = await self.wait_for(
                                response.extra['event'], **kwargs)
                        except asyncio.TimeoutError:
                            await response.extra_function(
                                self, context, response, None)
                            process_result = False
                        else:
                            process_result = await response.extra_function(
                                self, context, response, result)
                        if not response.extra.get('loop', False):
                            process_result = False

                except Exception as e:
                    message_reference = await self.handle_error(
                        e, message, context, response, edit=message_reference)
                    self.last_response = message_reference

            else:
                logger.error("Unknown message type: {}".format(
                    response.message_type))
            '''
Ejemplo n.º 36
0
Archivo: core.py Proyecto: sasma/JshBot
        def can_respond(self, message):
            """Determines whether or not the bot can respond.

            Checks that the message has text, matches an invoker, and that the
            guild/channel/user is not muted or blocked. Admins and mods
            override this.
            """
            if self.fresh_boot is None:  # Ignore until bot is ready
                return False
            # Ignore empty messages and messages by bots
            if (not message.content or message.author.bot
                    or message.author.id == self.user.id) and not self.selfbot:
                return False

            # Check that the message starts with a valid invoker
            content = message.content
            if isinstance(message.channel,
                          PrivateChannel):  # No custom invoker or data
                guild_data = {}
                invokers = self.command_invokers
            else:
                guild_data = data.get(self,
                                      'core',
                                      None,
                                      message.guild.id,
                                      default={})
                invokers = [guild_data.get('command_invoker', None)]
                if not invokers[0]:
                    invokers = self.command_invokers
            has_mention_invoker = False
            has_name_invoker = False
            has_nick_invoker = False
            has_regular_invoker = False
            for invoker in invokers:
                if content.startswith(invoker):
                    has_regular_invoker = True
                    break
            if not has_regular_invoker:
                has_mention_invoker = content.startswith(
                    ('<@' + str(self.user.id) + '>',
                     '<@!' + str(self.user.id) + '>'))
                if not has_mention_invoker:
                    clean_content = content.lower()
                    has_name_invoker = clean_content.startswith(
                        self.user.name.lower())
                    if (not has_name_invoker
                            and not isinstance(message.channel, PrivateChannel)
                            and message.guild.me.nick):
                        has_nick_invoker = clean_content.startswith(
                            message.guild.me.nick.lower())
                        if has_nick_invoker:  # Clean up content (nickname)
                            content = content[len(message.guild.me.nick
                                                  ):].strip()
                    else:  # Clean up content (name)
                        content = content[len(self.user.name):].strip()
                else:  # Clean up content (mention)
                    content = content.partition(' ')[2].strip()
            else:  # Clean up content (invoker)
                content = content.partition(invoker)[2].strip()

            if guild_data.get('mention_mode', False):  # Mention mode enabled
                if not (has_mention_invoker or has_name_invoker
                        or has_nick_invoker):
                    return False
            else:  # Any invoker will do
                if not (has_regular_invoker or has_mention_invoker
                        or has_name_invoker or has_nick_invoker):
                    return False

            if self.selfbot:  # Selfbot check
                if message.author.id == self.owners[0]:
                    return [content, False, False, True]
                else:
                    return False

            # Respond to direct messages
            author = message.author
            is_owner = author.id in self.owners
            if isinstance(message.channel, PrivateChannel):
                return [content, False, False, is_owner]

            modrole = data.get(self,
                               'core',
                               'modrole',
                               guild_id=message.guild.id,
                               volatile=True)
            is_mod = modrole in author.roles or author.guild_permissions.administrator
            is_admin = author == message.guild.owner
            result = [content, is_mod, is_admin, is_owner]

            # Owners/moderators override everything
            # This is faster than calling the function in jshbot.data
            channel_id = message.channel.id
            if is_mod or is_admin or is_owner:
                return result
            # Server/channel muted, or user is blocked
            if (guild_data.get('muted', False)
                    or (channel_id in guild_data.get('muted_channels', []))
                    or (author.id in guild_data.get('blocked', []))):
                return False
            else:
                return result  # Clear to respond
Ejemplo n.º 37
0
async def automated_dump_message(bot,
                                 guild,
                                 details,
                                 query=None,
                                 moderator_id=None):
    """Generates an automated log menu that asks the user to select channels to log."""
    try:
        log_channel = _check_log_channel(bot, guild)
    except BotException:
        return

    # Fetch and build preliminary dump data
    logs = data.get(bot, __name__, 'logs', guild_id=guild.id, volatile=True)
    if not logs:
        return
    dump_data = _build_dump_data(bot, logs, log_channel, details=details)
    channel_ids = list(logs.keys())
    channel_mentions = '<#' + '>, <#'.join(str(it) for it in channel_ids) + '>'

    # Show confirmation box
    embed = discord.Embed(title=':warning: Autolog event triggered',
                          color=discord.Color(0xffcc4d))
    embed.add_field(name='Details', value=details, inline=False)
    embed.add_field(
        name='\u200b',
        inline=False,
        value='By default, these channels will be logged:\n{}'.format(
            channel_mentions))
    embed.add_field(
        name='\u200b',
        inline=False,
        value=
        ('Listed channels will be logged in 5 minutes.\n'
         'Click :x: to cancel, :next_track: to log now, or :grey_question: to specify channels.'
         ))

    message = await log_channel.send(embed=embed)

    async def _menu(bot, context, response, result, timed_out):
        if not result and not timed_out:
            return

        # Timed out or skipped waiting period
        if timed_out or (result[0].emoji == '⏭'):
            response.embed.remove_field(2)
            response.embed.set_field_at(1,
                                        name='\u200b',
                                        value='Logging started.')
            await response.message.edit(embed=response.embed)
            asyncio.ensure_future(
                _dump(bot,
                      dump_data,
                      log_channel,
                      details=details,
                      query=query,
                      moderator_id=moderator_id))

        # Cancelled
        elif result[0].emoji == '❌':
            response.embed.remove_field(2)
            response.embed.add_field(name='\u200b', value='Logging cancelled.')
            await response.message.edit(embed=response.embed)

        # Specify channels
        else:
            response.embed.set_field_at(
                2,
                name='\u200b',
                inline=False,
                value=
                '**Type the channels you want to log, separated by spaces.**')
            await response.message.edit(embed=response.embed)
            try:
                await response.message.clear_reactions()
            except:
                pass

            # Read response
            kwargs = {
                'timeout':
                300,
                'check':
                lambda m: m.author == result[1] and m.channel == context.
                channel
            }
            channels = []
            response.embed.remove_field(2)
            try:
                result = await bot.wait_for('message', **kwargs)
                for it in result.content.split():
                    channel = data.get_channel(bot,
                                               it,
                                               constraint=discord.TextChannel)
                    if channel.id not in channel_ids:
                        raise CBException("Channel {} not logged.".format(
                            channel.mention))
                    channels.append(channel)
                try:
                    await result.delete()
                except:
                    pass
            except BotException as e:
                logger.debug("Error!")
                response.embed.set_field_at(
                    1,
                    name='\u200b',
                    value='{}\nDefault channels will be logged.'.format(
                        e.error_details))
            except Exception as e:
                logger.debug("Timeout!")
                response.embed.set_field_at(
                    1,
                    name='\u200b',
                    value=
                    'Channel selection timed out. Default channels will be logged.'
                )

            # Upload dump data
            await response.message.edit(embed=response.embed)
            await _dump(bot,
                        dump_data,
                        log_channel,
                        details=details,
                        query=query,
                        moderator_id=moderator_id,
                        logged_channels=channels)

        return False

    extra = {
        'buttons': ['❌', '⏭', '❔'],
        'elevation': Elevation.BOT_MODERATORS,
        'autodelete': 30,
        'kwargs': {
            'timeout': 300
        }
    }
    response = Response(embed=embed,
                        message_type=MessageTypes.INTERACTIVE,
                        extra=extra,
                        extra_function=_menu)
    await bot.handle_response(message, response, message_reference=message)
Ejemplo n.º 38
0
async def check_commission_advertisement(bot, message):
    """Checks new messages in the commissions channel."""
    if isinstance(message.channel, discord.abc.PrivateChannel):
        return
    guild_data = data.get(bot,
                          __name__,
                          None,
                          guild_id=message.guild.id,
                          default={})
    if (not guild_data.get('rules')
            or message.channel.id != guild_data['rules']['channel']
            or message.author.id in guild_data.get('whitelist', [])
            or message.author.bot):
        return

    cooldown = guild_data['rules']['cooldown']
    advertisement_data = await _get_advertisement_data(
        bot, message.guild, ignore_user_id=message.author.id)
    deleted_persistence = data.get(bot,
                                   __name__,
                                   'recently_deleted',
                                   guild_id=message.guild.id,
                                   default={})
    time_delta = cooldown  # Assume cooldown has been passed
    author_id = message.author.id

    # Check the last advertisement's creation time (if it exists)
    if str(author_id) in deleted_persistence:
        time_delta = time.time() - deleted_persistence[str(author_id)]
    if author_id in advertisement_data:
        last_message = advertisement_data[author_id]
        time_delta = time.time() - last_message.created_at.replace(
            tzinfo=tz.utc).timestamp()

    # Not enough time has passed
    if time_delta < cooldown:
        # content_backup = message.content  # TODO: Consider sending the user a content backup?
        await message.delete()
        wait_for = utilities.get_time_string(cooldown - time_delta,
                                             text=True,
                                             full=True)
        warning = ('You cannot send another advertisement at this time. '
                   'You must wait {}.').format(wait_for)
        await message.author.send(embed=discord.Embed(
            colour=discord.Colour(0xffcc4d), description=warning))
        return

    # Enough time has passed - delete the last message
    elif author_id in advertisement_data:
        try:
            await advertisement_data[author_id].delete()
        except:  # User deleted their advertisement already
            logger.warn("Failed to delete the last advertisement.")

    # Schedule a notification for when a new advertisement post is eligible
    utilities.schedule(bot,
                       __name__,
                       time.time() + cooldown,
                       _notify_advertisement_available,
                       search='c_ad_{}'.format(message.guild.id),
                       destination='u{}'.format(author_id),
                       info='Commission advertisement post eligibility.')

    advertisement_data[author_id] = message
    notification = (
        'Hello! Your advertisement post in the commissions channel has been recorded. '
        '**Please remember that there can only be one message per advertisement**.\n\n'
        'If you want to revise your advertisement [(like adding an image)]'
        '(https://imgur.com/a/qXB2v "Click here for a guide on how to add an image '
        'with a message"), you can delete your advertisement and submit it again, '
        'although this only works within the next 10 minutes and if nobody else has '
        'posted another advertisement after yours.\n\nYou are eligible to post a '
        'new advertisement after the waiting period of {}. When you post a new '
        'advertisement, your previous one will be automatically deleted.\n\n'
        'For convenience, you will be notified when you are eligible to make '
        'a new post.').format(
            utilities.get_time_string(cooldown, text=True, full=True))
    await message.author.send(embed=discord.Embed(
        colour=discord.Colour(0x77b255), description=notification))
Ejemplo n.º 39
0
async def mod_wrapper(bot, message, base, blueprint_index, options, arguments,
                      keywords, cleaned_content):
    response, tts, message_type, extra = ('', False, 0, None)
    mod_action = ''

    if blueprint_index == 0:  # info
        server_data = data.get(bot,
                               'base',
                               None,
                               server_id=message.server.id,
                               default={})
        disabled_commands = server_data.get('disabled', [])
        display_list = []
        for disabled_command in disabled_commands:
            display_list.append('{0} ({1})'.format(
                disabled_command[0],
                'all' if disabled_command[1] == -1 else disabled_command[1] +
                1))
        response = ('```\n'
                    'Information for server {0}\n'
                    'ID: {0.id}\n'
                    'Owner: {0.owner.id}\n'
                    'Moderators: {1}\n'
                    'Blocked users: {2}\n'
                    'Muted: {3}\n'
                    'Muted channels: {4}\n'
                    'Command invoker: {5}\n'
                    'Mention mode: {6}\n'
                    'Disabled commands: {7}```').format(
                        message.server, server_data.get('moderators', []),
                        server_data.get('blocked', []),
                        server_data.get('muted', []),
                        server_data.get('muted_channels', []),
                        server_data.get('command_invoker', None),
                        server_data.get('mention_mode', False), display_list)

    elif blueprint_index == 1:  # Toggle command
        try:  # Explicit index
            split_arguments = arguments[0].split()
            command = bot.commands[split_arguments[0]]
            guess = [command.base, int(split_arguments[1]) - 1]
            assert -1 < guess[1] < len(command.blueprints)
        except IndexError:  # No index
            guess = [command.base, -1]
        except:  # Guess the help index
            guess = list(parser.guess_index(bot, arguments[0]))
        if guess[0] is None:
            raise BotException(EXCEPTION, "Invalid base.")

        command = bot.commands[guess[0]]
        if command.plugin is bot.commands['base'].plugin:
            raise BotException(EXCEPTION,
                               "The base commands cannot be disabled.")
        pass_in = (bot, 'base', 'disabled', guess)
        pass_in_keywords = {'server_id': message.server.id}
        disabled_commands = data.get(*pass_in[:-1],
                                     **pass_in_keywords,
                                     default=[])
        if guess in disabled_commands:
            data.list_data_remove(*pass_in, **pass_in_keywords)
            response = "Enabled"
        else:
            data.list_data_append(*pass_in, **pass_in_keywords)
            response = "Disabled"
        response += " the `{0}` command {1}.".format(
            guess[0], "and all associated subcommands"
            if guess[1] == -1 else "(subcommand {})".format(guess[1] + 1))
        mod_action = response

    elif blueprint_index in (2, 3):  # Block or unblock
        user = data.get_member(bot, arguments[0], message.server)
        block = blueprint_index == 2
        mod_action = 'Blocked {}' if block else 'Unblocked {}'
        mod_action = mod_action.format('{0} ({0.id})'.format(user))
        blocked = data.is_blocked(bot, message.server, user.id, strict=True)
        mod = data.is_mod(bot, message.server, user.id)
        if mod:
            raise BotException(EXCEPTION,
                               "Cannot block or unblock a moderator.")
        elif block:
            if blocked:
                raise BotException(EXCEPTION, "User is already blocked.")
            else:
                data.list_data_append(bot,
                                      'base',
                                      'blocked',
                                      user.id,
                                      server_id=message.server.id)
                response = "User is now blocked."
        else:
            if not blocked:
                raise BotException(EXCEPTION, "User is already unblocked.")
            else:
                data.list_data_remove(bot,
                                      'base',
                                      'blocked',
                                      user.id,
                                      server_id=message.server.id)
                response = "User is now unblocked."

    elif blueprint_index == 4:  # Clear
        response = ('​' + '\n' * 80 +
                    "The chat was pushed up by a bot moderator.")

    elif blueprint_index in (5, 6):  # Mute or unmute
        server_id = message.server.id
        mute = blueprint_index == 5
        mod_action = 'Muted {}' if mute else 'Unmuted {}'

        if arguments[0]:
            channel = data.get_channel(bot, arguments[0], message.server)
            muted = channel.id in data.get(bot,
                                           'base',
                                           'muted_channels',
                                           server_id=server_id,
                                           default=[])
            mod_action = mod_action.format(channel.name)
            if mute:
                if muted:
                    raise BotException(EXCEPTION, "Channel is already muted.")
                else:
                    data.list_data_append(bot,
                                          'base',
                                          'muted_channels',
                                          channel.id,
                                          server_id=server_id)
                    if str(channel.type) == 'voice':  # disconnect
                        await utilities.leave_and_stop(bot, message.server)
                    response = "Channel muted."
            else:  # unmute
                if not muted:
                    raise BotException(EXCEPTION,
                                       "Channel is already unmuted.")
                else:
                    data.list_data_remove(bot,
                                          'base',
                                          'muted_channels',
                                          channel.id,
                                          server_id=server_id)
                    response = "Channel unmuted."

        else:  # server
            mod_action = mod_action.format('the server')
            muted = data.get(bot,
                             'base',
                             'muted',
                             server_id=server_id,
                             default=False)
            if not (muted ^ mute):
                response = "Server is already {}muted.".format(
                    '' if muted else 'un')
                raise BotException(EXCEPTION, response)
            else:
                data.add(bot, 'base', 'muted', mute, server_id=server_id)
                response = "Server {}muted.".format('' if mute else 'un')

    elif blueprint_index == 7:  # Invoker
        if len(arguments[0]) > 10:
            raise BotException(
                EXCEPTION,
                "The invoker can be a maximum of 10 characters long.")
        data.add(bot,
                 'base',
                 'command_invoker',
                 arguments[0] if arguments[0] else None,
                 server_id=message.server.id)
        response = "Custom command invoker {}.".format(
            'set' if arguments[0] else 'cleared')
        if arguments[0]:
            response = "Custom command invoker set."
            mod_action = "Set the server command invoker to '{}'.".format(
                arguments[0])
        else:
            response = "Custom command invoker cleared."
            mod_action = "Removed the custom command invoker."

    elif blueprint_index == 8:  # Mention
        current_mode = data.get(bot,
                                'base',
                                'mention_mode',
                                server_id=message.server.id,
                                default=False)
        data.add(bot,
                 'base',
                 'mention_mode',
                 not current_mode,
                 server_id=message.server.id)
        response = "Mention mode {}activated.".format(
            'de' if current_mode else '')
        mod_action = "{}activated mention mode.".format(
            'de' if current_mode else '').capitalize()

    # Send notification if configured
    send_notifications = data.get(bot,
                                  'base',
                                  'notifications',
                                  server_id=message.server.id,
                                  default=True)
    if mod_action and send_notifications:
        if message.edited_timestamp:
            timestamp = message.edited_timestamp
        else:
            timestamp = message.timestamp
        notification = ('Moderator {0} ({0.id}) from {0.server} on {1}:\n\t'
                        '{2}').format(message.author, timestamp, mod_action)
        logs = await utilities.get_log_text(bot,
                                            message.channel,
                                            limit=20,
                                            before=message)
        logs += '\n{}'.format(utilities.get_formatted_message(message))
        await bot.send_message(message.server.owner, notification)
        await utilities.send_text_as_file(bot, message.server.owner, logs,
                                          'context')

    return (response, tts, message_type, extra)
Ejemplo n.º 40
0
async def base_wrapper(bot, message, base, blueprint_index, options, arguments,
                       keywords, cleaned_content):
    response, tts, message_type, extra = ('', False, 0, None)

    if blueprint_index == 0:  # version
        response = '`{}`\n{}'.format(bot.version, bot.date)

    elif blueprint_index == 1:  # source
        response = random.choice([
            "It's shit. I'm sorry.",
            "You want to see what the Matrix is like?",
            "Script kiddie level stuff in here.",
            "Beware the lack of PEP 8 guidelines inside!",
            "Snarky comments inside and out.",
            "Years down the road, this will all just be a really "
            "embarrassing but funny joke.", "Made with ~~love~~ pure hatred.",
            "At least he's using version control.",
            "Yes, I know I'm not very good. Sorry...",
            "Take it easy on me, okay?", "You're going to groan. A lot.",
            "You might be better off *not* looking inside."
        ])
        response += ("\nhttps://github.com/jkchen2/JshBot\n"
                     "https://github.com/jkchen2/JshBot-plugins")

    elif blueprint_index == 2:  # uptime
        uptime_total_seconds = int(time.time()) - bot.time
        uptime_struct = time.gmtime(uptime_total_seconds)
        days = int(uptime_total_seconds / 86400)
        hours = uptime_struct.tm_hour
        minutes = uptime_struct.tm_min
        seconds = uptime_struct.tm_sec
        response = ("The bot has been on since **{0}**\n{1} days, {2} hours, "
                    "{3} minutes, and {4} seconds").format(
                        bot.readable_time, days, hours, minutes, seconds)

    elif blueprint_index == 3:  # Announcement
        announcement = data.get(bot, 'base', 'announcement')
        if not announcement:
            response = "No announcement right now!"
        else:
            response = announcement

    elif blueprint_index == 4:  # Invite
        if bot.selfbot:
            raise BotException(EXCEPTION, "Nope.")
        response_list = []

        if 'details' in options:
            for plugin in bot.plugins.keys():
                permission_items = data.get(bot,
                                            plugin,
                                            'permissions',
                                            volatile=True,
                                            default={}).items()
                if permission_items:
                    response_list.append('***`{}`***'.format(plugin))
                    response_list.append('\t' + '\n\t'.join([
                        '**`{0[0]}`** -- {0[1]}'.format(item)
                        for item in permission_items
                    ]) + '\n')

        permissions_number = utilities.get_permission_bits(bot)
        app_id = (await bot.application_info()).id
        response_list.append(
            'https://discordapp.com/oauth2/authorize?&client_id={0}'
            '&scope=bot&permissions={1}\n**Remember: you must have the '
            '"Administrator" role on the server you are trying to add the '
            'bot to.**'.format(app_id, permissions_number))
        response = '\n'.join(response_list)

    elif blueprint_index in (5, 6):  # Join/leave voice channel
        if message.channel.is_private:
            raise BotException(
                EXCEPTION, "This command cannot be used in direct messages.")
        voice_channel = message.author.voice_channel
        if not voice_channel:
            raise BotException(EXCEPTION, "You are not in a voice channel.")
        try:
            if blueprint_index == 5:
                await utilities.join_and_ready(bot,
                                               voice_channel,
                                               reconnect=True,
                                               is_mod=data.is_mod(
                                                   bot, message.server,
                                                   message.author.id))
                response = "Joined {}.".format(voice_channel.name)
            else:
                await utilities.leave_and_stop(bot,
                                               message.server,
                                               member=message.author,
                                               safe=False)
                response = "Left {}.".format(voice_channel.name)
        except BotException as e:
            raise e  # Pass up
        except Exception as e:
            action = 'join' if blueprint_index == 5 else 'leave'
            raise BotException(
                EXCEPTION,
                "Failed to {} the voice channel.".format(action),
                e=e)

    return (response, tts, message_type, extra)
Ejemplo n.º 41
0
async def setup_globals(bot):
    global WEBHOOK_SET, TAG_CONVERTER
    TAG_CONVERTER = bot.plugins['tags.py'].TagConverter(
        apply_checks=True, voice_channel_bypass=True)
    WEBHOOK_SET = set(data.get(bot, __name__, 'webhooks', default=[]))
Ejemplo n.º 42
0
async def owner_wrapper(bot, message, base, blueprint_index, options,
                        arguments, keywords, cleaned_content):
    response, tts, message_type, extra = ('', False, 0, None)
    mod_action = ''

    send_notifications = data.get(bot,
                                  'base',
                                  'notifications',
                                  server_id=message.server.id,
                                  default=True)

    if blueprint_index in (0, 1):  # Add or remove moderator
        user = data.get_member(bot, arguments[0], server=message.server)
        user_is_mod = data.is_mod(bot, message.server, user.id, strict=True)
        user_is_elevated = data.is_mod(bot, message.server, user.id)
        blocked = data.is_blocked(bot, message.server, user.id, strict=True)
        mod_action = 'Added {}' if blueprint_index == 0 else 'Removed {}'
        mod_action = mod_action.format(
            '{0} ({0.id}) as a moderator'.format(user))
        if blocked:
            raise BotException(EXCEPTION, "User is blocked.")
        elif blueprint_index == 0:  # add
            if user_is_mod or user_is_elevated:
                raise BotException(EXCEPTION, "User is already a moderator.")
            else:
                data.list_data_append(bot,
                                      'base',
                                      'moderators',
                                      user.id,
                                      server_id=message.server.id)
                response = "User is now a moderator."
        else:  # remove
            if not user_is_mod:
                raise BotException(EXCEPTION,
                                   "User is not in the moderators list.")
            else:
                data.list_data_remove(bot,
                                      'base',
                                      'moderators',
                                      user.id,
                                      server_id=message.server.id)
                response = "User is no longer a moderator."

    elif blueprint_index == 2:  # Send feedback
        if data.get(bot, 'base', 'feedbackdisabled', default=False):
            response = ("Feedback has been temporarily disabled, probably "
                        "due to some troll spammers.")
        else:
            text = arguments[0]
            if len(text) > 1500:
                raise BotException(
                    EXCEPTION, "Whoa! That's a lot of feedback. "
                    "1500 characters or fewer, please.")
            text = ('{0} ({0.id}) on {1.timestamp}:'
                    '\n\t{2}').format(message.author, message, text)
            await utilities.notify_owners(bot, text, user_id=message.author.id)
            response = "Message sent to bot owners."

    elif blueprint_index == 3:  # Toggle notifications
        response = ("Bot moderator activity notifications are now turned "
                    "{}").format("OFF." if send_notifications else "ON.")
        data.add(bot,
                 'base',
                 'notifications',
                 not send_notifications,
                 server_id=message.server.id)

    # Send notification if configured
    if mod_action and send_notifications:
        if message.edited_timestamp:
            timestamp = message.edited_timestamp
        else:
            timestamp = message.timestamp
        notification = 'From {0.server} on {1}, you:\n\t{2}'.format(
            message.author, timestamp, mod_action)
        logs = await utilities.get_log_text(bot,
                                            message.channel,
                                            limit=20,
                                            before=message)
        logs += '\n{}'.format(utilities.get_formatted_message(message))
        await bot.send_message(message.server.owner, notification)
        await utilities.send_text_as_file(bot, message.server.owner, logs,
                                          'context')

    return (response, tts, message_type, extra)
Ejemplo n.º 43
0
def _get_current_game(bot, guild_id):
    latest_index = _update_current_game(bot)
    schedule_data = data.get(bot, __name__, 'schedule', volatile=True)
    return _embed_games_information(bot, [schedule_data[latest_index]],
                                    guild_id)
Ejemplo n.º 44
0
async def _update_schedule(bot):
    """Reads the GDQ schedule and updates the information in the database."""
    schedule_url = configurations.get(bot, __name__, 'schedule_url')
    html_data = (await utilities.future(requests.get, schedule_url)).text
    soup = BeautifulSoup(html_data, 'html.parser')
    run_table = soup.find('table', {'id': 'runTable'})
    schedule_data = []
    game_list = []

    if run_table is None:
        raise CBException('Run table not found!')

    debug_weeks = data.get(bot,
                           __name__,
                           'debug_weeks',
                           default=0,
                           volatile=True)
    current_data = {}
    for entry in run_table.find_all('tr'):
        entry_class = entry.get('class', [''])[0]

        if entry_class == 'day-split':
            continue

        subentries = [subentry.text for subentry in entry.find_all('td')]
        if entry_class == 'second-row':  # Extra data for the last game
            estimation, run_type = subentries
            split_estimate = estimation.split(':')
            estimation_seconds = (int(split_estimate[0]) * 3600 +
                                  int(split_estimate[1]) * 60 +
                                  int(split_estimate[2]))
            end_time = (current_data['scheduled'] + datetime.timedelta(
                seconds=(estimation_seconds + current_data['setup_seconds'])))
            key_name = utilities.get_cleaned_filename(current_data['game'] +
                                                      run_type,
                                                      cleaner=True)
            current_data.update({
                'estimation': estimation.strip(),
                'seconds': estimation_seconds,
                'type': run_type,
                'end': end_time,
                'key': key_name
            })
            game_list.append(key_name)
            schedule_data.append(current_data)

        else:  # Happens first
            while len(subentries) < 4:
                subentries.append('')
            start_time_string, game, runners, setup_time = subentries
            start_time = datetime.datetime.strptime(start_time_string,
                                                    '%Y-%m-%dT%H:%M:%SZ')
            setup_time = setup_time.strip()
            split_setup = setup_time.split(':')
            if len(split_setup) > 1:
                setup_seconds = (int(split_setup[0]) * 3600 +
                                 int(split_setup[1]) * 60 +
                                 int(split_setup[2]))
            else:
                setup_seconds = 0
            current_data = {
                'scheduled':
                start_time - datetime.timedelta(weeks=debug_weeks),
                'game': game,
                'runners': runners,
                'setup': setup_time,
                'setup_seconds': setup_seconds
            }

    # Add finale entry
    run_type = 'Party%'
    end_time = current_data['scheduled'] + datetime.timedelta(minutes=30)
    key_name = utilities.get_cleaned_filename(current_data['game'] + run_type,
                                              cleaner=True)
    current_data.update({
        'estimation': '0:30:00',
        'seconds': 60 * 30,
        'end': end_time,
        'type': run_type,
        'key': key_name
    })
    game_list.append(key_name)
    schedule_data.append(current_data)

    # Update scheduled notifications
    entries = utilities.get_schedule_entries(bot, __name__)
    for entry in entries:
        payload, key = entry[3:5]
        if key not in game_list:  # Not found error
            error_message = (
                ":warning: Warning: The game {} has been removed, renamed, or "
                "recategorized. You have been removed from the notification list "
                "for this game. Please check the schedule at {}.".format(
                    payload['text'],
                    configurations.get(bot, __name__, 'schedule_url')))
            utilities.update_schedule_entries(bot,
                                              __name__,
                                              search=key,
                                              payload={'error': error_message},
                                              time=time.time())
        else:
            game = schedule_data[game_list.index(key)]
            start_time, end_time = game['scheduled'], game['end']
            setup_delta = datetime.timedelta(seconds=game['setup_seconds'])
            scheduled = start_time.replace(
                tzinfo=datetime.timezone.utc).timestamp()
            current_time = datetime.datetime.utcnow()
            if start_time + setup_delta < current_time < end_time:
                stream_url = configurations.get(bot, __name__, 'stream_url')
                payload = {
                    'error':
                    ("Uh oh. The schedule shifted drastically and I didn't notice "
                     "fast enough - sorry! {} is live right now at {}").format(
                         payload['text'], stream_url)
                }
            else:
                payload.update({'end': scheduled + game['seconds']})
            utilities.update_schedule_entries(bot,
                                              __name__,
                                              search=key,
                                              payload=payload,
                                              time=scheduled)

    # Save data
    data.add(bot, __name__, 'schedule', schedule_data, volatile=True)
    try:
        _update_current_game(bot)
    except:
        pass
Ejemplo n.º 45
0
async def get_response(bot, context):
    response = Response()
    use_plugin = configurations.get(bot, __name__, key='enable')
    if not use_plugin:
        response.content = (
            "The GDQ plugin is currently disabled. (GDQ is probably over or hasn't started yet)"
        )
        return response

    embed_template = discord.Embed(
        title='Games Done Quick',
        url='https://gamesdonequick.com/',
        colour=discord.Colour(0x00aff0),
        description='\[ [Stream]({}) ] \[ [Schedule]({}) ] \[ [Donate]({}) ]'.
        format(configurations.get(bot, __name__, 'stream_url'),
               configurations.get(bot, __name__, 'schedule_url'),
               configurations.get(bot, __name__, 'donate_url')))
    embed_template.set_thumbnail(url='http://i.imgur.com/GcdqhUR.png')
    guild_id = context.guild.id if context.guild else None
    if context.index == 0:
        embed_template.add_field(name='Donation stats',
                                 value='Loading...',
                                 inline=False)
        response.game_index = data.get(bot,
                                       __name__,
                                       'current_index',
                                       volatile=True,
                                       default=0)
        schedule_data = data.get(bot, __name__, 'schedule', volatile=True)
        games_list = schedule_data[response.game_index:response.game_index + 5]
        game_data = _embed_games_information(bot, games_list, guild_id)
        value = '\n\n'.join('**{}**\n{}'.format(*it) for it in game_data)
        embed_template.add_field(name='Schedule', value=value, inline=False)
        response.update_stats = True
        response.update_task = None
        response.message_type = MessageTypes.INTERACTIVE
        response.extra_function = gdq_menu
        response.extra = {'buttons': ['⬅', '⏺', '➡']}

    elif context.index == 1:  # About
        embed_template.add_field(
            name='About',
            value=
            ("Games Done Quick (GDQ) is a week-long charity gaming marathon that "
             "brings together speedrunners from around the globe to raise money on a "
             "livestream. They are currently supporting {0}, and all donations go "
             "directly to the charity.\n\nCheck out the links above for the Twitch "
             "stream, games schedule, and the donation portal!").format(
                 configurations.get(bot, __name__, 'charity')))

    elif context.index == 2:  # Status
        status_text = await _get_status(bot)
        embed_template.add_field(name='Status',
                                 value=status_text,
                                 inline=False)

    elif context.index == 3:  # Current game
        embed_data = _get_current_game(bot, guild_id)[0]
        embed_template.add_field(name=embed_data[0],
                                 value=embed_data[1],
                                 inline=False)

    elif context.index == 4:  # Next game(s)
        embed_data = _get_next_games(bot, context.arguments[0], guild_id)
        for name, value in embed_data:
            embed_template.add_field(name=name, value=value, inline=False)

    elif context.index == 5:  # Search
        embed_data = _search_games(bot,
                                   context.arguments[0],
                                   guild_id=guild_id)
        embed_template.add_field(name=embed_data[0],
                                 value=embed_data[1],
                                 inline=False)

    elif context.index == 6:  # Notify
        game = _search_games(bot, context.arguments[0], return_game=True)
        response.content = _toggle_notification(bot,
                                                game,
                                                context,
                                                use_channel='channel'
                                                in context.options)
        embed_template = None

    response.embed = embed_template
    return response
Ejemplo n.º 46
0
async def check_recently_deleted(bot, message):
    """Checks if a user wants to revise their last advertisement."""
    if isinstance(message.channel, discord.abc.PrivateChannel):
        return
    guild_data = data.get(bot,
                          __name__,
                          None,
                          guild_id=message.guild.id,
                          default={})
    if (not guild_data.get('rules')
            or message.channel.id != guild_data['rules']['channel']
            or message.author.id in guild_data.get('whitelist', [])
            or message.author.bot):
        return

    cooldown = guild_data['rules']['cooldown']
    message_time = message.created_at.replace(tzinfo=tz.utc).timestamp()
    advertisement_data = await _get_advertisement_data(bot, message.guild)
    author_id = message.author.id

    # Message mismatch. Ignore deletion
    if advertisement_data.get(author_id) != message:
        return

    # User wants to replace their last message (limit within 10 minutes)
    future_message = await message.channel.history(limit=1,
                                                   after=message).flatten()
    time_delta = time.time() - message_time
    if not future_message and time_delta < 60:
        del advertisement_data[author_id]
        utilities.remove_schedule_entries(bot,
                                          __name__,
                                          search='c_ad_{}'.format(
                                              message.guild.id),
                                          destination='u{}'.format(author_id))
        notification = (
            'Heads up, you have deleted your last advertisement within 10 minutes of posting it '
            '(and nobody else posted an advertisement during that time).\n\n'
            'You can submit a revised advertisement now if you wish.')
        await message.author.send(embed=discord.Embed(description=notification)
                                  )

    # User deleted their advertisement for some reason?
    # Keep message creation time to prevent users from circumventing the cooldown
    elif time_delta < cooldown:
        deleted_persistence = data.get(bot,
                                       __name__,
                                       'recently_deleted',
                                       guild_id=message.guild.id,
                                       default={})

        # Clear any expired entries
        to_remove = [
            k for k, v in deleted_persistence.items()
            if time.time() - v > cooldown
        ]
        for remove_id in to_remove:
            del deleted_persistence[str(remove_id)]

        # Add persistence entry
        deleted_persistence[str(author_id)] = message_time
        data.add(bot,
                 __name__,
                 'recently_deleted',
                 deleted_persistence,
                 guild_id=message.guild.id)
Ejemplo n.º 47
0
async def set_status_on_boot(bot):
    """Checks to see if the status was set previously."""
    previous_status = data.get(bot, __name__, 'status')
    if previous_status:
        await bot.change_presence(activity=discord.Game(name=previous_status))
        logger.info("Detected old status - setting it now!")