Ejemplo n.º 1
0
def on_markdown_context(context, page, config):
    context['page'] = page
    context['config'] = config

    context['club_elapsed_months'] = int(
        round((context['now'] - CLUB_LAUNCH_AT).days / 30))
    context['members'] = ClubUser.avatars_listing()
    context['members_total_count'] = ClubUser.members_count()

    if 'topic_name' in page.meta:
        topic_name = page.meta['topic_name']
        context['topic'] = Topic.get_by_id(topic_name)
Ejemplo n.º 2
0
def define_env(env):
    with db:
        env.variables.update(dict(
            members=ClubUser.avatars_listing(),
            members_total_count=ClubUser.members_count(),
            jobs_count=Job.aggregate_metrics()['jobs_count'],
        ))

    @env.filter
    def sample(items, n=2, sample_fn=None):  # TODO deduplicate template_filters
        items = list(items)
        if len(items) <= n:
            return items
        return (sample_fn or random.sample)(items, n)
Ejemplo n.º 3
0
async def apply_changes(client, changes):
    all_discord_roles = {
        discord_role.id: discord_role
        for discord_role in await client.juniorguru_guild.fetch_roles()
    }

    changes_by_members = {}
    for member_id, op, role_id in changes:
        changes_by_members.setdefault(member_id, dict(add=[], remove=[]))
        changes_by_members[member_id][op].append(role_id)

    for member_id, changes in changes_by_members.items():
        discord_member = await client.juniorguru_guild.fetch_member(member_id)
        if changes['add']:
            discord_roles = [
                all_discord_roles[role_id] for role_id in changes['add']
            ]
            log.info(
                f'{discord_member.display_name}: adding {repr_roles(discord_roles)}'
            )
            await discord_member.add_roles(*discord_roles)
        if changes['remove']:
            discord_roles = [
                all_discord_roles[role_id] for role_id in changes['remove']
            ]
            log.info(
                f'{discord_member.display_name}: removing {repr_roles(discord_roles)}'
            )
            await discord_member.remove_roles(*discord_roles)
        with db:
            member = ClubUser.get_by_id(member_id)
            member.roles = get_roles(discord_member)
            member.save()
Ejemplo n.º 4
0
def test_avatars_listing(db_connection):
    user1 = create_user(1, avatar_path='avatars/1.png')
    user2 = create_user(2)  # noqa
    user3 = create_user(3, avatar_path='avatars/2.png')
    user4 = create_user(4, avatar_path='avatars/3.png')

    assert list(ClubUser.avatars_listing()) == [user1, user3, user4]
Ejemplo n.º 5
0
def test_author_members_listing(db_connection):
    author1 = create_user(1, is_member=True, is_bot=True)  # noqa
    author2 = create_user(2, is_member=True, is_bot=False)
    author3 = create_user(3, is_member=False, is_bot=True)  # noqa
    author4 = create_user(4, is_member=False, is_bot=False)  # noqa

    assert list(ClubUser.members_listing()) == [author2]
Ejemplo n.º 6
0
def create_user(id_, **kwargs):
    return ClubUser.create(id=id_,
                           is_member=kwargs.get('is_member', True),
                           is_bot=kwargs.get('is_bot', False),
                           avatar_path=kwargs.get('avatar_path'),
                           display_name=kwargs.get('display_name',
                                                   'Kuře Žluté'),
                           mention=kwargs.get('mention', f'<@{id_}>'),
                           joined_at=kwargs.get(
                               'joined_at',
                               datetime.now() - timedelta(days=3)))
Ejemplo n.º 7
0
async def main(client):
    AVATARS_PATH.mkdir(exist_ok=True, parents=True)
    for path in AVATARS_PATH.glob('*.png'):
        path.unlink()

    with db:
        for member in ClubUser.members_listing():
            log.info(
                f"Downloading avatar for '{member.display_name}' #{member.id}")
            discord_member = await client.juniorguru_guild.fetch_member(
                member.id)
            member.avatar_path = await download_avatar(discord_member)
            log.info(f"Result: '{member.avatar_path}'")
            member.save()
