From 9746a654736cba12c234f3c1b3d7b67427678ce3 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Sat, 27 Jun 2015 22:20:48 +0200 Subject: Port notification routines to Python Use a Python script for sending notification emails. The notification action and additional parameters are passed via command line arguments. For comment and package request notifications, the text is passed via stdin. Signed-off-by: Lukas Fleischer --- scripts/notify.py | 232 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100755 scripts/notify.py (limited to 'scripts') diff --git a/scripts/notify.py b/scripts/notify.py new file mode 100755 index 0000000..017555a --- /dev/null +++ b/scripts/notify.py @@ -0,0 +1,232 @@ +#!/usr/bin/python3 + +import configparser +import email.mime.text +import mysql.connector +import os +import smtplib +import subprocess +import sys +import textwrap + +config = configparser.RawConfigParser() +config.read(os.path.dirname(os.path.realpath(__file__)) + '/../conf/config') + +aur_db_host = config.get('database', 'host') +aur_db_name = config.get('database', 'name') +aur_db_user = config.get('database', 'user') +aur_db_pass = config.get('database', 'password') +aur_db_socket = config.get('database', 'socket') + +aur_location = config.get('options', 'aur_location') +aur_request_ml = config.get('options', 'aur_request_ml') + +sendmail = config.get('notifications', 'sendmail') +sender = config.get('notifications', 'sender') +reply_to = config.get('notifications', 'reply-to') + + +def send_notification(to, subject, body, cc=None, reference=None): + body = str.join('\n', [textwrap.fill(line) for line in body.splitlines()]) + + for recipient in to: + msg = email.mime.text.MIMEText(body, 'plain', 'utf-8') + msg['Subject'] = subject + msg['From'] = sender + msg['Reply-to'] = reply_to + msg['To'] = recipient + + if cc: + msg['Cc'] = str.join(', ', cc) + + if reference: + msg['In-Reply-To'] = reference + msg['References'] = reference + + p = subprocess.Popen([sendmail, '-t', '-oi'], stdin=subprocess.PIPE) + p.communicate(msg.as_bytes()) + +def username_from_id(cur, uid): + cur.execute('SELECT UserName FROM Users WHERE ID = %s', [uid]) + return cur.fetchone()[0] + +def pkgbase_from_id(cur, pkgbase_id): + cur.execute('SELECT Name FROM PackageBases WHERE ID = %s', [pkgbase_id]) + return cur.fetchone()[0] + +def pkgbase_from_pkgreq(cur, reqid): + cur.execute('SELECT PackageBaseID FROM PackageRequests WHERE ID = %s', + [reqid]) + return cur.fetchone()[0] + +def get_maintainer_email(cur, pkgbase_id): + cur.execute('SELECT Users.Email FROM Users ' + + 'INNER JOIN PackageBases ' + + 'ON PackageBases.MaintainerUID = Users.ID WHERE ' + + 'PackageBases.ID = %s', [pkgbase_id]) + return cur.fetchone()[0] + +def get_recipients(cur, pkgbase_id, uid): + cur.execute('SELECT DISTINCT Users.Email FROM Users ' + + 'INNER JOIN CommentNotify ' + + 'ON CommentNotify.UserID = Users.ID WHERE ' + + 'CommentNotify.UserID != %s AND ' + + 'CommentNotify.PackageBaseID = %s', [uid, pkgbase_id]) + return [row[0] for row in cur.fetchall()] + +def get_request_recipients(cur, pkgbase_id, uid): + cur.execute('SELECT DISTINCT Users.Email FROM Users ' + + 'INNER JOIN PackageBases ' + + 'ON PackageBases.MaintainerUID = Users.ID WHERE ' + + 'Users.ID = %s OR PackageBases.ID = %s', [uid, pkgbase_id]) + return [row[0] for row in cur.fetchall()] + +def send_resetkey(cur, uid): + cur.execute('SELECT UserName, Email, ResetKey FROM Users WHERE ID = %s', + [uid]) + username, to, resetkey = cur.fetchone() + + subject = 'AUR Password Reset' + body = 'A password reset request was submitted for the account %s ' \ + 'associated with your e-mail address. If you wish to reset your ' \ + 'password follow the link below, otherwise ignore this message ' \ + 'and nothing will happen.' % (username) + body += '\n\n' + body += '[1] ' + aur_location + '/passreset/?resetkey=' + resetkey + + send_notification([to], subject, body) + +def welcome(cur, uid): + cur.execute('SELECT UserName, Email, ResetKey FROM Users WHERE ID = %s', + [uid]) + username, to, resetkey = cur.fetchone() + + subject = 'Welcome to the Arch User Repository' + body = 'Welcome to %s! In order to set an initial password for your new ' \ + 'account, please click the link below. If the link does not work ' \ + 'try copying and pasting it into your browser.' % (aur_location) + body += '\n\n' + body += '[1] ' + aur_location + '/passreset/?resetkey=' + resetkey + + send_notification([to], subject, body) + +def comment(cur, uid, pkgbase_id): + user = username_from_id(cur, uid) + pkgbase = pkgbase_from_id(cur, pkgbase_id) + to = get_recipients(cur, pkgbase_id, uid) + text = sys.stdin.read() + + uri = aur_location + '/pkgbase/' + pkgbase + '/' + + subject = 'AUR Comment for %s' % (pkgbase) + body = 'from %s\n%s wrote:\n\n%s\n\n--- \n' \ + 'If you no longer wish to receive notifications about this ' \ + 'package, please go the the above package page and click the ' \ + 'UnNotify button.' % (uri, user, text) + thread_id = '' + + send_notification(to, subject, body, reference=thread_id) + +def flag(cur, uid, pkgbase_id): + user = username_from_id(cur, uid) + pkgbase = pkgbase_from_id(cur, pkgbase_id) + to = [get_maintainer_email(cur, pkgbase_id)] + + user_uri = aur_location + '/account/' + user + '/' + pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/' + + subject = 'AUR Out-of-date Notification for %s' % (pkgbase) + body = 'Your package %s has been flagged out of date by %s [1]. ' \ + 'You may view your package at:\n%s\n\n[1] %s' % \ + (pkgbase, user, pkgbase_uri, user_uri) + + send_notification(to, subject, body) + +def delete(cur, uid, old_pkgbase_id, new_pkgbase_id=None): + user = username_from_id(cur, uid) + old_pkgbase = pkgbase_from_id(cur, old_pkgbase_id) + if new_pkgbase_id: + new_pkgbase = pkgbase_from_id(cur, new_pkgbase_id) + to = get_recipients(cur, old_pkgbase_id, uid) + + subject = 'AUR Package deleted: %s' % (old_pkgbase) + if new_pkgbase_id: + new_pkgbase_uri = aur_location + '/pkgbase/' + new_pkgbase + '/' + body = '%s merged "%s" into "%s".\n\nYou will no longer receive ' \ + 'notifications about this package, please go to %s and click ' \ + 'the Notify button if you wish to recieve them again.' % \ + (user, old_pkgbase, new_pkgbase, new_pkgbase_uri) + else: + body = '%s deleted "%s".\n\nYou will no longer receive ' \ + 'notifications about this package.' % (user, old_pkgbase) + + send_notification(to, subject, body) + +def request_open(cur, uid, reqid, reqtype, pkgbase_id, merge_into=None): + user = username_from_id(cur, uid) + pkgbase = pkgbase_from_id(cur, pkgbase_id) + to = [aur_request_ml] + cc = get_request_recipients(cur, pkgbase_id, uid) + text = sys.stdin.read() + + user_uri = aur_location + '/account/' + user + '/' + pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/' + + subject = '[PRQ#%d] %s Request for %s' % \ + (int(reqid), reqtype.title(), pkgbase) + if merge_into: + merge_into_uri = aur_location + '/pkgbase/' + merge_into + '/' + body = '%s [1] filed a request to merge %s [2] into %s [3]:\n\n' \ + '%s\n\n[1] %s\n[2] %s\n[3] %s\n' % \ + (user, pkgbase, merge_into, text, user_uri, pkgbase_uri, + merge_into_uri) + else: + body = '%s [1] filed a %s request for %s [2]:\n\n' \ + '%s\n\n[1] %s\n[2] %s\n' % \ + (user, reqtype, pkgbase, text, user_uri, pkgbase_uri) + thread_id = '' + + send_notification(to, subject, body, cc, thread_id) + +def request_close(cur, uid, reqid, reason): + user = username_from_id(cur, uid) + pkgbase_id = pkgbase_from_pkgreq(cur, reqid) + to = [aur_request_ml] + cc = get_request_recipients(cur, pkgbase_id, uid) + text = sys.stdin.read() + + user_uri = aur_location + '/account/' + user + '/' + + subject = '[PRQ#%d] Request %s' % (int(reqid), reason.title()) + body = 'Request #%d has been %s by %s [1]' % (int(reqid), reason, user) + if text.strip() == '': + body += '.\n\n' + else: + body += ':\n\n' + text + '\n\n' + body += '[1] ' + user_uri + thread_id = '' + + send_notification(to, subject, body, cc, thread_id) + + +if __name__ == '__main__': + action = sys.argv[1] + action_map = { + 'send-resetkey': send_resetkey, + 'welcome': welcome, + 'comment': comment, + 'flag': flag, + 'delete': delete, + 'request-open': request_open, + 'request-close': request_close, + } + + db = mysql.connector.connect(host=aur_db_host, user=aur_db_user, + passwd=aur_db_pass, db=aur_db_name, + unix_socket=aur_db_socket, buffered=True) + cur = db.cursor() + + action_map[action](cur, *sys.argv[2:]) + + db.commit() + db.close() -- cgit v1.2.3-70-g09d2