Skip to content

Commit 707322f

Browse files
renzorenzon
renzo
authored andcommitted
Created command to collect discord user ids
Partial of #4791
1 parent 47f0dc6 commit 707322f

12 files changed

+191
-10
lines changed

pythonpro/celery.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55

66
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'pythonpro.settings')
77
app = Celery('pythonpro.celery')
8-
app.config_from_object('django.conf:settings', namespace='CELERY')
9-
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
8+
app.config_from_object(settings, namespace='CELERY')
9+
app.autodiscover_tasks()

pythonpro/discord/admin.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from django.contrib import admin
22

3-
from pythonpro.discord.models import DiscordUser
3+
from pythonpro.discord.models import DiscordUser, DiscordLead
44

55

66
@admin.register(DiscordUser)
@@ -18,3 +18,20 @@ def has_change_permission(self, request, obj=None):
1818

1919
def has_delete_permission(self, request, obj=None):
2020
return False
21+
22+
23+
@admin.register(DiscordLead)
24+
class DiscordLeadAdmin(admin.ModelAdmin):
25+
fields = ["discord_id", "status", "created_at", "updated_at"]
26+
list_display = fields
27+
list_filter = ['status']
28+
ordering = ['-updated_at']
29+
30+
def has_add_permission(self, request):
31+
return False
32+
33+
def has_change_permission(self, request, obj=None):
34+
return False
35+
36+
def has_delete_permission(self, request, obj=None):
37+
return False

pythonpro/discord/api_client.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ class DiscordBotClient:
3535
def __init__(self, bot_token: str):
3636
self._bot_token = bot_token
3737

