class CategoryChannel(DiscordModel): id = fields.BigIntegerField(primary_key=True) guild = fields.GuildField(db_index=True, on_delete=fields.CASCADE) _discord_cls = discord.CategoryChannel _discord_converter_cls = converter.CategoryChannelConverter @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): raise TypeError( f"discord_obj has to be a discord.{cls._discord_cls.__name__} " f"but a {type(discord_obj).__name__} was passed") if create_if_new: guild, _ = Guild.sync_from_discord_obj(discord_obj.guild, create_if_new=create_if_new) obj, created = cls.objects.get_or_create(id=discord_obj.id, guild=guild) else: obj = cls.objects.get(id=discord_obj.id) # obj.guild._discord_obj = discord_obj.guild created = False obj._discord_obj = discord_obj return obj, not created async def fetch(self) -> discord.CategoryChannel: discord_category_channel = self._core.get_channel(self.id) if discord_category_channel is None: discord_category_channel = await self._core.fetch_channel(self.id) self._discord_obj = discord_category_channel # if not self.guild.is_fetched: # await self.guild.fetch() return discord_category_channel
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()
class Role(DiscordModel): id = fields.BigIntegerField(primary_key=True) guild = fields.GuildField(db_index=True, on_delete=fields.CASCADE) _discord_cls = discord.Role _discord_converter_cls = converter.RoleConverter @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): raise TypeError( f"discord_obj has to be a discord.{cls._discord_cls.__name__} " f"but a {type(discord_obj).__name__} was passed") guild, _ = Guild.sync_from_discord_obj(discord_obj.guild) obj = cls(id=discord_obj.id, guild=guild) 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.Role: if not self.guild.is_fetched: await self.guild.fetch() discord_role = self.guild.get_role(self.id) if discord_role is None: discord_role = await self.guild.fetch_role(self.id) self._discord_obj = discord_role return discord_role
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
class Message(DiscordModel): id = fields.BigIntegerField(primary_key=True) channel = fields.TextChannelField(db_index=True, on_delete=fields.CASCADE) author = fields.UserField(db_index=True, on_delete=fields.CASCADE) _discord_cls = discord.Message _discord_converter_cls = converter.MessageConverter @property def guild(self): return self.channel.guild @guild.setter def guild(self, value): if self.channel.is_dm: raise AttributeError("Cannot set guild of private message") self.channel.guild = value @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): raise TypeError( f"discord_obj has to be a discord.{cls._discord_cls.__name__} " f"but a {type(discord_obj).__name__} was passed") channel, _ = TextChannel.sync_from_discord_obj( discord_obj.channel, create_if_new=create_if_new) if discord_obj.guild: user = discord_obj.author._user else: user = discord_obj.author author, _ = User.sync_from_discord_obj(user) if create_if_new: obj, created = cls.objects.get_or_create(id=discord_obj.id, channel=channel, author=author) else: obj = cls.objects.get(id=discord_obj.id) created = False # obj.channel._discord_obj = discord_obj.channel # obj.user._discord_obj = user obj._discord_obj = discord_obj return obj, not created async def fetch(self) -> discord.Message: # if not self.channel.is_fetched: # await self.channel.fetch() channel = await self.channel await channel.fetch() discord_message = await channel.fetch_message(self.id) self._discord_obj = discord_message return discord_message
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
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
class Message(DiscordModel): id = fields.BigIntegerField(primary_key=True) channel = fields.TextChannelField(db_index=True, on_delete=fields.CASCADE) author = fields.UserField(db_index=True, on_delete=fields.CASCADE) _discord_cls = discord.Message _discord_converter_cls = converter.MessageConverter @property def guild(self): return self.channel.guild @guild.setter def guild(self, value): self.channel.guild = value @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): raise TypeError( f"discord_obj has to be a discord.{cls._discord_cls.__name__} " f"but a {type(discord_obj).__name__} was passed") channel, _ = TextChannel.sync_from_discord_obj(discord_obj.channel) author, _ = Member.sync_from_discord_obj(discord_obj.author) obj = cls(id=discord_obj.id, channel=channel, author=author) 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.Message: if not self.channel.is_fetched: await self.channel.fetch() discord_message = self.channel.get_message(self.id) if discord_message is None: discord_message = await self.channel.fetch_message(self.id) self._discord_obj = discord_message return discord_message
class DoublesMatch(models.Model): id = fields.BigIntegerField(primary_key=True) channel = fields.TextChannelField(null=True, db_index=True, on_delete=fields.SET_NULL) guild = fields.GuildField(on_delete=fields.CASCADE) # if tournament is None, it's a matchmaking match tournament = fields.ForeignKey(Tournament, null=True, blank=True, on_delete=fields.CASCADE) in_dms = fields.BooleanField() team_1 = fields.ForeignKey(ParticipantTeam, on_delete=fields.CASCADE) team_2 = fields.ForeignKey(ParticipantTeam, on_delete=fields.CASCADE) team_1_score = fields.SmallIntegerField(default=0) team_2_score = fields.SmallIntegerField(default=0) current_game = fields.SmallIntegerField(default=1) last_game_won_by = fields.SmallIntegerField(null=True) wins_required = fields.SmallIntegerField(default=2) winner = fields.ForeignKey(ParticipantTeam, null=True, blank=True, on_delete=fields.CASCADE)
class TextChannel(DiscordModel): # can also be a DMChannel id = fields.BigIntegerField(primary_key=True) guild = fields.GuildField(db_index=True, null=True, blank=True, on_delete=fields.CASCADE) is_dm = fields.BooleanField(default=False) language = fields.LanguageField() _discord_cls = discord.TextChannel _discord_converter_cls = converter.TextChannelConverter @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): if isinstance(discord_obj, discord.DMChannel): is_dm = True else: raise TypeError( f"discord_obj has to be a discord.{cls._discord_cls.__name__} " f"or a discord.DMChannel " f"but a {type(discord_obj).__name__} was passed") else: is_dm = False if is_dm: if create_if_new: obj, created = cls.objects.get_or_create(id=discord_obj.id, is_dm=True) else: obj = cls.objects.get(id=discord_obj.id) created = False else: if create_if_new: guild, _ = Guild.sync_from_discord_obj(discord_obj.guild) obj, created = cls.objects.get_or_create(id=discord_obj.id, guild=guild, is_dm=False) else: obj = cls.objects.get(id=discord_obj.id) # obj.guild._discord_obj = discord_obj.guild created = False obj._discord_obj = discord_obj return obj, not created @classmethod async def convert(cls, ctx, argument): converter = cls._discord_converter_cls() discord_obj = await converter.convert(ctx, argument) obj, existed_already = await cls.from_discord_obj(discord_obj) if not existed_already: await obj.async_save() return obj async def fetch(self) -> discord.TextChannel: discord_text_channel = self._core.get_channel(self.id) if discord_text_channel is None: discord_text_channel = await self._core.fetch_channel(self.id) self._discord_obj = discord_text_channel # if not self.is_dm and not self.guild.is_fetched: # await self.guild.fetch() return discord_text_channel
class User(DiscordModel): id = fields.BigIntegerField(primary_key=True) is_staff = fields.BooleanField(default=False, db_index=True) is_active = fields.BooleanField(default=True, db_index=True) register_message = fields.MessageField(blank=True, null=True, on_delete=fields.SET_NULL) language = fields.LanguageField() _discord_cls = discord.User _discord_converter_cls = converter.UserConverter @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.ClientUser, discord.Member, MockMember, discord.Object)): raise TypeError( f"discord_obj has to be a discord.{cls._discord_cls.__name__} " f"but a {type(discord_obj).__name__} was passed") if not isinstance(discord_obj, (MockMember, discord.Object)): # if self if discord_obj.id == discord_obj._state.user.id: obj, _ = cls.objects.get_or_create(id=discord_obj.id) return obj, True if discord_obj.bot: raise ValueError("Bot users cannot be stored in the database") qs = cls.objects.filter(id=discord_obj.id) existed_already = qs.exists() if not existed_already: raise UserDoesNotExist(user_id=discord_obj.id) obj = qs.first() if not obj.is_active: raise InactiveUser(user_id=discord_obj.id) if isinstance(discord_obj, discord.Member): discord_obj = discord_obj._user if not isinstance(discord_obj, (MockMember, discord.Object)): obj._discord_obj = discord_obj return obj, existed_already @async_using_db def async_delete(self, using=None, keep_parents=False): self.delete(using=using, keep_parents=keep_parents) def delete(self, using=None, keep_parents=False): _id = self.id name = self.name super().delete(using=using, keep_parents=keep_parents) new_user = User(id=_id, is_active=False) new_user.save() @async_using_db def async_load(self, prefetch_related=True): self.load(prefetch_related=prefetch_related) def load(self, prefetch_related=True): super().load(prefetch_related=True) if not self.is_active: raise InactiveUser(f"The user {self.id} is inactive") @async_using_db def _get_register_message(self): # allows internals to bypass GDPR checks to make the GDPR functionality # itself work, e.g. to handle register reactions super().load(prefetch_related=False) return self.register_message async def fetch(self) -> discord.User: if not self._is_loaded: await self.async_load() discord_user = self._core.get_user(self.id) if discord_user is None: discord_user = await self._core.fetch_user(self.id) self._discord_obj = discord_user return discord_user
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
class User(DiscordModel): id = fields.BigIntegerField(primary_key=True) is_staff = fields.BooleanField(default=False, db_index=True) is_active = fields.BooleanField(default=True, db_index=True) register_message = fields.MessageField(blank=True, null=True, on_delete=fields.SET_NULL) language = fields.LanguageField() _discord_cls = discord.User _discord_converter_cls = converter.UserConverter @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): raise TypeError( f"discord_obj has to be a discord.{cls._discord_cls.__name__} " f"but a {type(discord_obj).__name__} was passed") qs = cls.objects.filter(id=discord_obj.id) existed_already = qs.exists() if not existed_already: raise UserDoesNotExist(user_id=discord_obj.id) obj = qs.first() if not obj.is_active: raise InactiveUser(user_id=discord_obj.id) obj._discord_obj = discord_obj return obj, existed_already @async_using_db def async_delete(self, using=None, keep_parents=False): self.delete(using=using, keep_parents=keep_parents) def delete(self, using=None, keep_parents=False): _id = self.id name = self.name super().delete(using=using, keep_parents=keep_parents) if not User.objects.filter(id=_id).exists(): print("Deleted user", name) new_user = User(id=_id, is_active=False) new_user.save() @async_using_db def async_load(self, prefetch_related=True): super().load(prefetch_related=False) if not self.is_active: raise self.InactiveUser(f"The user {self.id} is inactive") def load(self, prefetch_related=True): super().load(prefetch_related=True) if not self.is_active: raise self.InactiveUser(f"The user {self.id} is inactive") async def fetch(self) -> discord.User: if not self._is_loaded: self.load() discord_user = self._core.get_user(self.id) if discord_user is None: discord_user = await self._core.fetch_user(self.id) self._discord_obj = discord_user return discord_user