<commit_before>import asyncio
import discord
import functools
import json
import re
import sys
import time
import pytz
from collections import namedtuple
from datetime import datetime, timedelta, timezone


def cached_attribute(func):
    """decorator that returns a cached results."""

    @functools.wraps(func)
    def inner_func(server):
        key = func.__name__
        record = cache.setdefault(server, dict())
        if key in record:
            return record[key]
        return record.setdefault(key, func(server))
    return inner_func

# process level client
client = discord.Client()

# process level cache by server
# discord.Server -> dict()
cache = dict()

# bot specific settings
settings = None

#
# Bot Cacheable Attributes
#
# If problem with this crops up, removing the cached_attribute decorator will remove caching behavior.
#
# Some attributes of the bot are fixed when the bot starts, like:
#
#   - The announcement channel where the bot looks and posts new raids
#   - The backup channel where the bot suggests going if raid channels are full
#   - The raid channels, which are looked for the first time and never rescanned
#


#@cached_attribute
def get_raid_additional_roles(server):
    """Gets roles that should get read perms when the raid is started."""
    return [r for r in server.roles if r.name in settings.raid_additional_roles]


def get_raid_organizer_roles(server):
    """Gets roles that can help organizer raids."""
    return [r for r in server.roles if r.name in settings.raid_organizer_roles]


#@cached_attribute
def get_raid_channels(server):
    """Gets the list of raid channels for ther server."""
    raid_channels = []
    rx = re.compile(settings.raid_channel_regex)
    for channel in server.channels:
        p = channel.permissions_for(server.me)
        if rx.search(channel.name) and p.manage_roles and p.manage_messages and p.manage_channels and p.read_messages:
            raid_channels.append(channel)
    return raid_channels


#@cached_attribute
def get_announcement_channel(server):
    """Gets the announcement channel for a server."""
    return discord.utils.find(lambda c: c.name == settings.announcement_channel, server.channels)


#@cached_attribute
def get_backup_channel(server):
    """Gets the announcement channel for a server."""
    return discord.utils.find(lambda c: c.name == settings.backup_raid_channel, server.channels)


#
# End server cacheable attributes
#


#
# Configuration abstraction
#

def get_join_emoji():
    """Gets the join emoji for a server."""
    return settings.raid_join_emoji


def get_leave_emoji():
    """Gets the leave emoji for a server."""
    return settings.raid_leave_emoji


def get_full_emoji():
    """Gets the full emoji for a server."""
    return settings.raid_full_emoji


def strfdelta(tdelta, fmt):
    d = {"days": tdelta.days}
    d["hours"], rem = divmod(tdelta.seconds, 3600)
    d["minutes"], d["seconds"] = divmod(rem, 60)
    return fmt.format(**d)


def adjusted_datetime(dt, tz='US/Eastern'):
    zone = pytz.timezone(tz)
    return dt + zone.utcoffset(dt)


def get_raid_expiration(started_dt):
    return started_dt + timedelta(seconds=settings.raid_duration_seconds)

#
#
#

#
# Raid properties
#


def get_raid_role(channel):
    return discord.utils.find(lambda r: r.name == channel.name, channel.server.roles)


def get_raid_members(channel):
    return [target for target, _ in channel.overwrites if isinstance(target, discord.User)]


def get_raid_start_embed(creator, started_dt, expiration_dt):
    embed = discord.Embed()
    embed.color = discord.Color.green()
    embed.title = 'A raid has started!'
    embed.add_field(name='creator', value=creator.mention, inline=True)
    embed.add_field(name='started at', value=started_dt.strftime(settings.time_format), inline=False)
    embed.add_field(name='channel expires', value=expiration_dt.strftime(settings.time_format), inline=False)
    embed.set_footer(text='To join, tap {} below'.format(get_join_emoji()))
    return embed


def get_raid_end_embed(creator, started_dt, ended_dt):
    duration = ended_dt - started_dt
    embed = discord.Embed()
    embed.color = discord.Color.red()
    embed.title = 'This raid has ended.'
    embed.add_field(name='creator', value=creator.mention if creator else None, inline=True)
    embed.add_field(name='duration', value=strfdelta(duration, '{hours:02}:{minutes:02}:{seconds:02}'), inline=True)
    embed.add_field(name='started at', value=started_dt.strftime(settings.time_format), inline=False)
    embed.add_field(name='ended at', value=ended_dt.strftime(settings.time_format), inline=False)
    return embed


