From 688496d857dc66508166364e30e6ed5f260602d3 Mon Sep 17 00:00:00 2001 From: salvadorpla Date: Sat, 30 Nov 2019 14:07:16 +0100 Subject: [PATCH] first commit --- README.md | 1 + db-setup.py | 152 ++++++++++++++++++++ mailing.py | 390 ++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 61 ++++++++ 4 files changed, 604 insertions(+) create mode 100644 README.md create mode 100644 db-setup.py create mode 100644 mailing.py create mode 100644 setup.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..adadce3 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# mailing diff --git a/db-setup.py b/db-setup.py new file mode 100644 index 0000000..639aaa8 --- /dev/null +++ b/db-setup.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pdb +import getpass +import os +import sys +import psycopg2 +from psycopg2 import sql +from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT + +# 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): + print("File %s not found, asking."%file_path) + write_parameter( parameter, 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) + sys.exit(0) + +def write_parameter( parameter, file_path ): + print("Setting up mailing DB parameters...") + print("\n") + mastodon_db = input("Mastodon db name: ") + mastodon_db_user = input("Mastodon db user: ") + mailing_db = input("Mailing db name: ") + mailing_db_user = input("Mailing db user: ") + mailing_db_table = input("Mailing db table: ") + with open(file_path, "w") as text_file: + print("mastodon_db: {}".format(mastodon_db), file=text_file) + print("mastodon_db_user: {}".format(mastodon_db_user), file=text_file) + print("mailing_db: {}".format(mailing_db), file=text_file) + print("mailing_db_user: {}".format(mailing_db_user), file=text_file) + print("mailing_db_table: {}".format(mailing_db_table), file=text_file) + +def create_table(db, db_user, table, sql): + + try: + + conn = None + conn = psycopg2.connect(database = db, user = db_user, password = "", host = "/var/run/postgresql", port = "5432") + cur = conn.cursor() + + + print("Creating table.. "+table) + # Create the table in PostgreSQL database + cur.execute(sql) + + conn.commit() + print("Table "+table+" created!") + print("\n") + + except (Exception, psycopg2.DatabaseError) as error: + + print(error) + + finally: + + if conn is not None: + + conn.close() + +############################################################################################# + +# Load configuration from config file +config_filepath = "config.txt" +mastodon_db = get_parameter("mastodon_db", config_filepath) # E.g., mastodon_production +mastodon_db_user = get_parameter("mastodon_db_user", config_filepath) # E.g., mastodon +mailing_db = get_parameter("mailing_db", config_filepath) # E.g., inactive +mailing_db_user = get_parameter("mailing_db_user", config_filepath) # E.g., mastodon +mailing_db_table = get_parameter("mailing_db_table", config_filepath) # E.g., inactive_users + +############################################################ +# create database +############################################################ + +try: + + conn = psycopg2.connect(dbname='postgres', + user=mailing_db_user, host='', + password='') + + conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) + + cur = conn.cursor() + + print("Creating database " + mailing_db + ". Please wait...") + cur.execute(sql.SQL("CREATE DATABASE {}").format( + sql.Identifier(mailing_db)) + ) + print("Database " + mailing_db + " created!") + +except (Exception, psycopg2.DatabaseError) as error: + + print(error) + +finally: + + if conn is not None: + + conn.close() + +############################################################################################# + +try: + + conn = None + conn = psycopg2.connect(database = mailing_db, user = mailing_db_user, password = "", host = "/var/run/postgresql", port = "5432") + +except (Exception, psycopg2.DatabaseError) as error: + + print(error) + # Load configuration from config file + os.remove("config.txt") + print("Exiting. Run setup again with right parameters") + sys.exit(0) + +if conn is not None: + + print("\n") + print("Mailing db parameters saved to config.txt!") + print("\n") + +############################################################ +# Create needed tables +############################################################ + +print("Creating table...") + +######################################## + +db = mailing_db +db_user = mailing_db_user +table = mailing_db_table +sql = "create table "+table+" (datetime timestamptz, account_id int primary key, username varchar(30), email varchar(50), emailed_at timestamptz, emailed boolean default False, deleted boolean default False, elapse_days varchar(30))" + +create_table(db, db_user, table, sql) + +##################################### + +print("Done!") +print("Now you can run setup.py!") +print("\n") diff --git a/mailing.py b/mailing.py new file mode 100644 index 0000000..29133fc --- /dev/null +++ b/mailing.py @@ -0,0 +1,390 @@ +#!/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'] = 'notificacions@mastodont.cat' + 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 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1a99267 --- /dev/null +++ b/setup.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pdb +import getpass +import fileinput,re +import os +import sys + +def create_dir(): + if not os.path.exists('secrets'): + os.makedirs('secrets') + +def create_file(): + if not os.path.exists('secrets/secrets.txt'): + with open('secrets/secrets.txt', 'w'): pass + print(secrets_filepath + " created!") + +def write_parameter( parameter, file_path ): + print("\n") + print("Setting up SMTP parameters...") + smtp_host = input("Enter SMTP hostname: ") + smtp_user_login = input("Enter SMTP user login, ex. user@" + smtp_host +"? ") + smtp_user_password = getpass.getpass("SMTP user password? ") + email_subject = input("Enter the subject of the email: ") + with open(file_path, "w") as text_file: + print("smtp_host: {}".format(smtp_host), file=text_file) + print("smtp_user_login: {}".format(smtp_user_login), file=text_file) + print("smtp_user_password: {}".format(smtp_user_password), file=text_file) + print("email_subject: {}".format(email_subject), file=text_file) + +# 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): + print("File %s not found, creating it."%file_path) + create_dir() + create_file() + write_parameter( parameter, file_path ) + print("\n") + print("SMTP setup done!\n") + print("Now you can run mailing.py!") + + # 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) + 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_pass = get_parameter("smtp_user_pass", secrets_filepath) +email_subject = get_parameter("email_subject", secrets_filepath) + +