From 97445baf3303c02bacf4a7443f1fed50217a42d9 Mon Sep 17 00:00:00 2001 From: spla Date: Mon, 1 Aug 2022 11:14:25 +0200 Subject: [PATCH] Refactored. Added Inactives Class --- inactives.py | 464 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 inactives.py diff --git a/inactives.py b/inactives.py new file mode 100644 index 0000000..a1402c6 --- /dev/null +++ b/inactives.py @@ -0,0 +1,464 @@ +import os +import sys +from datetime import datetime, timezone, timedelta +import time +import psycopg2 +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +import smtplib +from smtplib import SMTPException, SMTPAuthenticationError, SMTPConnectError, SMTPRecipientsRefused +import socket +from socket import gaierror +import pdb + +def smtp_config(): + + secrets_filepath = "secrets/secrets.txt" + smtp_host = get_parameter("smtp_host", secrets_filepath) + smtp_user_login = get_parameter("smtp_user_login", secrets_filepath) + smtp_user_password = get_parameter("smtp_user_password", secrets_filepath) + email_subject = get_parameter("email_subject", secrets_filepath) + + return (smtp_host, smtp_user_login, smtp_user_password, email_subject) + +def db_config(): + + config_filepath = "config/db_config.txt" + mastodon_db = get_parameter("mastodon_db", config_filepath) + mastodon_db_user = get_parameter("mastodon_db_user", config_filepath) + mailing_db = get_parameter("mailing_db", config_filepath) + mailing_db_user = get_parameter("mailing_db_user", config_filepath) + mailing_db_table = get_parameter("mailing_db_table", config_filepath) + + return (mastodon_db, mastodon_db_user, mailing_db, mailing_db_user, mailing_db_table) + +def get_parameter( parameter, file_path ): + + # Check if secrets file exists + if not os.path.isfile(file_path): + if file_path == "secrets/secrets.txt": + print("File %s not found, exiting. Run setup.py."%file_path) + elif file_path == "config.txt": + print("File %s not found, exiting. Run db-setup.py."%file_path) + sys.exit(0) + + # Find parameter in file + with open( file_path ) as f: + for line in f: + if line.startswith( parameter ): + return line.replace(parameter + ":", "").strip() + + # Cannot find parameter, exit + print(file_path + " Missing parameter %s "%parameter) + print("Run setup.py") + sys.exit(0) + +class Inactives: + + name = "Inactives" + + def __init__(self): + + smtp_host, smtp_user_login, smtp_user_password, email_subject = smtp_config() + + mastodon_db, mastodon_db_user, mailing_db, mailing_db_user, mailing_db_table = db_config() + + self.smtp_host = smtp_host + self.smtp_user_login = smtp_user_login + self.smtp_user_password = smtp_user_password + self.email_subject = email_subject + self.mastodon_db = mastodon_db + self.mastodon_db_user = mastodon_db_user + self.mailing_db = mailing_db + self.mailing_db_user = mailing_db_user + self.mailing_db_table = mailing_db_table + self.now = datetime.now(timezone.utc) + + def id(self): + + ############################################################################### + # get id of inactive users is + + conn = None + + try: + + conn = psycopg2.connect(database = self.mailing_db, user = self.mailing_db_user, password = "", host = "/var/run/postgresql", port = "5432") + + cur = conn.cursor() + + cur.execute("SELECT account_id FROM " + self.mailing_db_table) + + rows = cur.fetchall() + + if rows != []: + + inactive_users_id = [] + + for row in rows: + + inactive_users_id.append(row[0]) + + else: + + inactive_users_id = [] + + cur.close() + + return inactive_users_id + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + + def delete(self,ids_lst): + + print(f'Checking {len(ids_lst)} ids to delete reactivated or deleted accounts...') + + i = 0 + + while i < len(ids_lst): + + seen = self.check_alive(self, ids_lst[i]) + + try: + + conn = psycopg2.connect(database = self.mailing_db, user = self.mailing_db_user, password = "", host = "/var/run/postgresql", port = "5432") + + cur = conn.cursor() + + cur.execute("SELECT emailed_at FROM " + self.mailing_db_table + " where account_id=(%s)", (ids_lst[i],)) + + row = cur.fetchone() + + email_datetime = row[0] + + email_datetime = email_datetime.replace(tzinfo=None) + + if seen != None: + + reactivated = email_datetime < seen + + last_year = datetime.today() - timedelta(days=180) + + if reactivated == True or seen == None or seen > last_year: #if inactive user had reactivated its account or had deleted it we must delete related row from 'mailing_db_table' + + cur.execute("DELETE FROM " + self.mailing_db_table + " where account_id=(%s)", (ids_lst[i],)) + + print(f"Deleting user {ids_lst[i]}") + + conn.commit() + + cur.close() + + i += 1 + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + + def get_inactives(self): + + ############################################################################### + # Connect to Mastodon's Postgres DB to get last six months inactive users + ############################################################################### + + conn = None + + try: + + conn = psycopg2.connect(database = self.mastodon_db, user = self.mastodon_db_user, password = "", host = "/var/run/postgresql", port = "5432") + + cur = conn.cursor() + + #cur.execute("select account_id, email from users where current_sign_in_at < now() - interval '180 days' and disabled=False and approved=True order by current_sign_in_at desc;") + cur.execute("select account_id, email, current_sign_in_at from users where current_sign_in_at < now() - interval '180 days' and disabled=False and approved=True and account_id in (select id from accounts where actor_type='Person')") + + rows = cur.fetchall() + + inactive_account_id = [] + + inactive_email = [] + + current_sign_in_at = [] + + for row in rows: + + inactive_account_id.append(row[0]) + + inactive_email.append(row[1]) + + current_sign_in_at.append(row[2]) + + cur.close() + + print(f'inactive accounts: {len(inactive_account_id)}') + + return (inactive_account_id, inactive_email, current_sign_in_at) + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + + def usernames(self, account_ids): + + ######################################################################################################## + # get accounts usernames # + ######################################################################################################## + + inactive_usernames = [] + + i = 0 + while i < len(account_ids): + + conn = None + + try: + + conn = psycopg2.connect(database = self.mastodon_db, user = self.mastodon_db_user, password = "", host = "/var/run/postgresql", port = "5432") + + cur = conn.cursor() + + inactive_id = account_ids[i] + + cur.execute("select username from accounts where id = '%s';", [inactive_id]) + + row = cur.fetchone() + + new_username = row[0] + + inactive_usernames.append(new_username) + + i += 1 + + cur.close() + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + + return inactive_usernames + + def mailing(self, account_ids, account_emails, account_usernames, account_current_sign_in_ats): + + ########################################################################################################### + # email inactive users + + try: + + fp = open('message.txt') + text = fp.read() + message = MIMEText(text) + fp.close() + + except: + + print("message.txt file not found! Create it and write in the message you want for your inactive users.") + sys.exit(0) + + i = 0 + while i < len(account_emails): + + been_emailed = self.email_sent(self, account_ids[i]) + + if been_emailed == False: + + # Create message object instance + msg = MIMEMultipart() + + # Declare message elements + msg['From'] = self.smtp_user_login + msg['To'] = account_emails[i] + msg['Subject'] = account_usernames[i] + " " + self.email_subject + + # Add the message body to the object instance + msg.attach(message) + + try: + + # Create the server connection + server = smtplib.SMTP(self.smtp_host) + # Switch the connection over to TLS encryption + server.starttls() + # Authenticate with the server + server.login(self.smtp_user_login, self.smtp_user_password) + # Send the message + server.sendmail(msg['From'], msg['To'], msg.as_string()) + # Disconnect + server.quit() + + print("Successfully sent email message to %s" % msg['To']) + emailed = True + + self.write_db(self, self.now, account_ids[i], account_usernames[i], account_emails[i], self.now, emailed, account_current_sign_in_ats[i]) + + i += 1 + time.sleep(5) + + except SMTPAuthenticationError as auth_error: + + print(auth_error) + sys.exit(":-(") + + except socket.gaierror as socket_error: + + print(socket_error) + print("Unknown SMTP server") + sys.exit(":-(") + + except SMTPRecipientsRefused as recip_error: + + print(recip_error) + emailed = False + self.write_db(self, self.now, account_ids[i], account_usernames[i], account_emails[i], self.now, emailed, account_current_sign_in_ats[i]) + i += 1 + + else: + + emailed = True + self.write_db(self, self.now, account_ids[i], account_usernames[i], account_emails[i], self.now, emailed, account_current_sign_in_ats[i]) + i += 1 + + @staticmethod + def write_db(self, now, id, username, email, emailed_at, emailed, current_sign_in_at): + + ################################################################################### + # write to mailing database the status of inactive users + + + insert_line = "INSERT INTO " + self.mailing_db_table + "(datetime, account_id, username, email, emailed_at, emailed, current_sign_in_at) VALUES(%s,%s,%s,%s,%s,%s,%s) ON CONFLICT DO NOTHING" + + conn = None + + try: + + conn = psycopg2.connect(database = self.mailing_db, user = self.mailing_db_user, password = "", host = "/var/run/postgresql", port = "5432") + + cur = conn.cursor() + + cur.execute("SELECT account_id FROM " + self.mailing_db_table + " where account_id=(%s)", (id,)) + + row = cur.fetchone() + + if row == None: + + cur.execute(insert_line, (now, id, username, email, now, emailed, current_sign_in_at)) + + else: + + if emailed == True: + + cur.execute("SELECT datetime FROM " + self.mailing_db_table + " where account_id=(%s)", (id,)) + + row = cur.fetchone() + delta = now-row[0] + + cur.execute("UPDATE " + self.mailing_db_table + " SET elapsed_days=(%s), email=(%s), emailed=(%s), current_sign_in_at=(%s) where account_id=(%s)", (delta.days, email, emailed, current_sign_in_at, id)) + + print(f"Updating user {str(id)}") + + conn.commit() + + cur.close() + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + + @staticmethod + def email_sent(self, account_id): + + ############################################################################### + # check if inactive user had been already emailed + + been_emailed = False + + conn = None + + try: + + conn = psycopg2.connect(database = self.mailing_db, user = self.mailing_db_user, password = "", host = "/var/run/postgresql", port = "5432") + + cur = conn.cursor() + + cur.execute("SELECT emailed FROM " + self.mailing_db_table + " where account_id=(%s)", (account_id,)) + + row = cur.fetchone() + + if row != None: + + been_emailed = row[0] + + cur.close() + + return been_emailed + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + + @staticmethod + def check_alive(self, id): + + conn = None + + try: + + conn = psycopg2.connect(database = self.mastodon_db, user = self.mastodon_db_user, password = "", host = "/var/run/postgresql", port = "5432") + + cur = conn.cursor() + + cur.execute("select current_sign_in_at from users where account_id=(%s)", (id,)) + + row = cur.fetchone() + + if row != None: + + seen = row[0] + else: + + seen = None + + cur.close() + + return seen + + except (Exception, psycopg2.DatabaseError) as error: + + print(error)