diff --git a/TWLight/emails/management/commands/send_email_via_api.py b/TWLight/emails/management/commands/send_email_via_api.py new file mode 100644 index 0000000000..1e24df4d5a --- /dev/null +++ b/TWLight/emails/management/commands/send_email_via_api.py @@ -0,0 +1,127 @@ +import logging +import os +import requests + +from django.core.management.base import BaseCommand, CommandError + +logger = logging.getLogger("django") + + +class Command(BaseCommand): + help = "Command that sends emails via API:Emailuser in MediaWiki" + + def info(self, msg): + # Log and print so that messages are visible + # in docker logs (log) and cron job logs (print) + logger.info(msg) + self.stdout.write(msg) + self.stdout.flush() + + def add_arguments(self, parser): + # Named (optional) arguments + # TODO: Check if we can add multiple targets, or if we have to make one request at a time + parser.add_argument( + "--target", + type=str, + help="The Wikipedia username you want to send the email to.", + ) + parser.add_argument( + "--subject", + type=str, + help="The subject of the email.", + ) + parser.add_argument( + "--body", + type=str, + help="The body of the email.", + ) + + def handle(self, *args, **options): + if not options["target"]: + raise CommandError("You need to specify a user to send the email to") + + if not options["subject"]: + raise CommandError("You need to specify the subject of the email") + + if not options["body"]: + raise CommandError("You need to specify the body of the email") + + target = options["target"] + subject = options["subject"] + body = options["body"] + + email_bot_username = os.environ.get("EMAILWIKIBOTUSERNAME", None) + email_bot_password = os.environ.get("EMAILWIKIBOTPASSWORD", None) + + if email_bot_username is None or email_bot_password is None: + # Bot credentials not provided; exiting gracefully + raise CommandError( + "The email bot username and/or password were not provided. Exiting..." + ) + # Code taken in part from https://www.mediawiki.org/wiki/API:Emailuser#Python + # TODO: Add solution in case of rate-limiting + session = requests.Session() + # TODO: See if we need to change this to Meta or the user's home wiki? + # Or is this wiki fine? + url = "https://test.wikipedia.org/w/api.php" + + # Step 1: GET request to fetch login token + login_token_params = { + "action": "query", + "meta": "tokens", + "type": "login", + "format": "json", + } + + self.info("Getting login token...") + response_login_token = session.get(url=url, params=login_token_params) + if response_login_token.status_code != 200: + raise CommandError( + "There was an error in the request for obtaining the login token." + ) + login_token_data = response_login_token.json() + + login_token = login_token_data["query"]["tokens"]["logintoken"] + + if not login_token: + raise CommandError("There was an error obtaining the login token.") + + # Step 2: POST request to log in. Use of main account for login is not + # supported. Obtain credentials via Special:BotPasswords + # (https://www.mediawiki.org/wiki/Special:BotPasswords) for lgname & lgpassword + login_params = { + "action": "login", + "lgname": email_bot_username, + "lgpassword": email_bot_password, + "lgtoken": login_token, + "format": "json", + } + + self.info("Signing in...") + login_response = session.post(url, data=login_params) + if login_response.status_code != 200: + raise CommandError("There was an error in the request for the login.") + + # Step 3: GET request to fetch Email token + email_token_params = {"action": "query", "meta": "tokens", "format": "json"} + + self.info("Getting email token...") + email_token_response = session.get(url=url, params=email_token_params) + email_token_data = email_token_response.json() + + email_token = email_token_data["query"]["tokens"]["csrftoken"] + + # Step 4: POST request to send an email + email_params = { + "action": "emailuser", + "target": target, + "subject": subject, + "text": body, + "token": email_token, + "format": "json", + } + + self.info("Sending email...") + email_response = session.post(url, data=email_params) + if email_response.status_code != 200: + raise CommandError("There was an error when trying to send the email.") diff --git a/TWLight/users/management/commands/retrieve_monthly_users.py b/TWLight/users/management/commands/retrieve_monthly_users.py index a2b5c72e0c..e7d6b4cfb9 100644 --- a/TWLight/users/management/commands/retrieve_monthly_users.py +++ b/TWLight/users/management/commands/retrieve_monthly_users.py @@ -1,16 +1,25 @@ import calendar import datetime from dateutil.relativedelta import relativedelta +import logging +from django.core.management import call_command from django.core.management.base import BaseCommand from django.db import connection -from TWLight.users.signals import UserLoginRetrieval +logger = logging.getLogger("django") class Command(BaseCommand): help = "Retrieves user names that have logged-in in the past month and have approved applications and current authorizations." + def info(self, msg): + # Log and print so that messages are visible + # in docker logs (log) and cron job logs (print) + logger.info(msg) + self.stdout.write(msg) + self.stdout.flush() + def handle(self, *args, **options): current_date = datetime.datetime.now(datetime.timezone.utc).date() last_month = current_date - relativedelta(months=1) @@ -18,6 +27,7 @@ def handle(self, *args, **options): _, last_day = calendar.monthrange(last_month.year, last_month.month) last_day_last_month = datetime.date(last_month.year, last_month.month, last_day) + self.info("Getting query...") raw_query = """SELECT users_editor.wp_username, IF( -- has application status APPROVED = 2 SENT = 4 (applications_application.status = 2 OR applications_application.status = 4), 'true', 'false') AS has_approved_apps, @@ -44,12 +54,17 @@ def handle(self, *args, **options): last_day_last_month=last_day_last_month, ) + self.info("Creating monthly users...") with connection.cursor() as cursor: cursor.execute(raw_query) columns = [col[0] for col in cursor.description] monthly_users = [dict(zip(columns, row)) for row in cursor.fetchall()] if monthly_users: - UserLoginRetrieval.user_retrieve_monthly_logins.send( - sender=self.__class__, monthly_users=monthly_users + self.info("Sending email...") + call_command( + "send_email_via_api", + target="Suecarmol", # TODO: change to Vipin's user + subject="Monthly user report", + body=f"Here is a list of monthly users: \n\n {monthly_users}", ) diff --git a/conf/local.twlight.env b/conf/local.twlight.env index d61d8e4be3..3333b0ae90 100644 --- a/conf/local.twlight.env +++ b/conf/local.twlight.env @@ -21,3 +21,5 @@ TWLIGHT_API_PROVIDER_ENDPOINT=https://meta.wikimedia.org/w/api.php TWLIGHT_EZPROXY_URL=https://ezproxy.dev.localdomain:2443 # seeem to be having troubles with --workers > 1 GUNICORN_CMD_ARGS=--name twlight --workers 1 --backlog 2048 --timeout 300 --bind=0.0.0.0:80 --forwarded-allow-ips * --reload --log-level=info +EMAILWIKIBOTUSERNAME=Add-bot-name +EMAILWIKIBOTPASSWORD=Add-bot-password diff --git a/conf/production.twlight.env b/conf/production.twlight.env index 26ed9cf526..dbabeda2ef 100644 --- a/conf/production.twlight.env +++ b/conf/production.twlight.env @@ -21,3 +21,5 @@ TWLIGHT_OAUTH_PROVIDER_URL=https://meta.wikimedia.org/w/index.php TWLIGHT_API_PROVIDER_ENDPOINT=https://meta.wikimedia.org/w/api.php TWLIGHT_EZPROXY_URL=https://wikipedialibrary.idm.oclc.org GUNICORN_CMD_ARGS=--name twlight --worker-class gthread --workers 9 --threads 1 --timeout 30 --backlog 2048 --bind=0.0.0.0:80 --forwarded-allow-ips * --reload --log-level=info +EMAILWIKIBOTUSERNAME=Add-bot-name +EMAILWIKIBOTPASSWORD=Add-bot-password diff --git a/conf/staging.twlight.env b/conf/staging.twlight.env index ef8cbf693d..375fa9ba28 100644 --- a/conf/staging.twlight.env +++ b/conf/staging.twlight.env @@ -21,3 +21,5 @@ TWLIGHT_OAUTH_PROVIDER_URL=https://meta.wikimedia.org/w/index.php TWLIGHT_API_PROVIDER_ENDPOINT=https://meta.wikimedia.org/w/api.php TWLIGHT_EZPROXY_URL=https://wikipedialibrary.idm.oclc.org:9443 GUNICORN_CMD_ARGS=--name twlight --worker-class gthread --workers 4 --threads 1 --timeout 30 --backlog 2048 --bind=0.0.0.0:80 --forwarded-allow-ips * --reload --log-level=info +EMAILWIKIBOTUSERNAME=Add-bot-name +EMAILWIKIBOTPASSWORD=Add-bot-password diff --git a/docker-compose.debug_toolbar.yml b/docker-compose.debug_toolbar.yml index 0a69b18a40..b9e6590160 100644 --- a/docker-compose.debug_toolbar.yml +++ b/docker-compose.debug_toolbar.yml @@ -1,7 +1,5 @@ --- -version: '3.4' - services: twlight: build: diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 778f059af9..937d18c01a 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -1,7 +1,5 @@ --- -version: '3.4' - # Local environment should mount plaintext files as secrets secrets: DJANGO_DB_NAME: diff --git a/docker-compose.production.yml b/docker-compose.production.yml index 6f8c59b93e..5095b978ae 100644 --- a/docker-compose.production.yml +++ b/docker-compose.production.yml @@ -1,7 +1,5 @@ --- -version: '3.4' - secrets: DJANGO_DB_NAME: external: true diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml index 0fde1bb0a6..5f45a4737b 100644 --- a/docker-compose.staging.yml +++ b/docker-compose.staging.yml @@ -1,7 +1,5 @@ --- -version: '3.4' - secrets: DJANGO_DB_NAME: external: true diff --git a/docker-compose.yml b/docker-compose.yml index 83b8b0778a..53c03e7239 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,5 @@ --- -version: '3.4' - volumes: mysql: