示例#1
0
class CoreSettings(Model):
    name = fields.CharField(primary_key=True, max_length=64)
    prefixes = fields.SeparatedValuesField(max_length=256, default=['!'])
    description = fields.TextField(max_length=512, blank=True, null=True)
    status = fields.CharField(max_length=128, blank=True, null=True)
    lang = fields.LanguageField()
    home = fields.GuildField(blank=True, null=True, on_delete=fields.SET_NULL)
示例#2
0
class Guild(DiscordModel):
    # TODO normalize Guild
    register_time = fields.DatetimeField(auto_now_add=True)
    invite_code = fields.CharField(max_length=64, db_index=True)
    url = fields.CharField(max_length=256, unique=True)
    is_deleted = fields.BooleanField(default=False)
    prefix = fields.CharField(max_length=64)
    lang = fields.LanguageField(default=Languages.default.value)
    members = fields.ManyUsersField('hero.User',
                                    through='hero.Member',
                                    forward_key='user',
                                    backward_key='guild',
                                    related_name='guilds')

    @property
    def invite_url(self):
        return f'https://discord.gg/{self.invite_code}'

    @invite_url.setter
    def invite_url(self, value: str):
        if not isinstance(value, str):
            raise TypeError("invite_url must be a str")
        try:
            self.invite_code = value.split('://discord.gg/')[1]
        except IndexError:
            try:
                self.invite_code = value.split('://discordapp.com/invite/')[1]
            except IndexError:
                raise ValueError("Not a valid invite URL.")
示例#3
0
class Guild(DiscordModel):
    id = fields.BigIntegerField(primary_key=True)
    home = fields.BooleanField(default=False)
    # shard_id = fields.SmallIntegerField(db_index=True)
    register_time = fields.DateTimeField(auto_now_add=True)
    invite_code = fields.CharField(null=True,
                                   blank=True,
                                   max_length=64,
                                   db_index=True)
    prefix = fields.CharField(null=True, blank=True, max_length=64)
    notifications_channel = fields.OneToOneField(
        'TextChannel',
        related_name='notifying_guild',
        null=True,
        blank=True,
        on_delete=fields.SET_NULL)
    language = fields.LanguageField()
    members = fields.ManyToManyField('User', through='Member')

    _discord_cls = discord.Guild

    @property
    def invite_url(self):
        if self.invite_code is None:
            return None
        return f'https://discord.gg/{self.invite_code}'

    @invite_url.setter
    def invite_url(self, value: str):
        if not isinstance(value, str):
            raise TypeError("invite_url must be a str")
        try:
            self.invite_code = value.split('://discord.gg/')[1]
        except IndexError:
            try:
                self.invite_code = value.split('://discordapp.com/invite/')[1]
            except IndexError:
                try:
                    self.invite_code = value.split('://discord.com/invite/')[1]
                except IndexError:
                    raise ValueError("Not a valid invite URL.")

    async def notify(self, content: str = None, **send_kwargs):
        notifications_channel = await self.notifications_channel
        if notifications_channel is None:
            raise ValueError("notifications_channel needs to be set first")
        dest = await notifications_channel.fetch()
        msg = await dest.send(content, **send_kwargs)
        return msg

    async def fetch(self) -> discord.Guild:
        discord_guild = self._core.get_guild(self.id)
        if discord_guild is None:
            discord_guild = await self._core.fetch_guild(self.id)
        self._discord_obj = discord_guild
        return discord_guild
示例#4
0
class ScheduledTask(models.Model):
    extension_name = fields.CharField(max_length=64)
    method_name = fields.CharField(max_length=128)
    kwargs = JSONField(null=True, blank=True)
    when = fields.DateTimeField(db_index=True)
    time_tolerance = fields.IntegerField(
        default=300, null=True)  # seconds | None means infinite time tolerance
    context = fields.ForeignKey(Context,
                                null=True,
                                blank=True,
                                on_delete=fields.SET_NULL)
示例#5
0
class CoreSettings(AbstractSettings):
    name = fields.CharField(pk=True, max_length=64)
    token = fields.CharField(max_length=64)
    lang = fields.LanguageField(default=Languages.default.value)

    @property
    def logging_level(self):
        if hero.TEST:
            return logging.DEBUG
        else:
            return logging.WARNING
