class DoublesGame(models.Model): match = fields.ForeignKey(DoublesMatch, null=True, on_delete=fields.SET_NULL) number = fields.SmallIntegerField() guild = fields.GuildField(null=True, on_delete=fields.SET_NULL) first_to_strike = fields.ForeignKey(ParticipantTeam, null=True, on_delete=fields.SET_NULL) striking_message = fields.MessageField(null=True, on_delete=fields.SET_NULL) striked_stages = fields.SeparatedValuesField(default=[], max_length=64) suggested_stage = fields.SmallIntegerField(null=True, blank=True) suggested_by = fields.ForeignKey(ParticipantTeam, null=True, on_delete=fields.SET_NULL) suggestion_accepted = fields.BooleanField(null=True, blank=True) picked_stage = fields.SmallIntegerField(null=True, blank=True) winner = fields.ForeignKey(ParticipantTeam, null=True, blank=True, on_delete=fields.SET_NULL) needs_confirmation_by = fields.ForeignKey(ParticipantTeam, null=True, blank=True, on_delete=fields.SET_NULL)
class Game(models.Model): match = fields.ForeignKey(Match, null=True, on_delete=fields.SET_NULL) number = fields.SmallIntegerField() guild = fields.GuildField(null=True, db_index=True, on_delete=fields.SET_NULL) player_1_fighter = fields.SmallIntegerField(null=True, blank=True) player_2_fighter = fields.SmallIntegerField(null=True, blank=True) first_to_strike = fields.UserField(null=True, on_delete=fields.SET_NULL) striking_message = fields.MessageField(null=True, on_delete=fields.SET_NULL) striked_stages = fields.SeparatedValuesField(default=[], max_length=64, converter=Stage.parse, serializer=Stage.serialize) suggested_stage = fields.SmallIntegerField(null=True, blank=True) suggested_by = fields.UserField(null=True, on_delete=fields.SET_NULL) suggestion_accepted = fields.BooleanField(null=True, blank=True) picked_stage = fields.SmallIntegerField(null=True, blank=True) winner = fields.UserField(null=True, blank=True, on_delete=fields.SET_NULL) needs_confirmation_by = fields.UserField(null=True, blank=True, on_delete=fields.SET_NULL) def is_striked(self, stage): return stage in self.striked_stages
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)
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 Match(models.Model): class Meta: get_latest_by = 'started_at' id = fields.BigAutoField(primary_key=True) channel = fields.TextChannelField(null=True, blank=True, db_index=True, unique=True, on_delete=fields.SET_NULL) guild = fields.GuildField(on_delete=fields.CASCADE) voice_channel = fields.VoiceChannelField(null=True, blank=True, on_delete=fields.SET_NULL) # if tournament is None, it's a matchmaking match tournament = fields.ForeignKey(Tournament, null=True, blank=True, on_delete=fields.CASCADE) setup = fields.ForeignKey(MatchmakingSetup, null=True, blank=True, on_delete=fields.SET_NULL) management_message = fields.MessageField(null=True, blank=True, on_delete=fields.SET_NULL) ranked = fields.BooleanField() in_dms = fields.BooleanField() # if matchmaking match, looking is player_1, offering is player_2 player_1 = fields.UserField(db_index=True, on_delete=fields.CASCADE) player_1_rating = fields.IntegerField(null=True, blank=True) player_1_deviation = fields.IntegerField(null=True, blank=True) player_1_volatility = fields.FloatField(null=True, blank=True) player_1_global_rating = fields.IntegerField(null=True, blank=True) player_1_global_deviation = fields.IntegerField(null=True, blank=True) player_1_global_volatility = fields.FloatField(null=True, blank=True) player_1_score = fields.SmallIntegerField(default=0) player_2 = fields.UserField(db_index=True, on_delete=fields.CASCADE) player_2_rating = fields.IntegerField(null=True, blank=True) player_2_deviation = fields.IntegerField(null=True, blank=True) player_2_volatility = fields.FloatField(null=True, blank=True) player_2_global_rating = fields.IntegerField(null=True, blank=True) player_2_global_deviation = fields.IntegerField(null=True, blank=True) player_2_global_volatility = fields.FloatField(null=True, blank=True) player_2_score = fields.SmallIntegerField(default=0) current_game = fields.SmallIntegerField(default=1) wins_required = fields.SmallIntegerField(default=3) ruleset = fields.ForeignKey(Ruleset, null=True, blank=True, on_delete=fields.PROTECT) # if winner is None, match is active / ongoing # if winner is Purah, it was a friendly match winner = fields.UserField(null=True, blank=True, db_index=True, on_delete=fields.CASCADE) started_at = fields.DateTimeField(auto_now_add=True, db_index=True) ended_at = fields.DateTimeField(null=True, blank=True) spectating_message = fields.MessageField(null=True, blank=True, on_delete=fields.SET_NULL) match_end_message = fields.MessageField(null=True, blank=True, on_delete=fields.SET_NULL) @classmethod def ranked_matches_today_qs(cls, player_1, player_2, guild=None): one_day_ago = datetime.now() - timedelta(hours=18) # let's be generous if guild is None: qs = (cls.objects.filter(started_at__gt=one_day_ago, tournament=None, ranked=True, player_1=player_1, player_2=player_2) | cls.objects.filter(started_at__gt=one_day_ago, tournament=None, ranked=True, player_1=player_2, player_2=player_1)) else: qs = (cls.objects.filter(guild=guild, started_at__gt=one_day_ago, tournament=None, ranked=True, player_1=player_1, player_2=player_2) | cls.objects.filter(guild=guild, started_at__gt=one_day_ago, tournament=None, ranked=True, player_1=player_2, player_2=player_1)) return qs @async_using_db def get_match_participants(self): if self.tournament is None: return None, None member_1 = models.Member.objects.get(user=self.player_1, guild=self.guild) participant_1 = Participant.objects.get(pk=member_1) member_2 = models.Member.objects.get(user=self.player_2, guild=self.guild) participant_2 = Participant.objects.get(pk=member_2) return participant_1, participant_2
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
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