mailing/inactives.py

464 líneas
14 KiB
Python

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)