Ejemplo n.º 8
0
def main():
    with db:
        members = ClubUser.members_listing()
        changes = []
        top_members_limit = ClubUser.top_members_limit()
        log.info(
            f'members_count={len(members)}, top_members_limit={top_members_limit}'
        )

        # ROLE_MOST_DISCUSSING
        messages_count_stats = calc_stats(members,
                                          lambda m: m.messages_count(),
                                          top_members_limit)
        log.info(f"messages_count {repr_stats(members, messages_count_stats)}")

        recent_messages_count_stats = calc_stats(
            members, lambda m: m.recent_messages_count(), top_members_limit)
        log.info(
            f"recent_messages_count {repr_stats(members, recent_messages_count_stats)}"
        )

        most_discussing_members_ids = set(messages_count_stats.keys()) | set(
            recent_messages_count_stats.keys())
        log.info(
            f"most_discussing_members: {repr_ids(members, most_discussing_members_ids)}"
        )

        for member in members:
            changes.extend(
                evaluate_changes(member.id, member.roles,
                                 most_discussing_members_ids,
                                 ROLE_MOST_DISCUSSING))

        # ROLE_MOST_HELPFUL
        upvotes_count_stats = calc_stats(members, lambda m: m.upvotes_count(),
                                         top_members_limit)
        log.info(f"upvotes_count {repr_stats(members, upvotes_count_stats)}")

        recent_upvotes_count_stats = calc_stats(
            members, lambda m: m.recent_upvotes_count(), top_members_limit)
        log.info(
            f"recent_upvotes_count {repr_stats(members, recent_upvotes_count_stats)}"
        )

        most_helpful_members_ids = set(upvotes_count_stats.keys()) | set(
            recent_upvotes_count_stats.keys())
        log.info(
            f"most_helpful_members: {repr_ids(members, most_helpful_members_ids)}"
        )

        for member in members:
            changes.extend(
                evaluate_changes(member.id, member.roles,
                                 most_helpful_members_ids, ROLE_MOST_HELPFUL))

        # ROLE_HAS_INTRO_AND_AVATAR
        intro_avatar_members_ids = [
            member.id for member in members
            if member.avatar_path and member.has_intro()
        ]
        log.info(
            f"intro_avatar_members: {repr_ids(members, intro_avatar_members_ids)}"
        )
        for member in members:
            changes.extend(
                evaluate_changes(member.id, member.roles,
                                 intro_avatar_members_ids,
                                 ROLE_HAS_INTRO_AND_AVATAR))

        # ROLE_IS_NEW
        new_members_ids = [member.id for member in members if member.is_new()]
        log.info(f"new_members_ids: {repr_ids(members, new_members_ids)}")
        for member in members:
            changes.extend(
                evaluate_changes(member.id, member.roles, new_members_ids,
                                 ROLE_IS_NEW))

        # ROLE_IS_SPEAKER
        speaking_members_ids = [
            member.id for member in Event.list_speaking_members()
        ]
        log.info(
            f"speaking_members_ids: {repr_ids(members, speaking_members_ids)}")
        for member in members:
            changes.extend(
                evaluate_changes(member.id, member.roles, speaking_members_ids,
                                 ROLE_IS_SPEAKER))

    if DISCORD_MUTATIONS_ENABLED:
        apply_changes(changes)
    else:
        log.warning(
            "Skipping Discord mutations, DISCORD_MUTATIONS_ENABLED not set")
Ejemplo n.º 9
0
 def list_speaking_members(cls):
     return ClubUser.select() \
         .where(ClubUser.is_member == True) \
         .join(EventSpeaking)