38+
def get_member(self, discord_user_id: str):
39+
headers = {
40+
'Authorization': f'Bot {self._bot_token}'
41+
}
42+
r = requests.get(f'{_BASE_ENDPOINT_URI}/users/{discord_user_id}', headers=headers)
43+
r.raise_for_status()
44+
discord_user_dict = r.json()
45+
return discord_user_dict
46+
3847
def list_guild_members(self, guild_id, limit=100, after=0) -> dict:
3948
"""
4049
:param guild_id: the discord server id
@@ -138,10 +147,3 @@ def generate_api_client(self, autorization_code: str) -> Union[DiscordAppClient,
138147
if self._bot_token is None:
139148
return DiscordAppClient(access_token)
140149
return DiscordAppAndBotClient(access_token, self._bot_token)
141-
142-
143-
if __name__ == '__main__':
144-
import decouple
145-
c = DiscordBotClient(decouple.config('DISCORD_APP_BOT_TOKEN'))
146-
members = c.list_guild_members(971162582624903288)
147-
print(members)

pythonpro/discord/facade.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from django.conf import settings
2+
3+
from pythonpro.discord.api_client import DiscordBotClient
4+
from pythonpro.discord.tasks import clean_discord_user
5+
6+
discord_bot_client = DiscordBotClient(settings.DISCORD_APP_BOT_TOKEN)
7+
8+
9+
def clean_discord_users():
10+
discord_user_id = 0
11+
while True:
12+
discord_members = discord_bot_client.list_guild_members(settings.DISCORD_GUILD_ID, after=discord_user_id)
13+
if len(discord_members) == 0:
14+
break
15+
for member in discord_members:
16+
discord_user = member['user']
17+
discord_user_id = discord_user['id']
18+
is_bot = discord_user.get('bot', False)
19+
if is_bot:
20+
continue
21+
clean_discord_user.delay(discord_user_id)

pythonpro/discord/management/__init__.py

Whitespace-only changes.

pythonpro/discord/management/commands/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from django.core.management import BaseCommand
2+
3+
from pythonpro.discord import facade
4+
5+
6+
class Command(BaseCommand):
7+
help = 'Sincronizar usuários do Discord com Painel da DevPro'
8+
9+
def add_arguments(self, parser):
10+
pass
11+
12+
def handle(self, *args, **options):
13+
facade.clean_discord_users()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 4.2.10 on 2024-02-28 18:04
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('discord', '0002_discord_user_created_at_desc_index'),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name='DiscordLead',
15+
fields=[
16+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17+
('discord_id', models.CharField(max_length=64, unique=True)),
18+
('created_at', models.DateTimeField(auto_now_add=True)),
19+
('updated_at', models.DateTimeField(auto_now=True)),
20+
('status', models.CharField(choices=[('A', 'Ativa'), ('I', 'Inativa')], default='I', max_length=32)),
21+
],
22+
),
23+
]

pythonpro/discord/models.py

+15
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,18 @@ class Meta:
1515

1616
def __str__(self):
1717
return self.discord_email
18+
19+
20+
class DiscordLead(models.Model):
21+
class Status(models.TextChoices):
22+
ACTIVE = 'A', 'Ativa'
23+
INACTIVE = 'I', 'Inativa'
24+
25+
discord_id = models.CharField(max_length=64, unique=True)
26+
created_at = models.DateTimeField(auto_now_add=True)
27+
updated_at = models.DateTimeField(auto_now=True)
28+
status = models.CharField(
29+
max_length=32,
30+
choices=Status.choices,
31+
default=Status.INACTIVE
32+
)

pythonpro/discord/tasks.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from celery import shared_task
2+
3+
from pythonpro.discord.models import DiscordLead
4+
from pythonpro.memberkit.models import Subscription
5+
6+
7+
@shared_task(
8+
rate_limit=1,
9+
max_retries=5,
10+
retry_backoff=True,
11+
retry_backoff_max=700,
12+
retry_jitter=True
13+
)
14+
def clean_discord_user(discord_user_id):
15+
has_discord_access = Subscription.objects.filter(
16+
status=Subscription.Status.ACTIVE,
17+
subscription_types__has_discord_access=True,
18+
subscriber__discorduser__discord_id=discord_user_id
19+
).exists()
20+
21+
lead_status = DiscordLead.Status.ACTIVE if has_discord_access else DiscordLead.Status.INACTIVE
22+
DiscordLead.objects.update_or_create(
23+
defaults={'status': lead_status},
24+
discord_id=discord_user_id
25+
)
26+
27+
return print(f'Clean discord user: {discord_user_id}')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from pythonpro.discord.models import DiscordLead, DiscordUser
2+
from pythonpro.discord.tasks import clean_discord_user
3+
from model_bakery import baker
4+
5+
from pythonpro.memberkit.models import SubscriptionType, Subscription
6+
7+
8+
def test_active_user(db):
9+
discord_user_id = '1055109241507160165'
10+
django_user = baker.make(DiscordUser, discord_id=discord_user_id).user
11+
subscription_type = baker.make(SubscriptionType, has_discord_access=True)
12+
baker.make(
13+
Subscription,
14+
subscription_types=[subscription_type],
15+
subscriber=django_user,
16+
status=Subscription.Status.ACTIVE
17+
)
18+
clean_discord_user(discord_user_id)
19+
user = DiscordLead.objects.get(discord_id=discord_user_id)
20+
assert user.status == DiscordLead.Status.ACTIVE
21+
22+
23+
def test_no_discord_user(db):
24+
discord_user_id = '1055109241507160165'
25+
clean_discord_user(discord_user_id)
26+
user = DiscordLead.objects.get(discord_id=discord_user_id)
27+
assert user.status == DiscordLead.Status.INACTIVE
28+
29+
30+
def test_subscription_inactive(db):
31+
discord_user_id = '1055109241507160165'
32+
django_user = baker.make(DiscordUser, discord_id=discord_user_id).user
33+
subscription_type = baker.make(SubscriptionType, has_discord_access=True)
34+
baker.make(
35+
Subscription,
36+
subscription_types=[subscription_type],
37+
subscriber=django_user,
38+
status=Subscription.Status.INACTIVE
39+
)
40+
clean_discord_user(discord_user_id)
41+
user = DiscordLead.objects.get(discord_id=discord_user_id)
42+
assert user.status == DiscordLead.Status.INACTIVE
43+
44+
45+
def test_subscription_type_has_no_discord_access(db):
46+
discord_user_id = '1055109241507160165'
47+
django_user = baker.make(DiscordUser, discord_id=discord_user_id).user
48+
subscription_type = baker.make(SubscriptionType, has_discord_access=False)
49+
baker.make(
50+
Subscription,
51+
subscription_types=[subscription_type],
52+
subscriber=django_user,
53+
status=Subscription.Status.ACTIVE
54+
)
55+
clean_discord_user(discord_user_id)
56+
user = DiscordLead.objects.get(discord_id=discord_user_id)
57+
assert user.status == DiscordLead.Status.INACTIVE
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.core import management
2+
3+
4+
def test_clean_discord_users_command(mocker):
5+
mocker.patch('pythonpro.discord.facade.discord_bot_client.list_guild_members', return_value=[])
6+
management.call_command('clean_discord_users')

0 commit comments

Comments
 (0)