Exemple #1
0
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
Exemple #2
0
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}'
        )
Exemple #3
0
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)
Exemple #4
0
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)
Exemple #5
0
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")
Exemple #6
0
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}')
Exemple #7
0
    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()
Exemple #8
0
    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)
Exemple #9
0
    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)
Exemple #10
0
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