def get_success_embed(text):
    embed = discord.Embed()
    embed.color = discord.Color.green()
    embed.description = text
    return embed


def get_error_embed(text):
    embed = discord.Embed()
    embed.color = discord.Color.red()
    embed.description = text
    return embed


def get_raid_busy_embed(channel):
    embed = discord.Embed()
    embed.color = discord.Color.dark_teal()
    embed.title = 'All raid channels are busy at the moment.'
    embed.description = 'Coordinate this raid in {} instead. More channels will be available later.'.format(channel.mention)
    return embed


def get_raid_members_embed(members):
    embed = discord.Embed()
    embed.title = "Raid Members ({})".format(len(members))
    embed.description = "\n".join(sorted(member.display_name for member in members))
    embed.color = discord.Color.green()
    return embed


def get_raid_summary_embed(creator, channel_name, expiration_dt, text):
    embed = discord.Embed()
    embed.title = 'Welcome to this raid channel!'
    embed.description = "**{}**".format(text)
    embed.add_field(name='creator', value=creator.mention)
    embed.add_field(name='channel expires', value=expiration_dt.strftime(settings.time_format))
    embed.add_field(name='raid group', value='@{}'.format(channel_name), inline=False)
    embed.add_field(name="commands", value="You can use the following commands:", inline=False)
    embed.add_field(name="$leaveraid", value="Removes you from this raid.", inline=False)
    embed.add_field(name="$listraid", value="Shows all current members of this raid channel.", inline=False)
    embed.add_field(name="$endraid", value="Ends the raid and closes the channel.", inline=False)
    embed.set_footer(text='You can also leave the raid with the {} reaction below.'.format(get_leave_emoji()))
    embed.color = discord.Color.green()
    return embed


def is_raid_start_message(message):
    """Whether this is the start of a new raid."""
    if message.role_mentions:
        rx = re.compile(settings.raid_start_regex)
        return any(rx.search(mention.name) for mention in message.role_mentions)


def is_announcement_channel(channel):
    """Whether the channel is the announcement channel."""
    return channel and channel == get_announcement_channel(channel.server)


def is_raid_channel(channel):
    """Whether the channel is a valid raid_channel.
    """
    return channel and channel in get_raid_channels(channel.server)



def is_open(channel):
    return channel.topic is None


async def get_announcement_message(raid_channel):
    """Gets the message that created this channel."""
    server = raid_channel.server
    announcement_channel = get_announcement_channel(server)
    message_id = raid_channel.topic
    try:
        message = await client.get_message(announcement_channel, message_id)
    except:
        return None  # an error occurred, return None TODO: log here
    return message


async def get_raid_creator(raid_channel):
    message = await get_announcement_message(raid_channel)
    if message and message.embeds:
        embed = message.embeds[0]
        fields = embed.get('fields', [])
        if fields:
            creator_mention = fields[0]['value']

            # try in the overwrites
            for target, _ in raid_channel.overwrites:
                if isinstance(target, discord.User) and target.mention == creator_mention:
                    return target

            # otherwise try in the server users (less efficient but right)
            for target in raid_channel.server.members:
                if isinstance(target, discord.User) and target.mention == creator_mention:
                    return target

def get_raid_channel(message):
    """Pulls out the channel field from the message embed."""
    return message.channel_mentions[0] if message.channel_mentions else None


async def is_raid_expired(raid_channel):
    message = await get_announcement_message(raid_channel)
    if message is None:
        return True  # can't find message, clean up the raid channel
    create_ts = message.timestamp.replace(tzinfo=timezone.utc).timestamp()
    return settings.raid_duration_seconds < time.time() - create_ts


def get_available_raid_channel(server):
    """We may need to wrap function calls to this in a lock."""
    for channel in get_raid_channels(server):
        if is_open(channel):
            return channel




