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

import logging
from typing import Optional

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 (
    create_bot_client,
    default_bot_client,
    calculate_roles_for_user,
    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 = DiscordUserManager() class Meta: permissions = ( ("access_discord", "Can access the Discord service"), ) def __str__(self): return f'{self.user.username} - {self.uid}' def __repr__(self): 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) -> Optional[bool]: """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) -> Optional[bool]: """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 ) -> Optional[bool]: """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