Skip to content

Commit

Permalink
Implemented warning sales msgs on channel
Browse files Browse the repository at this point in the history
Close #4870
  • Loading branch information
renzo authored and renzon committed May 23, 2024
1 parent 1ef2fb8 commit 769a35c
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 19 deletions.
3 changes: 2 additions & 1 deletion contrib/env-sample
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,5 @@ HOTZAPP_API_URL=https://hotzapp.me
DISCORD_APP_CLIENT_ID=your-app-client-id
DISCORD_APP_CLIENT_SECRET=your-app-client-secret
DISCORD_APP_BOT_TOKEN=your-bot-token
DISCORD_GUILD_ID=your-discord_server_id
DISCORD_GUILD_ID=your-discord_server_id
DISCORD_GUILD_SALES_CHANNEL_ID=your-guild-discord-channel-to-send-sales-messages
12 changes: 11 additions & 1 deletion pythonpro/discord/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@

from pythonpro.discord.api_client import DiscordBotClient

discord_bot_client = DiscordBotClient(settings.DISCORD_APP_BOT_TOKEN)

class _DevProDiscordBotClient(DiscordBotClient):
"""
This class provides data respective to specific seetings for DevPro Discord Guild
"""

def send_to_sales_channel(self, msg: str) -> dict:
return self.create_message(settings.DISCORD_GUILD_SALES_CHANNEL_ID, msg)


devpro_discord_bot_client = _DevProDiscordBotClient(settings.DISCORD_APP_BOT_TOKEN)
26 changes: 22 additions & 4 deletions pythonpro/discord/facade.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import logging
from datetime import timedelta

from django.conf import settings
from django.contrib.auth import get_user_model
from django.db.models import Max

from pythonpro.discord.bot import discord_bot_client
from pythonpro.discord.tasks import clean_discord_user
from pythonpro.discord.bot import devpro_discord_bot_client
from pythonpro.discord.tasks import clean_discord_user, warn_subscription_expiration
from pythonpro.memberkit.models import Subscription

from django.utils.timezone import datetime

logger = logging.getLogger(__name__)


def clean_discord_users():
discord_user_id = 0
while True:
discord_members = discord_bot_client.list_guild_members(settings.DISCORD_GUILD_ID, after=discord_user_id)
discord_members = devpro_discord_bot_client.list_guild_members(settings.DISCORD_GUILD_ID, after=discord_user_id)
if len(discord_members) == 0:
break
for member in discord_members:
Expand All @@ -25,4 +31,16 @@ def clean_discord_users():


def warn_users_about_subscription_expiration():
return None
today = datetime.today()
thirty_days_in_future = today + timedelta(days=30)
users_with_subscription_expirating = get_user_model().objects.annotate(
max_subscription_expiration_date=Max('subscriptions__expired_at')
).filter(
subscriptions__status=Subscription.Status.ACTIVE,
max_subscription_expiration_date__lte=thirty_days_in_future
).values('id', 'max_subscription_expiration_date')
for user_dct in users_with_subscription_expirating:
warn_subscription_expiration.delay(
user_dct['id'],
user_dct['max_subscription_expiration_date'].strftime('%d/%m/%Y')
)
91 changes: 80 additions & 11 deletions pythonpro/discord/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,27 @@

from celery import shared_task
from django.conf import settings
from django.contrib.auth import get_user_model

from pythonpro.discord.bot import discord_bot_client
from pythonpro.discord.models import DiscordLead
from pythonpro.discord.bot import devpro_discord_bot_client
from pythonpro.discord.models import DiscordLead, DiscordUser
from pythonpro.memberkit.models import Subscription

logger = logging.getLogger(__name__)

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


@shared_task(
rate_limit=1,
Expand All @@ -31,21 +45,76 @@ def clean_discord_user(discord_user_id):
)

if not has_discord_access:
discord_bot_client.send_user_message(discord_user_id, msg)
discord_bot_client.remove_guild_member(settings.DISCORD_GUILD_ID, discord_user_id)
devpro_discord_bot_client.send_user_message(discord_user_id, msg)
devpro_discord_bot_client.remove_guild_member(settings.DISCORD_GUILD_ID, discord_user_id)

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