async def start_raid_group(user, message_id, description):
    # get the server
    server = user.server

    # find an available raid channel
    channel = get_available_raid_channel(server)

    if channel:
        # set the topic
        await client.edit_channel(channel, topic=message_id)

        # create a role with the same name as this channel
        role = await client.create_role(server, name=channel.name, mentionable=True)

        # get the message
        announcement_channel = get_announcement_channel(server)
        message = await client.get_message(announcement_channel, message_id)

        # calculate expiration time
        expiration_dt = adjusted_datetime(get_raid_expiration(message.timestamp))
        summary_message = await client.send_message(channel, embed=get_raid_summary_embed(user, channel.name, expiration_dt, description))

        # add shortcut reactions for commands
        await client.add_reaction(summary_message, get_leave_emoji())

        # set channel permissions to make raid viewers see the raid.
        for role in get_raid_additional_roles(server):
            perms = discord.PermissionOverwrite(read_messages=True)
            await client.edit_channel_permissions(channel, role, perms)

        return channel

async def end_raid_group(channel):
    # get the creator before we remove roles
    creator = await get_raid_creator(channel)

    # remove all the permissions
    raid_viewer_roles = get_raid_additional_roles(channel.server)
    for target, _ in channel.overwrites:
        if isinstance(target, discord.User) or target in raid_viewer_roles:
            await client.delete_channel_permissions(channel, target)

    # remove the role
    role = get_raid_role(channel)
    if role:
        await client.delete_role(channel.server, role)

    # purge all messages
    await client.purge_from(channel)

    # update the message if its available
    message = await get_announcement_message(channel)
    if message:
        started_dt = adjusted_datetime(message.timestamp)
        ended_dt = adjusted_datetime(datetime.now())
        await client.edit_message(message, embed=get_raid_end_embed(creator, started_dt, ended_dt))
        await client.clear_reactions(message)

    # remove the topic
    channel = await client.edit_channel(channel, topic=None)


async def invite_user_to_raid(channel, user):
    # adds an overwrite for the user
    perms = discord.PermissionOverwrite(read_messages=True)
    await client.edit_channel_permissions(channel, user, perms)<commit_msg>utc now<commit_after>import asyncio
import discord
import functools
import json
import re
import sys
import time
import pytz
from collections import namedtuple
from datetime import datetime, timedelta, timezone


def cached_attribute(func):
    """decorator that returns a cached results."""

    @functools.wraps(func)
    def inner_func(server):
        key = func.__name__
        record = cache.setdefault(server, dict())
        if key in record:
            return record[key]
        return record.setdefault(key, func(server))
    return inner_func

# process level client
client = discord.Client()

# process level cache by server
# discord.Server -> dict()
cache = dict()

# bot specific settings
settings = None

#
# Bot Cacheable Attributes
#
# If problem with this crops up, removing the cached_attribute decorator will remove caching behavior.
#
# Some attributes of the bot are fixed when the bot starts, like:
#
#   - The announcement channel where the bot looks and posts new raids
#   - The backup channel where the bot suggests going if raid channels are full
#   - The raid channels, which are looked for the first time and never rescanned
#


#@cached_attribute
def get_raid_additional_roles(server):
    """Gets roles that should get read perms when the raid is started."""
    return [r for r in server.roles if r.name in settings.raid_additional_roles]


def get_raid_organizer_roles(server):
    """Gets roles that can help organizer raids."""
    return [r for r in server.roles if r.name in settings.raid_organizer_roles]


#@cached_attribute
def get_raid_channels(server):
    """Gets the list of raid channels for ther server."""
    raid_channels = []
    rx = re.compile(settings.raid_channel_regex)
    for channel in server.channels:
        p = channel.permissions_for(server.me)
        if rx.search(channel.name) and p.manage_roles and p.manage_messages and p.manage_channels and p.read_messages:
            raid_channels.append(channel)
    return raid_channels


#@cached_attribute
def get_announcement_channel(server):
    """Gets the announcement channel for a server."""
    return discord.utils.find(lambda c: c.name == settings.announcement_channel, server.channels)


#@cached_attribute
def get_backup_channel(server):
    """Gets the announcement channel for a server."""
    return discord.utils.find(lambda c: c.name == settings.backup_raid_channel, server.channels)


#
# End server cacheable attributes
#


#
# Configuration abstraction
#

def get_join_emoji():
    """Gets the join emoji for a server."""
    return settings.raid_join_emoji


