#!/usr/bin/env python # -*- coding: utf-8 -*- from datetime import datetime, timezone import time import threading import os import sys import os.path from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText import smtplib from smtplib import SMTPException, SMTPAuthenticationError, SMTPConnectError, SMTPRecipientsRefused import psycopg2 import socket from socket import gaierror ################################################################################### # write to database mailing status of inactive users ################################################################################### def write_db(now, id, username, email, emailed_at, emailed): insert_line = "INSERT INTO " + mailing_db_table + "(datetime, account_id, username, email, emailed_at, emailed) VALUES(%s,%s,%s,%s,%s,%s) ON CONFLICT DO NOTHING" conn = None try: conn = psycopg2.connect(database = mailing_db, user = mailing_db_user, password = "", host = "/var/run/postgresql", port = "5432") cur = conn.cursor() cur.execute("SELECT account_id FROM " + mailing_db_table + " where account_id=(%s)", (id,)) row = cur.fetchone() if row == None: cur.execute(insert_line, (now, id, username, email, now, emailed)) else: if emailed == True: cur.execute("SELECT datetime FROM " + mailing_db_table + " where account_id=(%s)", (id,)) row = cur.fetchone() delta = now-row[0] cur.execute("UPDATE " + mailing_db_table + " SET elapsed_days=(%s), email=(%s), emailed=(%s) where account_id=(%s)", (delta, email, emailed, id)) conn.commit() cur.close() except (Exception, psycopg2.DatabaseError) as error: print(error) finally: if conn is not None: conn.close() ############################################################################### # check if inactive user had been already emailed ############################################################################### def email_sent(id): try: conn = psycopg2.connect(database = mailing_db, user = mailing_db_user, password = "", host = "/var/run/postgresql", port = "5432") cur = conn.cursor() cur.execute("SELECT emailed FROM " + mailing_db_table + " where account_id=(%s)", (id,)) row = cur.fetchone() if row == None: been_emailed = False else: 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() ############################################################################### # Connect to Mastodon's Postgres DB to check if any inactive user is back online ############################################################################### def check_alive(id): try: conn = psycopg2.connect(database = mastodon_db, user = mastodon_db_user, password = "", host = "/var/run/postgresql", port = "5432") cur = conn.cursor() cur.execute("select last_sign_in_at from users where account_id=(%s)", (id,)) row = cur.fetchone() seen = row[0] cur.close() return seen except (Exception, psycopg2.DatabaseError) as error: print(error) ############################################################################### # INITIALISATION ############################################################################### # Returns the parameter from the specified file 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) # Load secrets from secrets file 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) # Load configuration from config file config_filepath = "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) ############################################################################### # check if inactive user is back online ############################################################################### try: conn = psycopg2.connect(database = mailing_db, user = mailing_db_user, password = "", host = "/var/run/postgresql", port = "5432") cur = conn.cursor() cur.execute("SELECT account_id FROM " + 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() except (Exception, psycopg2.DatabaseError) as error: print(error) finally: if conn is not None: conn.close() i = 0 while i < len(inactive_users_id): seen = check_alive(inactive_users_id[i]) try: conn = psycopg2.connect(database = mailing_db, user = mailing_db_user, password = "", host = "/var/run/postgresql", port = "5432") cur = conn.cursor() cur.execute("SELECT emailed_at FROM " + mailing_db_table + " where account_id=(%s)", (inactive_users_id[i],)) row = cur.fetchone() email_datetime = row[0] email_datetime = email_datetime.replace(tzinfo=None) if email_datetime < seen: cur.execute("DELETE FROM " + mailing_db_table + " where account_id=(%s)", (inactive_users_id[i],)) conn.commit() cur.close() i += 1 except (Exception, psycopg2.DatabaseError) as error: print(error) finally: if conn is not None: conn.close() ############################################################################### now = datetime.now(timezone.utc) ############################################################################### # Connect to Mastodon's Postgres DB to get last year inactive users ############################################################################### try: conn = psycopg2.connect(database = mastodon_db, user = mastodon_db_user, password = "", host = "/var/run/postgresql", port = "5432") cur = conn.cursor() cur.execute("select account_id, email from users where last_sign_in_at < now() - interval '365 days' and disabled=False and approved=True order by last_sign_in_at desc;") rows = cur.fetchall() inactive_account_id = [] inactive_email = [] for row in rows: inactive_account_id.append(row[0]) inactive_email.append(row[1]) cur.close() except (Exception, psycopg2.DatabaseError) as error: print(error) ######################################################################################################## # get related usernames # ######################################################################################################## inactive_usernames = [] i = 0 while i < len(inactive_account_id): try: conn = psycopg2.connect(database = mastodon_db, user = mastodon_db_user, password = "", host = "/var/run/postgresql", port = "5432") cur = conn.cursor() inactive_id = inactive_account_id[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() ########################################################################################################### # email inactive users ########################################################################################################### try: fp = open('message.txt') text = fp.read() message = MIMEText(text) print(type(message)) print(message) except: print("message.txt file not found! Create it and write in the message you want for your inactive users.") finally: fp.close() i = 0 while i < len(inactive_email): been_emailed = email_sent(inactive_account_id[i]) if been_emailed == False: # Create message object instance msg = MIMEMultipart() # Declare message elements msg['From'] = smtp_user_login msg['To'] = inactive_email[i] msg['Subject'] = inactive_usernames[i] + " " + email_subject # Add the message body to the object instance msg.attach(message) try: # Create the server connection server = smtplib.SMTP(smtp_host) # Switch the connection over to TLS encryption server.starttls() # Authenticate with the server server.login(smtp_user_login, 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 write_db(now, inactive_account_id[i], inactive_usernames[i], inactive_email[i], now, emailed) 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 write_db(now, inactive_account_id[i], inactive_usernames[i], inactive_email[i], now, emailed) i += 1 else: emailed = True write_db(now, inactive_account_id[i], inactive_usernames[i], inactive_email[i], now, emailed) i += 1