msg = """Olá, sou o bot da DevPro no Discord.
_SALES_MSG_TEMPLATE = """Usuário: {user_name}
Com licença expirando em {expiration_date}
Id: {user_id}
email; {user_email}
"""

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

Você pode conferir todo seus histórico de assinaturas acessando
_WARN_USER_TEMPLATE = """Olá {user_name},
https://painel.dev.pro.br
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.
Se tiver qualquer dúvida, entre em contato pelo email [email protected]
Renove agora e garanta um desconto exclusivo de R$ 300!
Um abraço do Bot da DevPro
"""
🔒 Por que renovar sua assinatura?
* Economize R$ 300: Apenas para assinantes atuais, estamos oferecendo um desconto especial de R$ 300 na renovação anual.
* Acesso Ininterrupto: Continue desfrutando de conteúdos exclusivos, cursos atualizados e suporte especializado sem nenhuma interrupção.
* 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.
* Novidades e Exclusividades: Esteja sempre à frente com as últimas novidades e ferramentas que a DevPro tem a oferecer.
⚠️ 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.
Não deixe essa oportunidade escapar! Renove sua assinatura agora e continue sua jornada de aprendizado e crescimento profissional com a DevPro.
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].
Estamos ansiosos para continuar sendo parte do seu sucesso!
Atenciosamente,
Bot DevPro
OBS: Para você não correr os risco de perder essa oportunidade, vou te reenviar essa mensagem uma vez por dia.
""" # noqa: E501 W291


@shared_task(
rate_limit=1,
max_retries=5,
retry_backoff=True,
retry_backoff_max=700,
retry_jitter=True
)
def warn_subscription_expiration(user_id: int, expiration_date: str):
user = get_user_model().objects.select_related('discorduser').get(id=user_id)
devpro_discord_bot_client.send_to_sales_channel(_SALES_MSG_TEMPLATE.format(
user_name=user.first_name,
user_email=user.email,
user_id=user_id,
expiration_date=expiration_date,

))

logging.info(f'Warn msg sent to sales discord channel for user with id {user_id}')
try:
discorduser = user.discorduser
except DiscordUser.DoesNotExist:
logger.info(f'No discord user found for user with id {user.id}')
else:
devpro_discord_bot_client.send_user_message(
discorduser.discord_id,
_WARN_USER_TEMPLATE.format(
user_name=user.first_name,
expiration_date=expiration_date
)
)
logger.info(f'Subscription warn sent to user with id: {user.id}')
return None
45 changes: 43 additions & 2 deletions pythonpro/discord/tests/test_commands.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,51 @@
from datetime import timedelta

from django.core import management
from django.utils.datetime_safe import datetime
from model_bakery import baker

from pythonpro.memberkit.models import Subscription


def test_clean_discord_users_command(mocker):
mocker.patch('pythonpro.discord.facade.discord_bot_client.list_guild_members', return_value=[])
mocker.patch('pythonpro.discord.facade.devpro_discord_bot_client.list_guild_members', return_value=[])
management.call_command('clean_discord_users')


def test_warn_users_about_subscriptions():
def test_warn_users_about_subscriptions(mocker, django_user_model):
mock = mocker.patch('pythonpro.discord.facade.warn_subscription_expiration', return_value=[])
subscriber = baker.make(django_user_model)
expiration_date = datetime.today() + timedelta(days=29)
baker.make(
Subscription, status=Subscription.Status.ACTIVE,
expired_at=expiration_date,
subscriber=subscriber
)
management.call_command('warn_users_about_subscription_expiration')
mock.delay.assert_called_once_with(subscriber.id, expiration_date.strftime('%d/%m/%Y'))


def test_dont_botter_user_with_inactive_subscriptions(mocker, django_user_model):
mock = mocker.patch('pythonpro.discord.facade.warn_subscription_expiration', return_value=[])
subscriber = baker.make(django_user_model)
expiration_date = datetime.today() + timedelta(days=29)
baker.make(
Subscription, status=Subscription.Status.INACTIVE,
expired_at=expiration_date,
subscriber=subscriber
)
management.call_command('warn_users_about_subscription_expiration')
assert not mock.delay.called


