Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0ad1aea

Browse files
author
renzo
committed
Implemented warning sales msgs on channel
Close #4870
1 parent a22224f commit 0ad1aea

File tree

7 files changed

+236
-19
lines changed

7 files changed

+236
-19
lines changed

contrib/env-sample

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,5 @@ HOTZAPP_API_URL=https://hotzapp.me
6868
DISCORD_APP_CLIENT_ID=your-app-client-id
6969
DISCORD_APP_CLIENT_SECRET=your-app-client-secret
7070
DISCORD_APP_BOT_TOKEN=your-bot-token
71-
DISCORD_GUILD_ID=your-discord_server_id
71+
DISCORD_GUILD_ID=your-discord_server_id
72+
DISCORD_GUILD_SALES_CHANNEL_ID=your-guild-discord-channel-to-send-sales-messages

pythonpro/discord/bot.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,14 @@
22

33
from pythonpro.discord.api_client import DiscordBotClient
44

5-
discord_bot_client = DiscordBotClient(settings.DISCORD_APP_BOT_TOKEN)
5+
6+
class _DevProDiscordBotClient(DiscordBotClient):
7+
"""
8+
This class provides data respective to specific seetings for DevPro Discord Guild
9+
"""
10+
11+
def send_to_sales_channel(self, msg: str) -> dict:
12+
return self.create_message(settings.DISCORD_GUILD_SALES_CHANNEL_ID, msg)
13+
14+
15+
devpro_discord_bot_client = _DevProDiscordBotClient(settings.DISCORD_APP_BOT_TOKEN)

pythonpro/discord/facade.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
import logging
2+
from datetime import timedelta
23

34
from django.conf import settings
5+
from django.contrib.auth import get_user_model
6+
from django.db.models import Max
47

5-
from pythonpro.discord.bot import discord_bot_client
6-
from pythonpro.discord.tasks import clean_discord_user
8+
from pythonpro.discord.bot import devpro_discord_bot_client
9+
from pythonpro.discord.tasks import clean_discord_user, warn_subscription_expiration
10+
from pythonpro.memberkit.models import Subscription
11+
12+
from django.utils.timezone import datetime
713

814
logger = logging.getLogger(__name__)
915

1016

1117
def clean_discord_users():
1218
discord_user_id = 0
1319
while True:
14-
discord_members = discord_bot_client.list_guild_members(settings.DISCORD_GUILD_ID, after=discord_user_id)
20+
discord_members = devpro_discord_bot_client.list_guild_members(settings.DISCORD_GUILD_ID, after=discord_user_id)
1521
if len(discord_members) == 0:
1622
break
1723
for member in discord_members:
@@ -25,4 +31,16 @@ def clean_discord_users():
2531

2632

2733
def warn_users_about_subscription_expiration():
28-
return None
34+
today = datetime.today()
35+
thirty_days_in_future = today + timedelta(days=30)
36+
users_with_subscription_expirating = get_user_model().objects.annotate(
37+
max_subscription_expiration_date=Max('subscriptions__expired_at')
38+
).filter(
39+
subscriptions__status=Subscription.Status.ACTIVE,
40+
max_subscription_expiration_date__lte=thirty_days_in_future
41+
).values('id', 'max_subscription_expiration_date')
42+
for user_dct in users_with_subscription_expirating:
43+
warn_subscription_expiration.delay(
44+
user_dct['id'],
45+
user_dct['max_subscription_expiration_date'].strftime('%d/%m/%Y')
46+
)

pythonpro/discord/tasks.py

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,27 @@
22

33
from celery import shared_task
44
from django.conf import settings
5+
from django.contrib.auth import get_user_model
56

6-
from pythonpro.discord.bot import discord_bot_client
7-
from pythonpro.discord.models import DiscordLead
7+
from pythonpro.discord.bot import devpro_discord_bot_client
8+
from pythonpro.discord.models import DiscordLead, DiscordUser
89
from pythonpro.memberkit.models import Subscription
910

1011
logger = logging.getLogger(__name__)
1112