示例#6
0
class Player(models.Model):
    user = fields.OneToOneField(models.User,
                                primary_key=True,
                                on_delete=fields.CASCADE)
    challonge_username = fields.CharField(null=True,
                                          blank=True,
                                          unique=True,
                                          max_length=64)
    challonge_user_id = fields.BigIntegerField(null=True,
                                               blank=True,
                                               unique=True)
    region = RegionField(null=True, blank=True, db_index=True)
    rating = fields.IntegerField(db_index=True, default=1500)
    deviation = fields.IntegerField(default=350)
    volatility = fields.FloatField(default=0.06)

    @async_using_db
    def get_last_ranked_match(self):
        from ..models import Match
        user = self.user
        qs = (Match.objects.filter(
            guild__guildsetup__verified=True, ranked=True, player_1=user)
              | Match.objects.filter(guild__guildsetup__verified=True,
                                     ranked=True,
                                     player_2=user))
        return qs.latest()
示例#7
0
class Ruleset(models.Model):
    class Meta:
        unique_together = (('name', 'guild', 'version'), )
        get_latest_by = 'version'

    name = fields.CharField(max_length=128)
    guild = fields.GuildField(db_index=True, on_delete=fields.CASCADE)
    version = fields.IntegerField(default=1)
    starter_stages = fields.SeparatedValuesField(
        default=Stage.get_default_starters,
        max_length=64,
        converter=Stage.parse,
        serializer=Stage.serialize)
    counterpick_stages = fields.SeparatedValuesField(
        default=Stage.get_default_counterpicks,
        max_length=64,
        converter=Stage.parse,
        serializer=Stage.serialize)
    counterpick_bans = fields.SmallIntegerField(default=2)
    dsr = DSRField(default=DSR('on'))

    @classmethod
    async def convert(cls, ctx, argument):
        try:
            argument = int(argument)
        except ValueError:
            raise BadArgument(
                f"{argument} is not a valid identifier for a ruleset")
        return await cls.async_get(pk=argument)

    def __str__(self):
        return self.name
示例#8
0
class Guild(DiscordModel):
    id = fields.BigIntegerField(primary_key=True)
    home = fields.BooleanField(default=False)
    shard_id = fields.SmallIntegerField(db_index=True)
    register_time = fields.DateTimeField(auto_now_add=True)
    invite_code = fields.CharField(null=True,
                                   blank=True,
                                   max_length=64,
                                   db_index=True)
    prefix = fields.CharField(null=True, blank=True, max_length=64)
    language = fields.LanguageField()
    members = fields.ManyToManyField(to='User', through='Member')
    moderating_guild = fields.GuildField(null=True,
                                         blank=True,
                                         on_delete=fields.SET_NULL)

    _discord_cls = discord.Guild

    @property
    def invite_url(self):
        if self.invite_code is None:
            return None
        return f'https://discord.gg/{self.invite_code}'

    @invite_url.setter
    def invite_url(self, value: str):
        if not isinstance(value, str):
            raise TypeError("invite_url must be a str")
        try:
            self.invite_code = value.split('://discord.gg/')[1]
        except IndexError:
            try:
                self.invite_code = value.split('://discordapp.com/invite/')[1]
            except IndexError:
                try:
                    self.invite_code = value.split('://discord.com/invite/')[1]
                except IndexError:
                    raise ValueError("Not a valid invite URL.")

    async def fetch(self) -> discord.Guild:
        discord_guild = self._core.get_guild(self.id)
        if discord_guild is None:
            discord_guild = await self._core.fetch_guild(self.id)
        self._discord_obj = discord_guild
        return discord_guild