Ejemplo n.º 10
0
async def main(client):
    with db:
        top_members_limit = ClubUser.top_members_limit()
        pin_reactions_by_members = [
            pin_reaction for pin_reaction in ClubPinReaction.listing()
            if pin_reaction.user.is_member
        ]

    log.info(
        f'Found {len(pin_reactions_by_members)} pin reactions by people who are currently members'
    )
    for pin_reaction in pin_reactions_by_members:
        member = await client.juniorguru_guild.fetch_member(
            pin_reaction.user.id)

        if member.dm_channel:
            channel = member.dm_channel
        else:
            channel = await member.create_dm()

        log.info(
            f"Checking DM if already pinned for {member.display_name} #{member.id}"
        )
        if await is_pinned(pin_reaction.message.url, channel):
            log.info(f"Already pinned for {member.display_name} #{member.id}")
            continue

        log.info(
            f"Not pinned for {member.display_name} #{member.id}, sending a message to DM"
        )
        if DISCORD_MUTATIONS_ENABLED:
            content = (
                '📌 Vidím špendlík! Ukládám ti příspěvek sem, do soukromé zprávy. '
                f'Když bude mít ~{top_members_limit} špendlíků, připnu jej v původním kanálu pro všechny.'
            )
            embed_description = [
                f"{pin_reaction.message.author.mention} v {pin_reaction.message.channel_mention}:",
                f"> {textwrap.shorten(pin_reaction.message.content, 500, placeholder='…')}",
                f"[Hop na příspěvek]({pin_reaction.message.url})",
                "",
            ]
            await channel.send(
                content=content,
                embed=Embed(description="\n".join(embed_description)))
        else:
            log.warning(
                "Skipping Discord mutations, DISCORD_MUTATIONS_ENABLED not set"
            )

    log.info(
        f"Going through messages pinned by reactions, minimum is {top_members_limit} pin reactions"
    )
    with db:
        messages = ClubMessage.pinned_by_reactions_listing(top_members_limit)
    log.info(f'Found {len(messages)} messages')
    for message in messages:
        log.info(
            f"Message {message.url} {'PINNED' if message.is_pinned else 'NOT PINNED'}"
        )
        if not message.is_pinned:
            log.info(f"Pinning {message.url}")
            discord_message = await client.fetch_channel(
                message.channel_id).fetch_message(message.id)
            await discord_message.pin(
                reason=
                f"The message has {message.pin_reactions_count} pin reactions, minimum is {top_members_limit}"
            )
Ejemplo n.º 11
0
async def main(client):
    with db:
        db.drop_tables([ClubMessage, ClubUser, ClubPinReaction])
        db.create_tables([ClubMessage, ClubUser, ClubPinReaction])

    authors = {}
    relevant_channels = (channel for channel in client.juniorguru_guild.text_channels
                         if channel.permissions_for(client.juniorguru_guild.me).read_messages)
    for channel in relevant_channels:
        log.info(f'Channel #{channel.name}')
        async for message in channel.history(limit=None, after=None):
            if message.author.id not in authors:
                # The message.author can be an instance of Member, but it can also be an instance of User,
                # if the author isn't a member of the Discord guild/server anymore. User instances don't
                # have certain properties, hence the getattr() calls below.
                with db:
                    log.info(f"User '{message.author.display_name}' #{message.author.id}")
                    author = ClubUser.create(id=message.author.id,
                                             is_bot=message.author.bot,
                                             is_member=bool(getattr(message.author, 'joined_at', False)),
                                             display_name=message.author.display_name,
                                             mention=message.author.mention,
                                             joined_at=getattr(message.author, 'joined_at', None),
                                             roles=get_roles(message.author))
                authors[message.author.id] = author
            with db:
                ClubMessage.create(id=message.id,
                                   url=message.jump_url,
                                   content=message.content,
                                   upvotes_count=count_upvotes(message.reactions),
                                   pin_reactions_count=count_pins(message.reactions),
                                   created_at=message.created_at,
                                   edited_at=message.edited_at,
                                   author=authors[message.author.id],
                                   channel_id=channel.id,
                                   channel_name=channel.name,
                                   channel_mention=channel.mention,
                                   type=message.type.name)

                users = set()
                for reaction in message.reactions:
                    if emoji_name(reaction.emoji) in EMOJI_PINS:
                        for user in [user async for user in reaction.users()]:
                            users.add(user)
                for user in users:
                    log.info(f"Message {message.jump_url} is pinned by user '{user.display_name}' #{user.id}")
                    ClubPinReaction.create(user=user.id, message=message.id)

    # remaining members (did not author a single message)
    log.info('Looking for remaining members, if any')
    remaining_members = [member async for member in client.juniorguru_guild.fetch_members(limit=None)
                         if member.id not in authors]
    for member in remaining_members:
        with db:
            log.info(f"Member '{member.display_name}' #{member.id}")
            ClubUser.create(id=member.id,
                            is_bot=member.bot,
                            is_member=True,
                            display_name=member.display_name,
                            mention=member.mention,
                            joined_at=member.joined_at,
                            roles=get_roles(member))

    with db:
        messages_count = ClubMessage.count()
        members_count = ClubUser.members_count()
    log.info(f'Saved {messages_count} messages from {len(authors)} authors')
    log.info(f'Saved {members_count} members')