13+
msg = """Olá, sou o bot da DevPro no Discord.
14+
15+
Eu não identifiquei sua conta de Discord em nosso sistema. Por isso eu removi seu acesso.
16+
17+
Você pode conferir todo seus histórico de assinaturas acessando
18+
19+
https://painel.dev.pro.br
20+
21+
Se tiver qualquer dúvida, entre em contato pelo email [email protected]
22+
23+
Um abraço do Bot da DevPro
24+
"""
25+
1226

1327
@shared_task(
1428
rate_limit=1,
@@ -31,21 +45,76 @@ def clean_discord_user(discord_user_id):
3145
)
3246

3347
if not has_discord_access:
34-
discord_bot_client.send_user_message(discord_user_id, msg)
35-
discord_bot_client.remove_guild_member(settings.DISCORD_GUILD_ID, discord_user_id)
48+
devpro_discord_bot_client.send_user_message(discord_user_id, msg)
49+
devpro_discord_bot_client.remove_guild_member(settings.DISCORD_GUILD_ID, discord_user_id)
3650

3751
logging.info(f'Clean discord user: {discord_user_id} with status: {lead_status.label}')
3852

3953

40-
msg = """Olá, sou o bot da DevPro no Discord.
54+
_SALES_MSG_TEMPLATE = """Usuário: {user_name}
55+
Com licença expirando em {expiration_date}
56+
Id: {user_id}
57+
email; {user_email}
58+
"""
4159

42-
Eu não identifiquei sua conta de Discord em nosso sistema. Por isso eu removi seu acesso.
4360

44-
Você pode conferir todo seus histórico de assinaturas acessando
61+
_WARN_USER_TEMPLATE = """Olá {user_name},
4562
46-
https://painel.dev.pro.br
63+
Sua assinatura anual da DevPro está prestes a expirar, e queremos oferecer uma oportunidade imperdível para que você continue aproveitando todos os benefícios de ser nosso assinante.
4764
48-
Se tiver qualquer dúvida, entre em contato pelo email [email protected]
65+
Renove agora e garanta um desconto exclusivo de R$ 300!
4966
50-
Um abraço do Bot da DevPro
51-
"""
67+
🔒 Por que renovar sua assinatura?
68+
69+
* Economize R$ 300: Apenas para assinantes atuais, estamos oferecendo um desconto especial de R$ 300 na renovação anual.
70+
* Acesso Ininterrupto: Continue desfrutando de conteúdos exclusivos, cursos atualizados e suporte especializado sem nenhuma interrupção.
71+
* Encontros ao Vivo com Instrutores Experientes: Mantenha o acesso a sessões ao vivo com nossos instrutores especializados, proporcionando aprendizado personalizado e esclarecimento de dúvidas em tempo real.
72+
* Novidades e Exclusividades: Esteja sempre à frente com as últimas novidades e ferramentas que a DevPro tem a oferecer.
73+
74+
⚠️ Atenção: Este desconto de R$ 300 é válido apenas até o vencimento da sua assinatura atual. Após {expiration_date}, o valor cheio será aplicado, e você perderá essa oferta especial.
75+
76+
Não deixe essa oportunidade escapar! Renove sua assinatura agora e continue sua jornada de aprendizado e crescimento profissional com a DevPro.
77+
78+
Para renovar, acesse https://painel.dev.pro.br/checkout/pagarme/renovacao-comunidade-devpro ou entre em contato com nosso suporte através do [email protected].
79+
80+
Estamos ansiosos para continuar sendo parte do seu sucesso!
81+
82+
Atenciosamente,
83+
Bot DevPro
84+
85+
OBS: Para você não correr os risco de perder essa oportunidade, vou te reenviar essa mensagem uma vez por dia.
86+
""" # noqa: E501 W291
87+
88+
89+
@shared_task(
90+
rate_limit=1,
91+
max_retries=5,
92+
retry_backoff=True,
93+
retry_backoff_max=700,
94+
retry_jitter=True
95+
)
96+
def warn_subscription_expiration(user_id: int, expiration_date: str):
97+
user = get_user_model().objects.select_related('discorduser').get(id=user_id)
98+
devpro_discord_bot_client.send_to_sales_channel(_SALES_MSG_TEMPLATE.format(
99+
user_name=user.first_name,
100+
user_email=user.email,
101+
user_id=user_id,
102+
expiration_date=expiration_date,
103+
104+
))
105+
106+
logging.info(f'Warn msg sent to sales discord channel for user with id {user_id}')
107+
try:
108+
discorduser = user.discorduser
109+
except DiscordUser.DoesNotExist:
110+
logger.info(f'No discord user found for user with id {user.id}')
111+
else:
112+
devpro_discord_bot_client.send_user_message(
113+
discorduser.discord_id,
114+
_WARN_USER_TEMPLATE.format(
115+
user_name=user.first_name,
116+
expiration_date=expiration_date
117+
)
118+
)
119+
logger.info(f'Subscription warn sent to user with id: {user.id}')
120+
return None
Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,51 @@
1+
from datetime import timedelta
2+
13
from django.core import management
4+
from django.utils.datetime_safe import datetime
5+
from model_bakery import baker
6+
7+
from pythonpro.memberkit.models import Subscription
28

