Source code for allianceauth.services.modules.discord.models

import logging
from typing import ClassVar

from requests.exceptions import HTTPError

from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import gettext_lazy

from allianceauth.notifications import notify

from . import __title__
from .app_settings import DISCORD_GUILD_ID
from .core import calculate_roles_for_user, create_bot_client, default_bot_client, user_formatted_nick
from .discord_client import DiscordApiBackoff
from .managers import DiscordUserManager
from .utils import LoggerAddTag

logger = LoggerAddTag(logging.getLogger(__name__), __title__)


[docs] class DiscordUser(models.Model): """The Discord user account of an Auth user.""" user = models.OneToOneField( User, primary_key=True, on_delete=models.CASCADE, related_name="discord", help_text="Auth user owning this Discord account", ) uid = models.BigIntegerField(db_index=True, help_text="user's ID on Discord") username = models.CharField( max_length=32, default="", blank=True, db_index=True, help_text="user's username on Discord" ) discriminator = models.CharField(max_length=4, default="", blank=True, help_text="user's discriminator on Discord") activated = models.DateTimeField( default=None, null=True, blank=True, help_text="Date & time this service account was activated" ) objects: ClassVar[DiscordUserManager] = DiscordUserManager() class Meta: default_permissions = () permissions = (("access_discord", "Can access the Discord service"),) def __str__(self) -> str: return f"{self.user.username} - {self.uid}" def __repr__(self) -> str: return f"{type(self).__name__}(user='{self.user}', uid={self.uid})" def update_nickname(self, nickname: str = None) -> bool: """Update nickname with formatted name of main character Params: - nickname: optional nickname to be used instead of user's main Returns: - True on success - None if user is no longer a member of the Discord server - False on error or raises exception """ if not nickname: nickname = user_formatted_nick(self.user) if not nickname: return False success = default_bot_client.modify_guild_member(guild_id=DISCORD_GUILD_ID, user_id=self.uid, nick=nickname) if success: logger.info("Nickname for %s has been updated", self.user) else: logger.warning("Failed to update nickname for %s", self.user) return success def update_groups(self, state_name: str = None) -> bool | None: """update groups for a user based on his current group memberships. Will add or remove roles of a user as needed. Params: - state_name: optional state name to be used Returns: - True on success - None if user is no longer a member of the Discord server - False on error or raises exception """ new_roles, is_changed = calculate_roles_for_user( user=self.user, client=default_bot_client, discord_uid=self.uid, state_name=state_name ) if is_changed is None: logger.debug("User is not a member of this guild %s", self.user) return None if is_changed: logger.debug("Need to update roles for user %s", self.user) success = default_bot_client.modify_guild_member( guild_id=DISCORD_GUILD_ID, user_id=self.uid, role_ids=list(new_roles.ids()) ) if success: logger.info("Roles for %s have been updated", self.user) else: logger.warning("Failed to update roles for %s", self.user) return success logger.info("No need to update roles for user %s", self.user) return True def update_username(self) -> bool | None: """Updates the username incl. the discriminator from the Discord server and saves it Returns: - True on success - None if user is no longer a member of the Discord server """ member_info = default_bot_client.guild_member(guild_id=DISCORD_GUILD_ID, user_id=self.uid) if not member_info: logger.warning("%s: User not a guild member", self.user) return None self.username = member_info.user.username self.discriminator = member_info.user.discriminator self.save() logger.info("%s: Username has been updated", self.user) return True def delete_user( self, notify_user: bool = False, is_rate_limited: bool = True, handle_api_exceptions: bool = False ) -> bool | None: """Deletes the Discount user both on the server and locally Params: - notify_user: When True will sent a notification to the user informing him about the deleting of his account - is_rate_limited: When False will disable default rate limiting (use with care) - handle_api_exceptions: When True method will return False when an API exception occurs Returns True when successful, otherwise False or raises exceptions Return None if user does no longer exist """ try: _user = self.user client = create_bot_client(is_rate_limited=is_rate_limited) success = client.remove_guild_member(guild_id=DISCORD_GUILD_ID, user_id=self.uid) if success is not False: deleted_count, _ = self.delete() if deleted_count > 0: if notify_user: notify( user=_user, title=gettext_lazy("Discord Account Disabled"), message=gettext_lazy( "Your Discord account was disabled automatically " "by Auth. If you think this was a mistake, " "please contact an admin." ), level="warning", ) logger.info("Account for user %s was deleted.", _user) return True logger.debug("Account for user %s was already deleted.", _user) return None logger.warning("Failed to remove user %s from the Discord server", _user) return False except (HTTPError, ConnectionError, DiscordApiBackoff) as ex: if handle_api_exceptions: logger.exception("Failed to remove user %s from Discord server: %s", self.user, ex) return False raise ex