Ejemplo n.º 12
0
def test_author_top_members_limit_rounds_up(db_connection):
    create_user(1)
    create_user(2)
    create_user(3)

    ClubUser.top_members_limit() == 1
Ejemplo n.º 13
0
def test_author_top_members_limit_doesnt_count_bots(db_connection):
    for id_ in range(200):
        create_user(id_, is_bot=id_ < 100)

    ClubUser.top_members_limit() == 5
Ejemplo n.º 14
0
def test_author_top_members_limit_is_five_percent(db_connection):
    for id_ in range(100):
        create_user(id_)

    ClubUser.top_members_limit() == 5
Ejemplo n.º 15
0
def main():
    log.info('Getting data from Memberful')
    # https://memberful.com/help/integrate/advanced/memberful-api/
    # https://juniorguru.memberful.com/api/graphql/explorer?api_user_id=52463
    transport = RequestsHTTPTransport(
        url='https://juniorguru.memberful.com/api/graphql/',
        headers={'Authorization': f'Bearer {MEMBERFUL_API_KEY}'},
        verify=True,
        retries=3)
    memberful = Memberful(transport=transport,
                          fetch_schema_from_transport=True)
    query = gql("""
        query getSubscriptions($cursor: String!) {
            subscriptions(after: $cursor) {
                totalCount
                pageInfo {
                    endCursor
                    hasNextPage
                }
                edges {
                    node {
                        active
                        createdAt
                        expiresAt
                        pastDue
                        coupon {
                            code
                        }
                        orders {
                            createdAt
                            coupon {
                                code
                            }
                        }
                        member {
                            discordUserId
                            email
                            fullName
                            id
                            stripeCustomerId
                        }
                    }
                }
            }
        }
    """)

    records = []
    seen_discord_ids = set()

    cursor = ''
    while cursor is not None:
        log.info('Requesting Memberful GraphQL')
        params = dict(cursor=cursor)
        result = memberful.execute(query, params)

        for edge in result['subscriptions']['edges']:
            node = edge['node']
            discord_id = node['member']['discordUserId']
            user = None
            if discord_id:
                seen_discord_ids.add(discord_id)
                try:
                    user = ClubUser.get_by_id(int(discord_id))
                except ClubUser.DoesNotExist:
                    pass

            records.append({
                'Name':
                node['member']['fullName'],
                'Discord Name':
                user.display_name if user else None,
                'E-mail':
                node['member']['email'],
                'Memberful ID':
                node['member']['id'],
                'Stripe ID':
                node['member']['stripeCustomerId'],
                'Discord ID':
                discord_id,
                'Memberful Active?':
                node['active'],
                'Memberful Since':
                arrow.get(node['createdAt']).date().isoformat(),
                'Memberful End':
                arrow.get(node['expiresAt']).date().isoformat(),
                'Memberful Coupon':
                get_active_coupon(node),
                'Discord Member?':
                user.is_member if user else False,
                'Discord Since':
                user.first_seen_on().isoformat() if user else None,
                'Memberful Past Due?':
                node['pastDue'],
            })

        if result['subscriptions']['pageInfo']['hasNextPage']:
            cursor = result['subscriptions']['pageInfo']['endCursor']
        else:
            cursor = None

    log.info('Process remaining Discord users')
    for user in ClubUser.listing():
        discord_id = str(user.id)
        if not user.is_bot and discord_id not in seen_discord_ids:
            records.append({
                'Name': None,
                'Discord Name': user.display_name,
                'E-mail': None,
                'Memberful ID': None,
                'Stripe ID': None,
                'Discord ID': discord_id,
                'Memberful Active?': False,
                'Memberful Since': None,
                'Memberful End': None,
                'Memberful Coupon': None,
                'Discord Member?': user.is_member,
                'Discord Since': user.first_seen_on().isoformat(),
                'Memberful Past Due?': False,
            })

    log.info('Uploading subscriptions to Google Sheets')
    google_sheets.upload(google_sheets.get(DOC_KEY, 'subscriptions'), records)