async def check_clan_member(ctx): try: await ctx.bot.database.get( Member.select(Member, ClanMember).join(ClanMember).join(Clan).join(Guild).where( Guild.guild_id == ctx.message.guild.id, Member.discord_id == ctx.author.id ) ) except DoesNotExist: raise InvalidMemberError return True
async def store_member_history(ctx, member_db_id, guild_id, guild_name, full_sync=False, count=250, mode=0): database = ctx['database'] redis_jobs = ctx['redis_jobs'] query = Member.select(Member, ClanMember).join(ClanMember).where(Member.id == member_db_id) member_db = await database.get(query) activities = await get_member_activity(ctx, member_db, count, full_sync, mode) for activity in activities: activity_id = activity.activity_details.instance_id await redis_jobs.enqueue_job( 'process_activity', activity, guild_id, guild_name, _job_id=f'process_activity-{activity_id}' )
async def get_sherpa_time_played(database, member_db): clan_sherpas = Member.select(Member.id).join(ClanMember).where((ClanMember.is_sherpa) & (Member.id != member_db.id)) full_list = list(constants.SUPPORTED_GAME_MODES.values()) mode_list = list(set([mode for sublist in full_list for mode in sublist])) all_games = Game.select().join(GameMember).where( (GameMember.member_id == member_db.id) & (Game.mode_id << mode_list) ) sherpa_games = Game.select(Game.id.distinct()).join(GameMember).where( (Game.id << all_games) & (GameMember.member_id << clan_sherpas) ) query = GameMember.select( GameMember.member_id, GameMember.game_id, fn.MAX(GameMember.time_played).alias('sherpa_time') ).where( (GameMember.game_id << sherpa_games) & (GameMember.member_id << clan_sherpas) ).group_by( GameMember.game_id, GameMember.member_id ).order_by( GameMember.game_id, fn.MAX(GameMember.time_played).desc() ).distinct(GameMember.game_id) total_time = 0 unique_sherpas = set() unique_games = set() try: results = await database.execute(query) except DoesNotExist: return (total_time, unique_sherpas) for result in results: unique_sherpas.add(result.member_id) unique_games.add(result.game_id) total_time += result.sherpa_time if result.sherpa_time else 0 all_game_sherpas_query = GameMember.select(Member.id.distinct()).join(Member).switch().join(Game).where( (Game.id << sherpa_games) & (GameMember.member_id << clan_sherpas) ) try: all_game_sherpas_db = await database.execute(all_game_sherpas_query) except DoesNotExist: return (total_time, unique_sherpas) for sherpa in all_game_sherpas_db: unique_sherpas.add(sherpa) return (total_time, unique_sherpas)
async def store_sherpas(bot, guild): discord_guild = await bot.fetch_guild(guild.guild_id) sherpas_discord = await find_sherpas(bot, guild) sherpas_discord_ids = [sherpa.id for sherpa in sherpas_discord] query = Member.select(Member.discord_id).join(ClanMember).join(Clan).join( Guild).where((ClanMember.is_sherpa) & (Guild.id == guild.id)) sherpas_db = await bot.database.execute(query) sherpas_db_ids = [sherpa_db.discord_id for sherpa_db in sherpas_db] discord_set = set(sherpas_discord_ids) db_set = set(sherpas_db_ids) sherpas_added = list(discord_set - db_set) sherpas_removed = list(db_set - discord_set) added = removed = [] base_member_query = ClanMember.select(ClanMember.id).join(Member) if sherpas_added: members = base_member_query.where(Member.discord_id << sherpas_added) query = ClanMember.update( is_sherpa=True).where(ClanMember.id << members) await bot.database.execute(query) added = [ sherpa async for sherpa in convert_sherpas(bot, sherpas_added) ] message_added = [f"{str(sherpa)} {sherpa.id}" for sherpa in added] log.info( f"Sherpas added in {str(discord_guild)} ({guild.guild_id}): {message_added}" ) if sherpas_removed: members = base_member_query.where(Member.discord_id << sherpas_removed) query = ClanMember.update( is_sherpa=False).where(ClanMember.id << members) await bot.database.execute(query) removed = [ sherpa async for sherpa in convert_sherpas(bot, sherpas_removed) ] message_removed = [f"{str(sherpa)} {sherpa.id}" for sherpa in removed] log.info( f"Sherpas removed in {str(discord_guild)} ({guild.guild_id}): {message_removed}" ) return (added, removed)
async def process_activity(ctx, activity, guild_id, guild_name): database = ctx['database'] game = GameApi(activity) member_dbs = await get_cached_members(ctx, guild_id, guild_name) clan_dbs = await database.get_clans_by_guild(guild_id) clan_ids = [clan.id for clan in clan_dbs] try: game_db = await database.get(Game, instance_id=game.instance_id) except DoesNotExist: log.debug(f"Skipping missing player check because game {game.instance_id} does not exist") else: pgcr = await get_pgcr(ctx, game.instance_id) clan_game = ClanGame(pgcr, member_dbs) api_players_db = [ await database.get_clan_member_by_platform(player.membership_id, player.membership_type, clan_ids) for player in clan_game.clan_players ] query = Member.select(Member, ClanMember).join( GameMember).switch(Member).join(ClanMember).switch(GameMember).join(Game).where( (Game.instance_id == game.instance_id) ) db_players_db = await database.execute(query) missing_player_dbs = set(api_players_db).symmetric_difference(set([db_player for db_player in db_players_db])) if len(missing_player_dbs) > 0: for missing_player_db in missing_player_dbs: for game_player in clan_game.clan_players: if member_hash(game_player) == member_hash_db(missing_player_db, game_player.membership_type) \ and game.date > missing_player_db.clanmember.join_date: log.debug(f'Found missing player in {game.instance_id} {game_player}') await database.create_game_member( game_player, game_db, member_dbs[0].clan_id, missing_player_db) else: log.debug(f"Continuing because game {game.instance_id} exists") return supported_modes = set(sum(constants.SUPPORTED_GAME_MODES.values(), [])) if game.mode_id not in supported_modes: log.debug(f'Continuing because game {game.instance_id} mode {game.mode_id} not supported') return pgcr = await get_pgcr(ctx, game.instance_id) if not pgcr: log.error(f"Continuing because error with pgcr for game {game.instance_id}") return clan_game = ClanGame(pgcr, member_dbs) game_mode_details = constants.MODE_MAP[game.mode_id] if len(clan_game.clan_players) < game_mode_details['threshold']: log.debug(f"Continuing because not enough clan players in game {game.instance_id}") return game_db = await database.create_game(clan_game) if not game_db: log.error(f"Continuing because error with storing game {game.instance_id}") return await database.create_clan_game(game_db, clan_game, clan_game.clan_id) game_title = game_mode_details['title'].title() log.info(f"{game_title} game id {game.instance_id} on {game.date} created")
async def ack_clan_application(ctx, payload): is_approved = payload.emoji.name == constants.EMOJI_CHECKMARK approver_id = payload.user_id message_id = payload.message_id approver_user = payload.member guild = approver_user.guild try: # pylama:ignore=E712 query = ClanMemberApplication.select().join( MemberDb).where((ClanMemberApplication.message_id == message_id) & (ClanMemberApplication.approved == False)) application_db = await ctx.ext_conns['database'].get(query) except DoesNotExist: return try: approver_db = await ctx.ext_conns['database'].get( MemberDb.select( MemberDb, ClanMember, Clan).join(ClanMember).join(Clan).where( (ClanMember.member_type >= constants.CLAN_MEMBER_ADMIN) & (MemberDb.discord_id == approver_id))) except DoesNotExist: raise InvalidAdminError application_db.approved = is_approved application_db.approved_by_id = approver_db.id await ctx.ext_conns['database'].update(application_db) admin_channel = ctx.get_channel( ctx.guild_map[payload.guild_id].admin_channel) applicant_user = guild.get_member(application_db.member.discord_id) if is_approved: ack_message = 'Approved' else: ack_message = 'Denied' admin_message = await admin_channel.send( f"Application for {applicant_user.display_name} was {ack_message} by {approver_user.display_name}." ) await applicant_user.send( f"Your application to join {approver_db.clanmember.clan.name} has been {ack_message}." ) if is_approved: admin_context = await ctx.get_context(admin_message) manager = MessageManager(admin_context) platform_id, membership_id, username = get_primary_membership( application_db.member) res = await execute_pydest_auth( ctx.ext_conns, ctx.ext_conns['destiny'].api.group_approve_pending_member, approver_db, manager, group_id=approver_db.clanmember.clan.clan_id, membership_type=platform_id, membership_id=membership_id, message=f"Join my clan {approver_db.clanmember.clan.name}!", access_token=approver_db.bungie_access_token) if res.error_status == 'ClanTargetDisallowsInvites': message = f"User **{applicant_user.display_name}** ({username}) has disabled clan invites" elif res.error_status != 'Success': message = f"Could not invite **{applicant_user.display_name}** ({username})" log.info( f"Could not invite '{applicant_user.display_name}' ({username}): {res}" ) else: message = ( f"Invited **{applicant_user.display_name}** ({username}) " f"to clan **{approver_db.clanmember.clan.name}**") await manager.send_message(message, mention=False, clean=False) await ctx.ext_conns['redis_cache'].delete( f'{ctx.guild.id}-clan-application-{application_db.member_id}')
async def list(self, ctx): """List games on the100 in the linked group(s)""" manager = MessageManager(ctx) clan_dbs = await self.bot.database.get_clans_by_guild(ctx.guild.id) game_tasks = [ self.bot.the100.get_group_gaming_sessions(clan_db.the100_group_id) for clan_db in clan_dbs if clan_db.the100_group_id ] results = await asyncio.gather(*game_tasks) games = [] for result in results: if isinstance(result, dict) and result.get('error'): log.error(result) continue games.extend(result) if not games: return await manager.send_and_clean("No the100 game sessions found" ) embeds = [] for game in games: try: spots_reserved = game['party_size'] - 1 except TypeError: continue start_time = datetime.fromisoformat( game['start_time']).astimezone(tz=pytz.utc) embed = discord.Embed(color=constants.BLUE, ) embed.set_thumbnail(url=(constants.THE100_LOGO_URL)) embed.add_field( name="Activity", value= f"[{game['category']}](https://www.the100.io/gaming_sessions/{game['id']})" ) embed.add_field(name="Start Time", value=start_time.strftime( constants.THE100_DATE_DISPLAY)) embed.add_field(name="Description", value=game['name'], inline=False) primary = [] reserve = [] for session in game['confirmed_sessions']: gamertag = session['user']['gamertag'] try: query = Member.select( Member, ClanMember, Clan, Guild).join(ClanMember).join(Clan).join(Guild).where( Member.the100_id == session['user_id']) member_db = await self.bot.database.get(query) except DoesNotExist: pass else: if member_db.clanmember.clan.guild.guild_id == ctx.guild.id: gamertag = f"{gamertag} (m)" if session['reserve_spot']: reserve.append(gamertag) else: primary.append(gamertag) embed.add_field(name=( f"Players Joined: {game['primary_users_count']}/{game['team_size']} " f"(Spots Reserved: {spots_reserved})"), value=', '.join(primary), inline=False) embed.add_field(name="Reserves", value=', '.join(reserve) or "None", inline=False) embed.set_footer(text=(f"Creator: {game['creator_gamertag']} | " f"Group: {game['group_name']} | " f"(m) denotes clan member")) embeds.append(embed) paginator = EmbedPages(ctx, embeds) await paginator.paginate()
async def info(self, ctx, *args): # noqa TODO """Show member information""" manager = MessageManager(ctx) member_name = ' '.join(args) requestor_query = self.bot.database.get( Member.select(Member, ClanMember, Clan).join(ClanMember).join(Clan).join(Guild).where( Guild.guild_id == ctx.guild.id, Member.discord_id == ctx.author.id)) try: requestor_db = await asyncio.create_task(requestor_query) except DoesNotExist: requestor_db = None if not member_name: member_db = requestor_db member_name = ctx.author.nick member_discord = ctx.message.author discord_username = str(ctx.message.author) else: try: member_discord = await commands.MemberConverter().convert( ctx, str(member_name)) except BadArgument: discord_username = None else: discord_username = str(member_discord) member_query = self.bot.database.get_member_by_naive_username( member_name) try: member_db = await asyncio.create_task(member_query) except DoesNotExist: return await manager.send_and_clean( f"Could not find username `{member_name}` in any connected clans" ) the100_link = None if member_db.the100_username: the100_url = f"https://www.the100.io/users/{quote(member_db.the100_username)}" the100_link = f"[{member_db.the100_username}]({the100_url})" bungie_link = None if member_db.bungie_id: try: bungie_info = await execute_pydest( self.bot.destiny.api.get_membership_data_by_id( member_db.bungie_id), self.bot.redis) except pydest.PydestException: bungie_link = member_db.bungie_username else: bungie_member_data = BungieUser(bungie_info["Response"]) bungie_member_id = bungie_member_data.memberships.bungie.id bungie_member_type = constants.PLATFORM_BUNGIE bungie_member_name = bungie_member_data.memberships.bungie.username bungie_url = f"https://www.bungie.net/en/Profile/{bungie_member_type}/{bungie_member_id}" bungie_link = f"[{bungie_member_name}]({bungie_url})" timezone = None if member_db.timezone: tz = datetime.now(pytz.timezone(member_db.timezone)) timezone = f"{tz.strftime('UTC%z')} ({tz.tzname()})" if member_db.discord_id: member_discord = await commands.MemberConverter().convert( ctx, str(member_db.discord_id)) discord_username = str(member_discord) requestor_is_admin = False if requestor_db and requestor_db.clanmember.member_type >= constants.CLAN_MEMBER_ADMIN: requestor_is_admin = True member_is_admin = False if member_db.clanmember.member_type >= constants.CLAN_MEMBER_ADMIN: member_is_admin = True embed = discord.Embed(colour=constants.BLUE, title=f"Member Info for {member_name}") embed.add_field( name="Clan", value= f"{member_db.clanmember.clan.name} [{member_db.clanmember.clan.callsign}]" ) embed.add_field( name="Join Date", value=member_db.clanmember.join_date.strftime('%Y-%m-%d %H:%M:%S')) if requestor_is_admin: embed.add_field(name="Last Active Date", value=member_db.clanmember.last_active.strftime( '%Y-%m-%d %H:%M:%S')) embed.add_field(name="Time Zone", value=timezone) embed.add_field(name="Xbox Gamertag", value=member_db.xbox_username) embed.add_field(name="PSN Username", value=member_db.psn_username) embed.add_field(name="Steam Username", value=member_db.steam_username) embed.add_field(name="Stadia Username", value=member_db.stadia_username) embed.add_field(name="Discord Username", value=discord_username) embed.add_field(name="Bungie Username", value=bungie_link) embed.add_field(name="The100 Username", value=the100_link) embed.add_field( name="Is Sherpa", value=constants.EMOJI_CHECKMARK if member_db.clanmember.is_sherpa else constants.EMOJI_CROSSMARK) embed.add_field(name="Is Admin", value=constants.EMOJI_CHECKMARK if member_is_admin else constants.EMOJI_CROSSMARK) await manager.send_embed(embed)
async def sherpatime(self, ctx, *args): """ Show total time spent in activities with at least one sherpa member. """ manager = MessageManager(ctx) member_name = " ".join(args) if not member_name: discord_id = ctx.author.id member_name = ctx.author.display_name try: member_db = await self.bot.database.get_member_by_discord_id( discord_id) except DoesNotExist: return await manager.send_and_clean( f"User `{ctx.author.display_name}` has not registered or is not a clan member" ) log.info( f"Getting sherpa time played for \"{ctx.author.display_name}\"" ) else: try: member_db = await self.bot.database.get_member_by_naive_username( member_name) except DoesNotExist: try: member_discord = await commands.MemberConverter().convert( ctx, member_name) member_db = await self.bot.database.get_member_by_discord_id( member_discord.id) except (BadArgument, DoesNotExist): return await manager.send_and_clean( f"Invalid member name `{member_name}`") else: member_name = member_discord.display_name log.info( f"Getting sherpa time played by username \"{member_name}\" for \"{ctx.author}\"" ) time_played, sherpa_ids = await get_sherpa_time_played( self.bot.database, member_db) sherpa_list = [] if time_played > 0: sherpas = await self.bot.database.execute( Member.select().where(Member.id << sherpa_ids)) for sherpa in sherpas: if sherpa.discord_id: sherpa_discord = await commands.MemberConverter().convert( ctx, str(sherpa.discord_id)) sherpa_list.append( f"{sherpa_discord.name}#{sherpa_discord.discriminator}" ) embed = discord.Embed( colour=constants.BLUE, title=f"Total time played with a Sherpa by {member_name}", description=f"{time_played / 3600:.2f} hours") if sherpa_list: embed.add_field(name="Sherpas Played With", value=', '.join(sherpa_list)) await manager.send_embed(embed)
async def member_sync(bot, guild_id): # noqa clan_dbs = await bot.database.get_clans_by_guild(guild_id) member_changes = {} for clan_db in clan_dbs: member_changes[clan_db.clan_id] = {'added': [], 'removed': [], 'changed': []} bungie_members = {} db_members = {} bungie_tasks = [] db_tasks = [] # Generate a dict of all members from both Bungie and the database for clan_db in clan_dbs: clan_id = clan_db.clan_id bungie_tasks.append(get_bungie_members(bot, clan_id)) db_tasks.append(get_database_members(bot.database, clan_id)) results = await asyncio.gather(*bungie_tasks, *db_tasks) # All Bungie results would be in the first half of the results for result in results[:len(results)//2]: bungie_members.update(result) # All database results are in the second half for result in results[len(results)//2:]: db_members.update(result) bungie_member_set = set( [member for member in bungie_members.keys()] ) db_member_set = set( [member for member in db_members.keys()] ) # Figure out if there are any members to add members_added = bungie_member_set - db_member_set for member_hash in members_added: member_info = bungie_members[member_hash] clan_id, platform_id, member_id = map(int, member_hash.split('-')) try: member_db = await bot.database.get_member_by_platform(member_id, platform_id) except DoesNotExist: member_db = await bot.database.create(MemberDb, **member_info.to_dict()) clan_db = await bot.database.get(Clan, clan_id=clan_id) member_details = dict( join_date=member_info.join_date, platform_id=member_info.platform_id, is_active=True, member_type=member_info.member_type, last_active=member_info.last_online_status_change ) await bot.database.create( ClanMember, clan=clan_db, member=member_db, **member_details) clan_member_db = await bot.database.execute( MemberDb.select(MemberDb, ClanMember).join(ClanMember).join(Clan).where( MemberDb.id == member_db.id ) ) # Kick off activity scans for each of the added members # Indexing `clan_member_db` is necessary becuase the query returns a multi-row set, and # normal means of limiting that output (ie. `.get()`) does not work for some reason. member_dbs = await bot.database.get_clan_members([clan_id]) asyncio.create_task(store_member_history(member_dbs, bot, clan_member_db[0], count=250)) member_changes[clan_db.clan_id]['added'].append(member_hash) # Figure out if there are any members to remove members_removed = db_member_set - bungie_member_set for member_hash in members_removed: clan_id, platform_id, member_id = map(int, member_hash.split('-')) try: member_db = await bot.database.get_member_by_platform(member_id, platform_id) except DoesNotExist: log.info(member_id) continue clanmember_db = await bot.database.get(ClanMember, member_id=member_db.id) await bot.database.delete(clanmember_db) member_changes[clan_db.clan_id]['removed'].append(member_hash) for clan, changes in member_changes.items(): if len(changes['added']): changes['added'] = await sort_members(bot.database, changes['added']) log.info(f"Added members {changes['added']}") if len(changes['removed']): changes['removed'] = await sort_members(bot.database, changes['removed']) log.info(f"Removed members {changes['removed']}") return member_changes