示例#9
0
class Emoji(DiscordModel):
    id = fields.BigAutoField(primary_key=True)
    name = fields.CharField(max_length=64)
    animated = fields.BooleanField(default=False)
    is_custom = fields.BooleanField()

    _discord_cls = discord.PartialEmoji
    _discord_converter_cls = converter.PartialEmojiConverter

    @classmethod
    def sync_from_discord_obj(cls, discord_obj, create_if_new=True):
        """Create a Hero object from a Discord object"""
        if not isinstance(discord_obj, (cls._discord_cls, discord.Emoji)):
            raise TypeError(
                f"discord_obj has to be a discord.{cls._discord_cls.__name__} "
                f"or discord.Emoji"
                f"but a {type(discord_obj).__name__} was passed")
        if isinstance(discord_obj, discord.Emoji):
            discord_obj = discord.PartialEmoji(name=discord_obj.name,
                                               animated=discord_obj.animated,
                                               id=discord_obj.id)
        if discord_obj.is_custom_emoji():
            obj, created = cls.objects.get_or_create(
                id=discord_obj.id,
                name=discord_obj.name,
                animated=discord_obj.animated,
                is_custom=True)
        else:
            obj, created = cls.objects.get_or_create(name=discord_obj.name,
                                                     animated=False,
                                                     is_custom=False)

        obj._discord_obj = discord_obj
        return obj, not created

    async def fetch(self) -> discord.PartialEmoji:
        if self.is_custom:
            # if not self.guild.is_fetched:
            #     await self.guild.fetch()
            guild = await self.guild
            await guild.fetch()
            emoji = await guild.fetch_emoji(self.id)
            discord_emoji = discord.PartialEmoji(name=emoji.name,
                                                 animated=emoji.animated,
                                                 id=emoji.id)
            if self.name != emoji.name:
                self.name = emoji.name
                await self.async_save()
        else:
            discord_emoji = discord.PartialEmoji(name=self.name)
        self._discord_obj = discord_emoji

        return discord_emoji
示例#10
0
class MatchmakingSetup(models.Model):
    channel = fields.OneToOneField(models.TextChannel,
                                   primary_key=True,
                                   on_delete=fields.CASCADE)
    name = fields.CharField(max_length=64)
    matchmaking_message = fields.MessageField(on_delete=fields.CASCADE)
    ruleset = fields.ForeignKey(Ruleset,
                                null=True,
                                blank=True,
                                on_delete=fields.SET_NULL)
    looking_role = fields.RoleField(on_delete=fields.CASCADE)
    available_role = fields.RoleField(on_delete=fields.CASCADE)
    ranked = fields.BooleanField(default=False)
示例#11
0
class Emoji(DiscordModel):
    id = fields.BigIntegerField(primary_key=True, auto_created=True)
    name = fields.CharField(max_length=64)
    animated = fields.BooleanField(default=False)
    is_custom = fields.BooleanField()

    _discord_cls = discord.PartialEmoji
    _discord_converter_cls = converter.PartialEmojiConverter

    @classmethod
    def sync_from_discord_obj(cls, discord_obj):
        """Create a Hero object from a Discord object"""
        if not isinstance(discord_obj, (cls._discord_cls, discord.Emoji)):
            raise TypeError(
                f"discord_obj has to be a discord.{cls._discord_cls.__name__} "
                f"or discord.Emoji"
                f"but a {type(discord_obj).__name__} was passed")
        if isinstance(discord_obj, discord.Emoji):
            discord_obj = discord.PartialEmoji(name=discord_obj.name,
                                               animated=discord_obj.animated,
                                               id=discord_obj.id)
        obj = cls(id=discord_obj.id,
                  name=discord_obj.name,
                  animated=discord_obj.animated)
        obj._discord_obj = discord_obj
        try:
            obj.load()
            existed_already = True
        except cls.DoesNotExist:
            existed_already = False
        return obj, existed_already

    async def fetch(self) -> discord.PartialEmoji:
        if self.is_custom:
            if not self.guild.is_fetched:
                await self.guild.fetch()
            self.guild: discord.Guild
            emoji = await self.guild.fetch_emoji(self.id)
            discord_emoji = discord.PartialEmoji(name=emoji.name,
                                                 animated=emoji.animated,
                                                 id=emoji.id)
            if self.name != emoji.name:
                self.name = emoji.name
                await self.async_save()
        else:
            discord_emoji = discord.PartialEmoji(name=self.name)
        self._discord_obj = discord_emoji

        return discord_emoji
示例#12
0
class Team(models.Model):
    class Meta:
        unique_together = (('member_1', 'member_2'), )

    # member_1 is always the user with the lower user ID
    # to avoid duplicate teams
    member_1 = fields.ForeignKey(Player,
                                 db_index=True,
                                 on_delete=fields.CASCADE)
    member_2 = fields.ForeignKey(Player,
                                 db_index=True,
                                 on_delete=fields.CASCADE)
    custom_name = fields.CharField(null=True, max_length=64)
    current_tournament = fields.ForeignKey(Tournament,
                                           null=True,
                                           blank=True,
                                           on_delete=fields.SET_NULL)
    current_participant_team = fields.ForeignKey(ParticipantTeam,
                                                 null=True,
                                                 blank=True,
                                                 on_delete=fields.SET_NULL)
    elo = fields.IntegerField(db_index=True, default=1000)