def get_leave_emoji():
    """Gets the leave emoji for a server."""
    return settings.raid_leave_emoji


def get_full_emoji():
    """Gets the full emoji for a server."""
    return settings.raid_full_emoji


def strfdelta(tdelta, fmt):
    d = {"days": tdelta.days}
    d["hours"], rem = divmod(tdelta.seconds, 3600)
    d["minutes"], d["seconds"] = divmod(rem, 60)
    return fmt.format(**d)


def adjusted_datetime(dt, tz='US/Eastern'):
    zone = pytz.timezone(tz)
    return dt + zone.utcoffset(dt)


def get_raid_expiration(started_dt):
    return started_dt + timedelta(seconds=settings.raid_duration_seconds)

#
#
#

#
# Raid properties
#


def get_raid_role(channel):
    return discord.utils.find(lambda r: r.name == channel.name, channel.server.roles)


def get_raid_members(channel):
    return [target for target, _ in channel.overwrites if isinstance(target, discord.User)]


def get_raid_start_embed(creator, started_dt, expiration_dt):
    embed = discord.Embed()
    embed.color = discord.Color.green()
    embed.title = 'A raid has started!'
    embed.add_field(name='creator', value=creator.mention, inline=True)
    embed.add_field(name='started at', value=started_dt.strftime(settings.time_format), inline=False)
    embed.add_field(name='channel expires', value=expiration_dt.strftime(settings.time_format), inline=False)
    embed.set_footer(text='To join, tap {} below'.format(get_join_emoji()))
    return embed


def get_raid_end_embed(creator, started_dt, ended_dt):
    duration = ended_dt - started_dt
    embed = discord.Embed()
    embed.color = discord.Color.red()
    embed.title = 'This raid has ended.'
    embed.add_field(name='creator', value=creator.mention if creator else None, inline=True)
    embed.add_field(name='duration', value=strfdelta(duration, '{hours:02}:{minutes:02}:{seconds:02}'), inline=True)
    embed.add_field(name='started at', value=started_dt.strftime(settings.time_format), inline=False)
    embed.add_field(name='ended at', value=ended_dt.strftime(settings.time_format), inline=False)
    return embed


def get_success_embed(text):
    embed = discord.Embed()
    embed.color = discord.Color.green()
    embed.description = text
    return embed


def get_error_embed(text):
    embed = discord.Embed()
    embed.color = discord.Color.red()
    embed.description = text
    return embed


def get_raid_busy_embed(channel):
    embed = discord.Embed()
    embed.color = discord.Color.dark_teal()
    embed.title = 'All raid channels are busy at the moment.'
    embed.description = 'Coordinate this raid in {} instead. More channels will be available later.'.format(channel.mention)
    return embed


def get_raid_members_embed(members):
    embed = discord.Embed()
    embed.title = "Raid Members ({})".format(len(members))
    embed.description = "\n".join(sorted(member.display_name for member in members))
    embed.color = discord.Color.green()
    return embed


def get_raid_summary_embed(creator, channel_name, expiration_dt, text):
    embed = discord.Embed()
    embed.title = 'Welcome to this raid channel!'
    embed.description = "**{}**".format(text)
    embed.add_field(name='creator', value=creator.mention)
    embed.add_field(name='channel expires', value=expiration_dt.strftime(settings.time_format))
    embed.add_field(name='raid group', value='@{}'.format(channel_name), inline=False)
    embed.add_field(name="commands", value="You can use the following commands:", inline=False)
    embed.add_field(name="$leaveraid", value="Removes you from this raid.", inline=False)
    embed.add_field(name="$listraid", value="Shows all current members of this raid channel.", inline=False)
    embed.add_field(name="$endraid", value="Ends the raid and closes the channel.", inline=False)
    embed.set_footer(text='You can also leave the raid with the {} reaction below.'.format(get_leave_emoji()))
    embed.color = discord.Color.green()
    return embed


def is_raid_start_message(message):
    """Whether this is the start of a new raid."""
    if message.role_mentions:
        rx = re.compile(settings.raid_start_regex)
        return any(rx.search(mention.name) for mention in message.role_mentions)


