def verify_ts3_accounts(self) -> None: if not self.bot.ts3c: raise ConnectionError("Not connected yet.") # Retrieve users users = self.bot.exec_("clientdblist", duration=200) start = 0 while len(users) > 0: for counter, user in enumerate(users): uid = user["client_unique_identifier"] cldbid = user["cldbid"] # Skip SQ account if "ServerQuery" in uid: continue # Send keepalive if counter % 100 == 0: self.bot.ts3c.send_keepalive() # Get user's account account = models.Account.get_by_identity(self.session, uid) if not account: self.revoke(None, cldbid) else: # User was checked, don't check again if ts3bot.timedelta_hours(datetime.datetime.today( ) - account.last_check) < Config.getfloat( "verify", "cycle_hours") and not (self.verify_all): continue LOG.info("Checking %s/%s", account, uid) try: account.update(self.session) # Sync groups ts3bot.sync_groups(self.bot, cldbid, account) except ts3bot.InvalidKeyException: self.revoke(account, cldbid) except ts3bot.ApiErrBadData: LOG.warning( "Got ErrBadData for this account after multiple attempts." ) except requests.RequestException: LOG.exception("Error during API call") raise # Skip to next user block start += len(users) try: users = self.bot.exec_("clientdblist", start=start, duration=200) except ts3.query.TS3QueryError as e: # Fetching users failed, most likely at end if e.args[0].error["id"] != "1281": LOG.exception("Error retrieving user list") users = []
def handle(bot: Bot, event: events.TextMessage, match: typing.Match): cldbid = bot.exec_("clientgetdbidfromuid", cluid=event.uid)[0]["cldbid"] # Grab user's account account = models.Account.get_by_identity(bot.session, event.uid) if not account or not account.is_valid: bot.send_message(event.id, "missing_token") return on_join_hours_timeout = Config.getfloat("verify", "on_join_hours") # Saved account is older than x hours or has no guilds if ( timedelta_hours(datetime.datetime.today() - account.last_check) >= on_join_hours_timeout or account.guilds.count() == 0 ): bot.send_message(event.id, "account_updating") try: account.update(bot.session) # Sync groups in case the user has left a guild or similar changes sync_groups(bot, cldbid, account) except InvalidKeyException: # Invalidate link account.invalidate(bot.session) sync_groups(bot, cldbid, account, remove_all=True) logging.info("Revoked user's permissions.") bot.send_message(event.id, "invalid_token_admin") return except (requests.RequestException, RateLimitException, ApiErrBadData): logging.exception("Error during API call") bot.send_message(event.id, "error_api") # User requested guild removal if match.group(1) and match.group(1).lower() == "remove": # Get active guilds has_active_guilds: int = ( account.guilds.join(models.Guild) .filter(models.Guild.group_id.isnot(None)) .filter(models.LinkAccountGuild.is_active.is_(True)) .count() ) # There are no active guilds, no need to remove anything if not has_active_guilds: bot.send_message(event.id, "guild_already_removed") return # Remove guilds account.guilds.filter(models.LinkAccountGuild.is_active.is_(True)).update( {"is_active": False} ) bot.session.commit() # Sync groups changes = sync_groups(bot, cldbid, account) if len(changes["removed"]) > 0: bot.send_message(event.id, "guild_removed") else: bot.send_message(event.id, "guild_error") return available_guilds = account.guilds.join(models.Guild).filter( models.Guild.group_id.isnot(None) ) # No guild specified if not match.group(1): available_guilds = available_guilds.all() if len(available_guilds) > 0: bot.send_message( event.id, "guild_selection", guilds="\n- ".join([_.guild.tag for _ in available_guilds]), ) else: bot.send_message(event.id, "guild_unknown") else: guild = match.group(1).lower() selected_guild: typing.Optional[ models.LinkAccountGuild ] = available_guilds.filter(models.Guild.tag.ilike(guild)).one_or_none() # Guild not found or user not in guild if not selected_guild: bot.send_message( event.id, "guild_invalid_selection", timeout=on_join_hours_timeout ) return # Toggle guild if selected_guild.is_active: selected_guild.is_active = False else: selected_guild.is_active = True # Remove other guilds if only one is allowed if not Config.getboolean("guild", "allow_multiple_guilds"): account.guilds.filter( models.LinkAccountGuild.id != selected_guild.id ).update({"is_active": False}) bot.session.commit() # Sync groups changes = sync_groups(bot, cldbid, account) if selected_guild.is_active and len(changes["added"]): bot.send_message(event.id, "guild_set", guild=selected_guild.guild.name) elif not selected_guild.is_active and len(changes["removed"]): bot.send_message( event.id, "guild_removed_one", guild=selected_guild.guild.name ) else: bot.send_message(event.id, "guild_error")
def handle(bot: Bot, event: events.TextMessage, match: Match) -> None: key = match.group(1) # Check with ArenaNet's API try: account_info = fetch_api("account", api_key=key) # Check if one of the guilds is in the alliance is_part_of_alliance = (bot.session.query(models.Guild).filter( models.Guild.guid.in_(account_info.get("guilds", []))).filter( models.Guild.is_part_of_alliance.is_(True)).count() > 0) # One of the guilds is in the alliance if is_part_of_alliance: account: models.Account = models.Account.get_or_create( bot.session, account_info, key) identity: models.Identity = models.Identity.get_or_create( bot.session, event.uid) # Check if account is registered to anyone linked_identity: Optional[ models. LinkAccountIdentity] = account.valid_identities.one_or_none() # Account is already linked if linked_identity: # Account is linked to another guid if linked_identity.identity.guid != event.uid: try: # Get user's DB id cldbid: str = bot.exec_("clientgetdbidfromuid", cluid=event.uid)[0]["cldbid"] except ts3.TS3Error: LOG.error("Failed to get user's dbid", exc_info=True) bot.send_message(event.id, "error_critical") return force_key_name = f"ts3bot-{cldbid}" # Fetch token info token_info = fetch_api("tokeninfo", api_key=key) # Override registration, same as !register if token_info.get("name", "") == force_key_name: ts3bot.transfer_registration(bot, account, event) LOG.info( "%s (%s) transferred permissions of %s onto themselves.", event.name, event.uid, account_info.get("name"), ) return LOG.warning( "%s (%s) tried to use an already registered API key/account. (%s)", event.name, event.uid, account_info.get("name"), ) bot.send_message(event.id, "token_in_use", api_name=force_key_name) else: # Account is linked to current guid LOG.info( "User %s (%s) tried to register a second time for whatever reason using %s", event.name, event.uid, account_info.get("name", "Unknown account"), ) # Save new API key if account.api_key != key: account.api_key = key account.is_valid = True bot.session.commit() bot.send_message(event.id, "registration_exists") return # Same API key supplied, last check was over 12 minutes ago if (ts3bot.timedelta_hours(datetime.datetime.today() - account.last_check) >= 0.2): # Update saved account info if same API key was posted again with a reasonable time frame account.update(bot.session) try: # Get user's DB id cldbid = bot.exec_("clientgetdbidfromuid", cluid=event.uid)[0]["cldbid"] # Sync groups ts3bot.sync_groups(bot, cldbid, account) bot.send_message(event.id, "registration_details_updated") except ts3.TS3Error: # User might not exist in the db LOG.error("Failed to sync user", exc_info=True) else: # Too early bot.send_message(event.id, "registration_too_early") else: # Otherwise account is not yet linked and can be used # Save API key account.api_key = key account.is_valid = True bot.session.commit() # Get user's DB id cldbid = bot.exec_("clientgetdbidfromuid", cluid=event.uid)[0]["cldbid"] # Unlink previous account from identity current_account = models.Account.get_by_identity( bot.session, event.uid) if current_account: LOG.info("Delinking %s from cldbid:%s", current_account, cldbid) current_account.invalidate(bot.session) # Register link between models bot.session.add( models.LinkAccountIdentity(account=account, identity=identity)) bot.session.commit() # Add all known guilds to user if enabled if Config.getboolean( "guild", "assign_on_register") and Config.getboolean( "guild", "allow_multiple_guilds"): cast(AppenderQuery, account.guilds).filter( models.LinkAccountGuild.id.in_( bot.session.query(models.LinkAccountGuild.id).join( models.Guild).filter( models.Guild.group_id.isnot( None)).subquery())).update( {"is_active": True}, synchronize_session="fetch") bot.session.commit() # Sync groups sync_groups(bot, cldbid, account) LOG.info( "Assigned alliance permissions to %s (%s) using %s", event.name, event.uid, account_info.get("name", "Unknown account"), ) # Was registered with other account previously if current_account: bot.send_message(event.id, "registration_update", account=account.name) else: bot.send_message(event.id, "welcome_registered") # Tell user about !guild if it's enabled if Config.getboolean("commands", "guild"): if Config.getboolean( "guild", "assign_on_register") and Config.getboolean( "guild", "allow_multiple_guilds"): bot.send_message(event.id, "welcome_registered_3") else: bot.send_message(event.id, "welcome_registered_2") else: bot.send_message( event.id, "invalid_world", world=enums.World(account_info.get("world")).proper_name, ) except InvalidKeyException: LOG.info("This seems to be an invalid API key.") bot.send_message(event.id, "invalid_token_retry") except (RateLimitException, RequestException, ApiErrBadData): bot.send_message(event.id, "error_api")
def verify_user( self, client_unique_id: str, client_database_id: str, client_id: str ) -> bool: """ Verify a user if they are in a known group, otherwise nothing is done. Groups are revoked/updated if necessary :param client_unique_id: The client's UUID :param client_database_id: The database ID :param client_id: The client's temporary ID during the session :return: True if the user has/had a known group and False if the user is new """ def revoked(response: str): if account: account.invalidate(self.session) changes = ts3bot.sync_groups( self, client_database_id, account, remove_all=True ) reason = "unknown reason" if response == "groups_revoked_missing_key": reason = "missing API key" elif response == "groups_revoked_invalid_key": reason = "invalid API key" logging.info( "Revoked user's (cldbid:%s) groups (%s) due to %s.", client_database_id, changes["removed"], reason, ) self.send_message(client_id, response) # Get all current groups server_groups = self.exec_("servergroupsbyclientid", cldbid=client_database_id) known_groups: typing.List[int] = ( [ _.group_id for _ in self.session.query(ts3bot.database.models.WorldGroup).options( load_only(ts3bot.database.models.WorldGroup.group_id) ) ] + [ _.group_id for _ in self.session.query(ts3bot.database.models.Guild) .filter(ts3bot.database.models.Guild.group_id.isnot(None)) .options(load_only(ts3bot.database.models.Guild.group_id)) ] + [ int(Config.get("teamspeak", "generic_world_id")), int(Config.get("teamspeak", "generic_guild_id")), ] ) # Check if user has any known groups has_group = False has_skip_group = False for server_group in server_groups: if int(server_group.get("sgid", -1)) in known_groups: has_group = True if server_group.get("name") in Config.whitelist_groups: has_skip_group = True # Skip users without any known groups if not has_group: return False # Skip users in whitelisted groups that should be ignored like # guests, music bots, etc if has_skip_group: return True # Grab user's account info account = models.Account.get_by_identity(self.session, client_unique_id) # User does not exist in DB if not account: revoked("groups_revoked_missing_key") return True # User was checked, don't check again if ts3bot.timedelta_hours( datetime.datetime.today() - account.last_check ) < Config.getfloat("verify", "on_join_hours"): return True logging.debug("Checking %s/%s", account, client_unique_id) try: account.update(self.session) # Sync groups ts3bot.sync_groups(self, client_database_id, account) except ts3bot.InvalidKeyException: revoked("groups_revoked_invalid_key") except ( requests.RequestException, ts3bot.RateLimitException, ts3bot.ApiErrBadData, ): logging.exception("Error during API call") return True