示例#13
0
class SsbuSettings(models.Settings):
    challonge_username = fields.CharField(max_length=64)
    challonge_api_key = fields.CharField(max_length=128)
示例#14
0
class Tournament(models.Model):
    id = fields.BigIntegerField(primary_key=True)  # Challonge ID
    key = fields.CharField(max_length=128, unique=True)
    name = fields.CharField(max_length=128)
    series = fields.ForeignKey(TournamentSeries,
                               null=True,
                               blank=True,
                               db_index=True,
                               on_delete=fields.SET_NULL)
    ranked = fields.BooleanField()
    signup_message = fields.MessageField(unique=True,
                                         db_index=True,
                                         null=True,
                                         on_delete=fields.SET_NULL)
    checkin_message = fields.MessageField(unique=True,
                                          db_index=True,
                                          null=True,
                                          blank=True,
                                          on_delete=fields.SET_NULL)
    # signup_emoji = fields.EmojiField(default=get_default_emoji, on_delete=fields.SET_DEFAULT)
    # checkin_emoji = fields.EmojiField(default=get_default_emoji, on_delete=fields.SET_DEFAULT)
    guild = fields.GuildField(db_index=True, on_delete=fields.CASCADE)
    announcements_channel = fields.TextChannelField(null=True,
                                                    on_delete=fields.SET_NULL)
    talk_channel = fields.TextChannelField(null=True,
                                           blank=True,
                                           on_delete=fields.SET_NULL)
    participant_role = fields.RoleField(null=True,
                                        unique=True,
                                        on_delete=fields.SET_NULL)
    organizer_role = fields.RoleField(null=True, on_delete=fields.SET_NULL)
    streamer_role = fields.RoleField(null=True,
                                     blank=True,
                                     on_delete=fields.SET_NULL)
    doubles = fields.BooleanField(db_index=True)
    format = FormatField(default=Formats.double_elimination)
    allow_matches_in_dms = fields.BooleanField()
    # don't hard delete rulesets that have already been used
    # instead, the ruleset should be swapped out with the updated version
    # and only for upcoming and ongoing tournaments
    ruleset = fields.ForeignKey(Ruleset, on_delete=fields.PROTECT)
    start_time = fields.DateTimeField(db_index=True)
    delay_start = fields.SmallIntegerField(null=True, blank=True)  # minutes
    start_task = fields.ForeignKey(ScheduledTask,
                                   null=True,
                                   on_delete=fields.SET_NULL)
    start_checkin_task = fields.ForeignKey(ScheduledTask,
                                           null=True,
                                           on_delete=fields.SET_NULL)
    check_reactions_task = fields.ForeignKey(ScheduledTask,
                                             null=True,
                                             on_delete=fields.SET_NULL)
    ended = fields.BooleanField(db_index=True, default=False)

    @property
    def full_challonge_url(self):
        return f"https://challonge.com/{self.key}"

    async def get_challonge_tournament(self):
        core = self._core
        extension_name = self._meta.app_label
        ssbu = core.get_controller(extension_name)
        return await ssbu.get_challonge_tournament(self.id)

    @classmethod
    async def convert(cls, ctx, argument):
        try:
            argument = int(argument)
            # argument is Challonge ID
            tournament = await cls.async_get(pk=argument)
        except ValueError:
            # argument is URL key
            tournament = await cls.async_get(key=argument)
        return tournament