def is_announcement_channel(channel):
    """Whether the channel is the announcement channel."""
    return channel and channel == get_announcement_channel(channel.server)


def is_raid_channel(channel):
    """Whether the channel is a valid raid_channel.
    """
    return channel and channel in get_raid_channels(channel.server)



def is_open(channel):
    return channel.topic is None


async def get_announcement_message(raid_channel):
    """Gets the message that created this channel."""
    server = raid_channel.server
    announcement_channel = get_announcement_channel(server)
    message_id = raid_channel.topic
    try:
        message = await client.get_message(announcement_channel, message_id)
    except:
        return None  # an error occurred, return None TODO: log here
    return message


async def get_raid_creator(raid_channel):
    message = await get_announcement_message(raid_channel)
    if message and message.embeds:
        embed = message.embeds[0]
        fields = embed.get('fields', [])
        if fields:
            creator_mention = fields[0]['value']

            # try in the overwrites
            for target, _ in raid_channel.overwrites:
                if isinstance(target, discord.User) and target.mention == creator_mention:
                    return target

            # otherwise try in the server users (less efficient but right)
            for target in raid_channel.server.members:
                if isinstance(target, discord.User) and target.mention == creator_mention:
                    return target

def get_raid_channel(message):
    """Pulls out the channel field from the message embed."""
    return message.channel_mentions[0] if message.channel_mentions else None


async def is_raid_expired(raid_channel):
    message = await get_announcement_message(raid_channel)
    if message is None:
        return True  # can't find message, clean up the raid channel
    create_ts = message.timestamp.replace(tzinfo=timezone.utc).timestamp()
    return settings.raid_duration_seconds < time.time() - create_ts


def get_available_raid_channel(server):
    """We may need to wrap function calls to this in a lock."""
    for channel in get_raid_channels(server):
        if is_open(channel):
            return channel




async def start_raid_group(user, message_id, description):
    # get the server
    server = user.server

    # find an available raid channel
    channel = get_available_raid_channel(server)

    if channel:
        # set the topic
        await client.edit_channel(channel, topic=message_id)

        # create a role with the same name as this channel
        role = await client.create_role(server, name=channel.name, mentionable=True)

        # get the message
        announcement_channel = get_announcement_channel(server)
        message = await client.get_message(announcement_channel, message_id)

        # calculate expiration time
        expiration_dt = adjusted_datetime(get_raid_expiration(message.timestamp))
        summary_message = await client.send_message(channel, embed=get_raid_summary_embed(user, channel.name, expiration_dt, description))

        # add shortcut reactions for commands
        await client.add_reaction(summary_message, get_leave_emoji())

        # set channel permissions to make raid viewers see the raid.
        for role in get_raid_additional_roles(server):
            perms = discord.PermissionOverwrite(read_messages=True)
            await client.edit_channel_permissions(channel, role, perms)

        return channel

async def end_raid_group(channel):
    # get the creator before we remove roles
    creator = await get_raid_creator(channel)

    # remove all the permissions
    raid_viewer_roles = get_raid_additional_roles(channel.server)
    for target, _ in channel.overwrites:
        if isinstance(target, discord.User) or target in raid_viewer_roles:
            await client.delete_channel_permissions(channel, target)

    # remove the role
    role = get_raid_role(channel)
    if role:
        await client.delete_role(channel.server, role)

    # purge all messages
    await client.purge_from(channel)

    # update the message if its available
    message = await get_announcement_message(channel)
    if message:
        started_dt = adjusted_datetime(message.timestamp)
        ended_dt = adjusted_datetime(datetime.utcnow())
        await client.edit_message(message, embed=get_raid_end_embed(creator, started_dt, ended_dt))
        await client.clear_reactions(message)

    # remove the topic
    channel = await client.edit_channel(channel, topic=None)


async def invite_user_to_raid(channel, user):
    # adds an overwrite for the user
    perms = discord.PermissionOverwrite(read_messages=True)
    await client.edit_channel_permissions(channel, user, perms)
{'fraction_non_alphanumeric': 0.0935763215716686, 'fraction_numerical': 0.0012924906294429364, 'mean_word_length': 3.8540359682141365, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3351240', 'n_tokens_mistral': 6905, 'n_tokens_neox': 6655, 'n_words': 1876}