39

410
def test_clean_discord_users_command(mocker):
5-
mocker.patch('pythonpro.discord.facade.discord_bot_client.list_guild_members', return_value=[])
11+
mocker.patch('pythonpro.discord.facade.devpro_discord_bot_client.list_guild_members', return_value=[])
612
management.call_command('clean_discord_users')
713

814

9-
def test_warn_users_about_subscriptions():
15+
def test_warn_users_about_subscriptions(mocker, django_user_model):
16+
mock = mocker.patch('pythonpro.discord.facade.warn_subscription_expiration', return_value=[])
17+
subscriber = baker.make(django_user_model)
18+
expiration_date = datetime.today() + timedelta(days=29)
19+
baker.make(
20+
Subscription, status=Subscription.Status.ACTIVE,
21+
expired_at=expiration_date,
22+
subscriber=subscriber
23+
)
24+
management.call_command('warn_users_about_subscription_expiration')
25+
mock.delay.assert_called_once_with(subscriber.id, expiration_date.strftime('%d/%m/%Y'))
26+
27+
28+
def test_dont_botter_user_with_inactive_subscriptions(mocker, django_user_model):
29+
mock = mocker.patch('pythonpro.discord.facade.warn_subscription_expiration', return_value=[])
30+
subscriber = baker.make(django_user_model)
31+
expiration_date = datetime.today() + timedelta(days=29)
32+
baker.make(
33+
Subscription, status=Subscription.Status.INACTIVE,
34+
expired_at=expiration_date,
35+
subscriber=subscriber
36+
)
37+
management.call_command('warn_users_about_subscription_expiration')
38+
assert not mock.delay.called
39+
40+
41+
def test_dont_botter_user_with_more_than_30_days_subscription(mocker, django_user_model):
42+
mock = mocker.patch('pythonpro.discord.facade.warn_subscription_expiration', return_value=[])
43+
subscriber = baker.make(django_user_model)
44+
expiration_date = datetime.today() + timedelta(days=31)
45+
baker.make(
46+
Subscription, status=Subscription.Status.ACTIVE,
47+
expired_at=expiration_date,
48+
subscriber=subscriber
49+
)
1050
management.call_command('warn_users_about_subscription_expiration')
51+
assert not mock.delay.called
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from model_bakery import baker
2+
3+
from pythonpro.discord.models import DiscordUser
4+
from pythonpro.discord.tasks import warn_subscription_expiration, _SALES_MSG_TEMPLATE
5+
6+
7+
def test_warn_subscription_expiration(django_user_model, mocker):
8+
user = baker.make(django_user_model)
9+
send_to_sales_mock = mocker.patch(
10+
'pythonpro.discord.facade.devpro_discord_bot_client.send_to_sales_channel',
11+
return_value=[]
12+
)
13+
expiration_date = '23/05/2024'
14+
warn_subscription_expiration(user.id, expiration_date)
15+
send_to_sales_mock.assert_called_once_with(_SALES_MSG_TEMPLATE.format(
16+
user_name=user.first_name,
17+
user_email=user.email,
18+
user_id=user.id,
19+
expiration_date=expiration_date,
20+
21+
))
22+
23+
24+
WARN_MSG = """Olá John,
25+
26+
Sua assinatura anual da DevPro está prestes a expirar, e queremos oferecer uma oportunidade imperdível para que você continue aproveitando todos os benefícios de ser nosso assinante.
27+
28+
Renove agora e garanta um desconto exclusivo de R$ 300!
29+
30+
🔒 Por que renovar sua assinatura?
31+
32+
* Economize R$ 300: Apenas para assinantes atuais, estamos oferecendo um desconto especial de R$ 300 na renovação anual.
33+
* Acesso Ininterrupto: Continue desfrutando de conteúdos exclusivos, cursos atualizados e suporte especializado sem nenhuma interrupção.
34+
* Encontros ao Vivo com Instrutores Experientes: Mantenha o acesso a sessões ao vivo com nossos instrutores especializados, proporcionando aprendizado personalizado e esclarecimento de dúvidas em tempo real.
35+
* Novidades e Exclusividades: Esteja sempre à frente com as últimas novidades e ferramentas que a DevPro tem a oferecer.
36+
37+
⚠️ Atenção: Este desconto de R$ 300 é válido apenas até o vencimento da sua assinatura atual. Após 23/05/2024, o valor cheio será aplicado, e você perderá essa oferta especial.
38+
39+
Não deixe essa oportunidade escapar! Renove sua assinatura agora e continue sua jornada de aprendizado e crescimento profissional com a DevPro.
40+
41+
Para renovar, acesse https://painel.dev.pro.br/checkout/pagarme/renovacao-comunidade-devpro ou entre em contato com nosso suporte através do [email protected].
42+
43+
Estamos ansiosos para continuar sendo parte do seu sucesso!
44+
45+
Atenciosamente,
46+
Bot DevPro
47+
48+
OBS: Para você não correr os risco de perder essa oportunidade, vou te reenviar essa mensagem uma vez por dia
49+
""" # noqa: E501 W291
50+
51+
52+
def test_warn_subscription_expiration_user_and_sales_channel(django_user_model, mocker):
53+
django_user = baker.make(django_user_model, first_name='John')
54+
discord_user = baker.make(DiscordUser, user=django_user, discord_id='946364767864504360')
55+
56+
send_to_sales_mock = mocker.patch(
57+
'pythonpro.discord.facade.devpro_discord_bot_client.send_to_sales_channel',
58+
return_value=[]
59+
)
60+
61+
send_user_msg_mock = mocker.patch(
62+
'pythonpro.discord.facade.devpro_discord_bot_client.send_user_message',
63+
return_value=[]
64+
)
65+
expiration_date = '23/05/2024'
66+
67+
warn_subscription_expiration(django_user.id, expiration_date)
68+
69+
send_to_sales_mock.assert_called_once_with(_SALES_MSG_TEMPLATE.format(
70+
user_name=django_user.first_name,
71+
user_email=django_user.email,
72+
user_id=django_user.id,
73+
expiration_date=expiration_date,
74+
75+
))
76+
77+
send_user_msg_mock.assert_called_once_with(discord_user.discord_id, WARN_MSG)

pythonpro/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@
315315
DISCORD_APP_CLIENT_SECRET = config('DISCORD_APP_CLIENT_SECRET')
316316
DISCORD_APP_BOT_TOKEN = config('DISCORD_APP_BOT_TOKEN')
317317
DISCORD_GUILD_ID = config('DISCORD_GUILD_ID')
318+
DISCORD_GUILD_SALES_CHANNEL_ID = config('DISCORD_GUILD_SALES_CHANNEL_ID')
318319

319320
# Celery config
320321

0 commit comments

Comments
 (0)