示例#15
0
class Context(models.DiscordModel):
    message = fields.MessageField(null=True,
                                  blank=True,
                                  on_delete=fields.SET_NULL)
    prefix = fields.CharField(max_length=64, null=True, blank=True)
    command_name = fields.CharField(max_length=256, null=True, blank=True)
    args_and_kwargs = JSONField(default={})

    _discord_cls = Context

    @property
    def args(self):
        if self.is_fetched:
            return self._discord_obj.args
        return self.args_and_kwargs['args']

    @property
    def kwargs(self):
        if self.is_fetched:
            return self._discord_obj.kwargs
        kwargs = self.args_and_kwargs.copy()
        if 'args' in kwargs:
            kwargs.pop('args')
        return kwargs

    @property
    def _state(self):
        if hasattr(self, '_discord_obj'):
            return self._discord_obj._state
        else:
            return self.message._state

    @classmethod
    @async_using_db
    def from_discord_obj(cls, discord_obj):
        message = async_to_sync(
            models.Message.from_discord_obj(discord_obj.message))
        prefix = discord_obj.prefix
        command_name = discord_obj.command_name
        args_and_kwargs = discord_obj.kwargs or {}
        if discord_obj.args:
            args_and_kwargs['args'] = discord_obj.args
        if not args_and_kwargs:
            args_and_kwargs = None
        return cls(message=message,
                   prefix=prefix,
                   command_name=command_name,
                   args_and_kwargs=args_and_kwargs)

    async def fetch(self):
        if self.message is not None:
            message = await self.message.fetch()
            args_and_kwargs = self.args_and_kwargs.copy()
            self._core: hero.Core
            command = self._core.get_command(self.command_name)
            ctx = hero.Context(message=message,
                               bot=self._core,
                               args=args_and_kwargs.pop('args', []),
                               kwargs=args_and_kwargs,
                               prefix=self.prefix,
                               command=command)
            self._discord_obj = ctx
            return ctx
        else:
            return False
示例#16
0
class Emoji(DiscordModel):
    guild = fields.GuildField(on_delete=fields.CASCADE)
    name = fields.CharField(max_length=64)
示例#17
0
class UserGroup(Model):
    name = fields.CharField(max_length=64, unique=True)
示例#18
0
class TournamentSeries(models.Model):
    class Meta:
        unique_together = (('name', 'guild'), )

    key_prefix = fields.CharField(primary_key=True, max_length=128)
    guild = fields.GuildField(db_index=True, on_delete=fields.CASCADE)
    name = fields.CharField(max_length=128)
    next_iteration = fields.IntegerField(default=1)
    ranked = fields.BooleanField()
    admins = fields.ManyToManyField(Player, null=True, blank=True)
    participant_role = fields.RoleField(
        null=True, unique=True, on_delete=fields.SET_NULL
    )  # cancel tournament creation if not found on Discord
    organizer_role = fields.RoleField(
        null=True, on_delete=fields.SET_NULL
    )  # cancel tournament creation if not found on Discord
    streamer_role = fields.RoleField(
        null=True, blank=True,
        on_delete=fields.SET_NULL)  # delete if not found on Discord
    # signup_emoji = fields.EmojiField(default=get_default_emoji, on_delete=fields.SET_DEFAULT)
    # checkin_emoji = fields.EmojiField(default=get_default_emoji, on_delete=fields.SET_DEFAULT)
    announcements_channel = fields.TextChannelField(
        null=True,
        on_delete=fields.SET_NULL)  # if None, cancel tournament creation
    talk_channel = fields.TextChannelField(null=True,
                                           blank=True,
                                           on_delete=fields.SET_NULL)
    introduction = fields.TextField(max_length=2048)
    default_participants_limit = fields.IntegerField(default=512)
    last_start_time = fields.DateTimeField(null=True, blank=True)
    delay_start = fields.SmallIntegerField(null=True, blank=True)  # minutes
    interval = IntervalField(null=True, blank=True)
    doubles = fields.BooleanField(db_index=True)
    format = FormatField(default=Formats.double_elimination)
    affects_elo = fields.BooleanField(default=True)
    allow_matches_in_dms = fields.BooleanField()
    ruleset = fields.ForeignKey(Ruleset,
                                null=True,
                                blank=True,
                                on_delete=fields.SET_NULL)

    cancelled = fields.BooleanField(default=False)

    @property
    def next_start_time(self):
        self.last_start_time: datetime.datetime
        if self.last_start_time is None or self.interval is None:
            return None
        if self.interval == Intervals.MONTHLY:
            weekday = self.last_start_time.weekday()
            day_number = self.last_start_time.day

    @classmethod
    async def convert(cls, ctx, argument):
        try:
            tournament_series = await cls.async_get(pk=argument)
        except ObjectDoesNotExist:
            try:
                guild = await models.Guild.from_discord_obj(ctx.guild)
                tournament_series = await cls.async_get(guild=guild,
                                                        name=argument)
            except ObjectDoesNotExist:
                raise BadArgument(
                    f"{argument} does not seem to be a valid tournament series."
                )
        return tournament_series