def test_dont_botter_user_with_more_than_30_days_subscription(mocker, django_user_model):
mock = mocker.patch('pythonpro.discord.facade.warn_subscription_expiration', return_value=[])
subscriber = baker.make(django_user_model)
expiration_date = datetime.today() + timedelta(days=31)
baker.make(
Subscription, status=Subscription.Status.ACTIVE,
expired_at=expiration_date,
subscriber=subscriber
)
management.call_command('warn_users_about_subscription_expiration')
assert not mock.delay.called
77 changes: 77 additions & 0 deletions pythonpro/discord/tests/test_warn_subscription_expiration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from model_bakery import baker

from pythonpro.discord.models import DiscordUser
from pythonpro.discord.tasks import warn_subscription_expiration, _SALES_MSG_TEMPLATE


def test_warn_subscription_expiration(django_user_model, mocker):
user = baker.make(django_user_model)
send_to_sales_mock = mocker.patch(
'pythonpro.discord.facade.devpro_discord_bot_client.send_to_sales_channel',
return_value=[]
)
expiration_date = '23/05/2024'
warn_subscription_expiration(user.id, expiration_date)
send_to_sales_mock.assert_called_once_with(_SALES_MSG_TEMPLATE.format(
user_name=user.first_name,
user_email=user.email,
user_id=user.id,
expiration_date=expiration_date,

))


WARN_MSG = """Olá John,
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.
Renove agora e garanta um desconto exclusivo de R$ 300!
🔒 Por que renovar sua assinatura?
* Economize R$ 300: Apenas para assinantes atuais, estamos oferecendo um desconto especial de R$ 300 na renovação anual.
* Acesso Ininterrupto: Continue desfrutando de conteúdos exclusivos, cursos atualizados e suporte especializado sem nenhuma interrupção.
* 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.
* Novidades e Exclusividades: Esteja sempre à frente com as últimas novidades e ferramentas que a DevPro tem a oferecer.
⚠️ 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.
Não deixe essa oportunidade escapar! Renove sua assinatura agora e continue sua jornada de aprendizado e crescimento profissional com a DevPro.
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].
Estamos ansiosos para continuar sendo parte do seu sucesso!
Atenciosamente,
Bot DevPro
OBS: Para você não correr os risco de perder essa oportunidade, vou te reenviar essa mensagem uma vez por dia.
""" # noqa: E501 W291


def test_warn_subscription_expiration_user_and_sales_channel(django_user_model, mocker):
django_user = baker.make(django_user_model, first_name='John')
discord_user = baker.make(DiscordUser, user=django_user, discord_id='946364767864504360')

send_to_sales_mock = mocker.patch(
'pythonpro.discord.facade.devpro_discord_bot_client.send_to_sales_channel',
return_value=[]
)

send_user_msg_mock = mocker.patch(
'pythonpro.discord.facade.devpro_discord_bot_client.send_user_message',
return_value=[]
)
expiration_date = '23/05/2024'

warn_subscription_expiration(django_user.id, expiration_date)

send_to_sales_mock.assert_called_once_with(_SALES_MSG_TEMPLATE.format(
user_name=django_user.first_name,
user_email=django_user.email,
user_id=django_user.id,
expiration_date=expiration_date,

))

send_user_msg_mock.assert_called_once_with(discord_user.discord_id, WARN_MSG)
1 change: 1 addition & 0 deletions pythonpro/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@
DISCORD_APP_CLIENT_SECRET = config('DISCORD_APP_CLIENT_SECRET')
DISCORD_APP_BOT_TOKEN = config('DISCORD_APP_BOT_TOKEN')
DISCORD_GUILD_ID = config('DISCORD_GUILD_ID')
DISCORD_GUILD_SALES_CHANNEL_ID = config('DISCORD_GUILD_SALES_CHANNEL_ID')

# Celery config

Expand Down

0 comments on commit 769a35c

Please sign in to comment.