[PATCH 3/6] Refactor the notification script

Lukas Fleischer lfleischer at archlinux.org
Thu May 17 21:01:56 UTC 2018


Reimplement most of the notification script logic. Create a separate
class for each notification type. Each class provides methods for
generating the list of recipients, the message subject, the message
body, the references to add at the end of the message and the message
headers. Additionally, a method for sending notification emails is
provided.

One major benefit of the new implementation is that both the generation
of recipients and message contents are much more flexible. For example,
it is now easily possible to make user-specific adjustments to every
single notification of a batch.

Signed-off-by: Lukas Fleischer <lfleischer at archlinux.org>
---
 aurweb/scripts/notify.py | 822 ++++++++++++++++++++-------------------
 1 file changed, 429 insertions(+), 393 deletions(-)

diff --git a/aurweb/scripts/notify.py b/aurweb/scripts/notify.py
index 4fed6ad..a637013 100755
--- a/aurweb/scripts/notify.py
+++ b/aurweb/scripts/notify.py
@@ -9,11 +9,6 @@ import aurweb.config
 import aurweb.db
 
 aur_location = aurweb.config.get('options', 'aur_location')
-aur_request_ml = aurweb.config.get('options', 'aur_request_ml')
-
-sendmail = aurweb.config.get('notifications', 'sendmail')
-sender = aurweb.config.get('notifications', 'sender')
-reply_to = aurweb.config.get('notifications', 'reply-to')
 
 
 def headers_cc(cclist):
@@ -28,29 +23,6 @@ def headers_reply(thread_id):
     return {'In-Reply-To': thread_id, 'References': thread_id}
 
 
-def send_notification(to, subject, body, refs, headers={}):
-    wrapped = ''
-    for line in body.splitlines():
-        wrapped += textwrap.fill(line, break_long_words=False) + '\n'
-    if refs:
-        body = wrapped + '\n' + refs
-    else:
-        body = wrapped.rstrip()
-
-    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
-
-        for key, value in headers.items():
-            msg[key] = value
-
-        p = subprocess.Popen([sendmail, '-t', '-oi'], stdin=subprocess.PIPE)
-        p.communicate(msg.as_bytes())
-
-
 def username_from_id(conn, uid):
     cur = conn.execute('SELECT UserName FROM Users WHERE ID = ?', [uid])
     return cur.fetchone()[0]
@@ -73,380 +45,444 @@ def get_user_email(conn, uid):
     return cur.fetchone()[0]
 
 
-def get_flag_recipients(conn, pkgbase_id):
-    cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' +
-                       'LEFT JOIN PackageComaintainers ' +
-                       'ON PackageComaintainers.UsersID = Users.ID ' +
-                       'INNER JOIN PackageBases ' +
-                       'ON PackageBases.MaintainerUID = Users.ID OR ' +
-                       'PackageBases.ID = PackageComaintainers.PackageBaseID ' +
-                       'WHERE PackageBases.ID = ?', [pkgbase_id])
-    return [row[0] for row in cur.fetchall()]
-
-
-def get_recipients(conn, pkgbase_id, uid):
-    cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' +
-                       'INNER JOIN PackageNotifications ' +
-                       'ON PackageNotifications.UserID = Users.ID WHERE ' +
-                       'PackageNotifications.UserID != ? AND ' +
-                       'PackageNotifications.PackageBaseID = ?',
-                       [uid, pkgbase_id])
-    return [row[0] for row in cur.fetchall()]
-
-
-def get_comment_recipients(conn, pkgbase_id, uid):
-    cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' +
-                       'INNER JOIN PackageNotifications ' +
-                       'ON PackageNotifications.UserID = Users.ID WHERE ' +
-                       'Users.CommentNotify = 1 AND ' +
-                       'PackageNotifications.UserID != ? AND ' +
-                       'PackageNotifications.PackageBaseID = ?',
-                       [uid, pkgbase_id])
-    return [row[0] for row in cur.fetchall()]
-
-
-def get_update_recipients(conn, pkgbase_id, uid):
-    cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' +
-                       'INNER JOIN PackageNotifications ' +
-                       'ON PackageNotifications.UserID = Users.ID WHERE ' +
-                       'Users.UpdateNotify = 1 AND ' +
-                       'PackageNotifications.UserID != ? AND ' +
-                       'PackageNotifications.PackageBaseID = ?',
-                       [uid, pkgbase_id])
-    return [row[0] for row in cur.fetchall()]
-
-
-def get_ownership_recipients(conn, pkgbase_id, uid):
-    cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' +
-                       'INNER JOIN PackageNotifications ' +
-                       'ON PackageNotifications.UserID = Users.ID WHERE ' +
-                       'Users.OwnershipNotify = 1 AND ' +
-                       'PackageNotifications.UserID != ? AND ' +
-                       'PackageNotifications.PackageBaseID = ?',
-                       [uid, pkgbase_id])
-    return [row[0] for row in cur.fetchall()]
-
-
-def get_request_recipients(conn, reqid):
-    cur = conn.execute('SELECT DISTINCT Users.Email FROM PackageRequests ' +
-                       'INNER JOIN PackageBases ' +
-                       'ON PackageBases.ID = PackageRequests.PackageBaseID ' +
-                       'INNER JOIN Users ' +
-                       'ON Users.ID = PackageRequests.UsersID ' +
-                       'OR Users.ID = PackageBases.MaintainerUID ' +
-                       'WHERE PackageRequests.ID = ?', [reqid])
-    return [row[0] for row in cur.fetchall()]
-
-
-def get_tu_vote_reminder_recipients(conn, vote_id):
-    cur = conn.execute('SELECT Email FROM Users ' +
-                       'WHERE AccountTypeID IN (2, 4) AND ID NOT IN ' +
-                       '(SELECT UserID FROM TU_Votes ' +
-                       'WHERE TU_Votes.VoteID = ?)', [vote_id])
-    return [row[0] for row in cur.fetchall()]
-
-
-def get_comment(conn, comment_id):
-    cur = conn.execute('SELECT Comments FROM PackageComments WHERE ID = ?',
-                       [comment_id])
-    return cur.fetchone()[0]
-
-
-def get_flagger_comment(conn, pkgbase_id):
-    cur = conn.execute('SELECT FlaggerComment FROM PackageBases WHERE ID = ?',
-                       [pkgbase_id])
-    return cur.fetchone()[0]
-
-
-def get_request_comment(conn, reqid):
-    cur = conn.execute('SELECT Comments FROM PackageRequests WHERE ID = ?',
-                       [reqid])
-    return cur.fetchone()[0]
-
-
-def get_request_closure_comment(conn, reqid):
-    cur = conn.execute('SELECT ClosureComment FROM PackageRequests ' +
-                       'WHERE ID = ?', [reqid])
-    return cur.fetchone()[0]
-
-
-def send_resetkey(conn, uid):
-    cur = conn.execute('SELECT UserName, Email, ResetKey FROM Users ' +
-                       'WHERE ID = ?', [uid])
-    username, to, resetkey = cur.fetchone()
-
-    subject = 'AUR Password Reset'
-    body = 'A password reset request was submitted for the account %s ' \
-           'associated with your email address. If you wish to reset your ' \
-           'password follow the link [1] below, otherwise ignore this ' \
-           'message and nothing will happen.' % (username)
-    refs = '[1] ' + aur_location + '/passreset/?resetkey=' + resetkey
-
-    send_notification([to], subject, body, refs)
-
-
-def welcome(conn, uid):
-    cur = conn.execute('SELECT UserName, Email, ResetKey FROM Users ' +
-                       'WHERE ID = ?', [uid])
-    username, to, resetkey = cur.fetchone()
-
-    subject = 'Welcome to the Arch User Repository'
-    body = 'Welcome to the Arch User Repository! In order to set an initial ' \
-           'password for your new account, please click the link [1] below. ' \
-           'If the link does not work, try copying and pasting it into your ' \
-           'browser.'
-    refs = '[1] ' + aur_location + '/passreset/?resetkey=' + resetkey
-
-    send_notification([to], subject, body, refs)
-
-
-def comment(conn, uid, pkgbase_id, comment_id):
-    user = username_from_id(conn, uid)
-    pkgbase = pkgbase_from_id(conn, pkgbase_id)
-    to = get_comment_recipients(conn, pkgbase_id, uid)
-    text = get_comment(conn, comment_id)
-
-    user_uri = aur_location + '/account/' + user + '/'
-    pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/'
-
-    subject = 'AUR Comment for %s' % (pkgbase)
-    body = '%s [1] added the following comment to %s [2]:' % (user, pkgbase)
-    body += '\n\n' + text + '\n\n'
-    body += 'If you no longer wish to receive notifications about this ' \
-            'package, please go to the package page [2] and select "%s".' % \
-            ('Disable notifications')
-    refs = '[1] ' + user_uri + '\n'
-    refs += '[2] ' + pkgbase_uri
-    thread_id = '<pkg-notifications-' + pkgbase + '@aur.archlinux.org>'
-    headers = headers_reply(thread_id)
-
-    send_notification(to, subject, body, refs, headers)
-
-
-def update(conn, uid, pkgbase_id):
-    user = username_from_id(conn, uid)
-    pkgbase = pkgbase_from_id(conn, pkgbase_id)
-    to = get_update_recipients(conn, pkgbase_id, uid)
-
-    user_uri = aur_location + '/account/' + user + '/'
-    pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/'
-
-    subject = 'AUR Package Update: %s' % (pkgbase)
-    body = '%s [1] pushed a new commit to %s [2].' % (user, pkgbase)
-    body += '\n\n'
-    body += 'If you no longer wish to receive notifications about this ' \
-            'package, please go to the package page [2] and select "%s".' % \
-            ('Disable notifications')
-    refs = '[1] ' + user_uri + '\n'
-    refs += '[2] ' + pkgbase_uri
-    thread_id = '<pkg-notifications-' + pkgbase + '@aur.archlinux.org>'
-    headers = headers_reply(thread_id)
-
-    send_notification(to, subject, body, refs, headers)
-
-
-def flag(conn, uid, pkgbase_id):
-    user = username_from_id(conn, uid)
-    pkgbase = pkgbase_from_id(conn, pkgbase_id)
-    to = get_flag_recipients(conn, pkgbase_id)
-    text = get_flagger_comment(conn, 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 [1] has been flagged out-of-date by %s [2]:' % \
-           (pkgbase, user)
-    body += '\n\n' + text
-    refs = '[1] ' + pkgbase_uri + '\n'
-    refs += '[2] ' + user_uri
-
-    send_notification(to, subject, body, refs)
-
-
-def adopt(conn, pkgbase_id, uid):
-    user = username_from_id(conn, uid)
-    pkgbase = pkgbase_from_id(conn, pkgbase_id)
-    to = get_ownership_recipients(conn, pkgbase_id, uid)
-
-    user_uri = aur_location + '/account/' + user + '/'
-    pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/'
-
-    subject = 'AUR Ownership Notification for %s' % (pkgbase)
-    body = 'The package %s [1] was adopted by %s [2].' % (pkgbase, user)
-    refs = '[1] ' + pkgbase_uri + '\n'
-    refs += '[2] ' + user_uri
-
-    send_notification(to, subject, body, refs)
-
-
-def disown(conn, pkgbase_id, uid):
-    user = username_from_id(conn, uid)
-    pkgbase = pkgbase_from_id(conn, pkgbase_id)
-    to = get_ownership_recipients(conn, pkgbase_id, uid)
-
-    user_uri = aur_location + '/account/' + user + '/'
-    pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/'
-
-    subject = 'AUR Ownership Notification for %s' % (pkgbase)
-    body = 'The package %s [1] was disowned by %s [2].' % (pkgbase, user)
-    refs = '[1] ' + pkgbase_uri + '\n'
-    refs += '[2] ' + user_uri
-
-    send_notification(to, subject, body, refs)
-
-
-def comaintainer_add(conn, pkgbase_id, uid):
-    pkgbase = pkgbase_from_id(conn, pkgbase_id)
-    to = [get_user_email(conn, uid)]
-
-    pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/'
-
-    subject = 'AUR Co-Maintainer Notification for %s' % (pkgbase)
-    body = 'You were added to the co-maintainer list of %s [1].' % (pkgbase)
-    refs = '[1] ' + pkgbase_uri
-
-    send_notification(to, subject, body, refs)
-
-
-def comaintainer_remove(conn, pkgbase_id, uid):
-    pkgbase = pkgbase_from_id(conn, pkgbase_id)
-    to = [get_user_email(conn, uid)]
-
-    pkgbase_uri = aur_location + '/pkgbase/' + pkgbase + '/'
-
-    subject = 'AUR Co-Maintainer Notification for %s' % (pkgbase)
-    body = ('You were removed from the co-maintainer list of %s [1].' %
-            (pkgbase))
-    refs = '[1] ' + pkgbase_uri
-
-    send_notification(to, subject, body, refs)
-
-
-def delete(conn, uid, old_pkgbase_id, new_pkgbase_id=None):
-    user = username_from_id(conn, uid)
-    old_pkgbase = pkgbase_from_id(conn, old_pkgbase_id)
-    if new_pkgbase_id:
-        new_pkgbase = pkgbase_from_id(conn, new_pkgbase_id)
-    to = get_recipients(conn, old_pkgbase_id, uid)
-
-    user_uri = aur_location + '/account/' + user + '/'
-    pkgbase_uri = aur_location + '/pkgbase/' + old_pkgbase + '/'
-
-    subject = 'AUR Package deleted: %s' % (old_pkgbase)
-    if new_pkgbase_id:
-        new_pkgbase_uri = aur_location + '/pkgbase/' + new_pkgbase + '/'
-        body = '%s [1] merged %s [2] into %s [3].\n\n' \
-               'If you no longer wish receive notifications about the new ' \
-               'package, please go to [3] and click "%s".' %\
-               (user, old_pkgbase, new_pkgbase, 'Disable notifications')
-        refs = '[1] ' + user_uri + '\n'
-        refs += '[2] ' + pkgbase_uri + '\n'
-        refs += '[3] ' + new_pkgbase_uri
-    else:
-        body = '%s [1] deleted %s [2].\n\n' \
-               'You will no longer receive notifications about this ' \
-               'package.' % (user, old_pkgbase)
-        refs = '[1] ' + user_uri + '\n'
-        refs += '[2] ' + pkgbase_uri
-
-    send_notification(to, subject, body, refs)
-
-
-def request_open(conn, uid, reqid, reqtype, pkgbase_id, merge_into=None):
-    user = username_from_id(conn, uid)
-    pkgbase = pkgbase_from_id(conn, pkgbase_id)
-    to = [aur_request_ml]
-    cc = get_request_recipients(conn, reqid)
-    text = get_request_comment(conn, reqid)
-
-    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]:' % \
-               (user, pkgbase, merge_into)
-        body += '\n\n' + text
-        refs = '[1] ' + user_uri + '\n'
-        refs += '[2] ' + pkgbase_uri + '\n'
-        refs += '[3] ' + merge_into_uri
-    else:
-        body = '%s [1] filed a %s request for %s [2]:' % \
-               (user, reqtype, pkgbase)
-        body += '\n\n' + text
-        refs = '[1] ' + user_uri + '\n'
-        refs += '[2] ' + pkgbase_uri
-    thread_id = '<pkg-request-' + reqid + '@aur.archlinux.org>'
-    # Use a deterministic Message-ID for the first email referencing a request.
-    headers = headers_msgid(thread_id)
-    headers.update(headers_cc(cc))
-
-    send_notification(to, subject, body, refs, headers)
-
-
-def request_close(conn, uid, reqid, reason):
-    to = [aur_request_ml]
-    cc = get_request_recipients(conn, reqid)
-    text = get_request_closure_comment(conn, reqid)
-
-    subject = '[PRQ#%d] Request %s' % (int(reqid), reason.title())
-    if int(uid):
-        user = username_from_id(conn, uid)
-        user_uri = aur_location + '/account/' + user + '/'
-        body = 'Request #%d has been %s by %s [1]' % (int(reqid), reason, user)
-        refs = '[1] ' + user_uri
-    else:
-        body = 'Request #%d has been %s automatically by the Arch User ' \
-               'Repository package request system' % (int(reqid), reason)
-        refs = None
-    if text.strip() == '':
-        body += '.'
-    else:
-        body += ':\n\n' + text
-    thread_id = '<pkg-request-' + reqid + '@aur.archlinux.org>'
-    headers = headers_reply(thread_id)
-    headers.update(headers_cc(cc))
-
-    send_notification(to, subject, body, refs, headers)
-
-
-def tu_vote_reminder(conn, vote_id):
-    to = get_tu_vote_reminder_recipients(conn, vote_id)
-
-    vote_uri = aur_location + '/tu/?id=' + vote_id
-
-    subject = 'TU Vote Reminder: Proposal %d' % (int(vote_id))
-    body = 'Please remember to cast your vote on proposal %d [1]. ' \
-           'The voting period ends in less than 48 hours.' % (int(vote_id))
-    refs = '[1] ' + vote_uri
-
-    send_notification(to, subject, body, refs)
+class Notification:
+    def get_refs(self):
+        return ()
+
+    def get_headers(self):
+        return {}
+
+    def send(self):
+        body = ''
+        for line in self.get_body().splitlines():
+            body += textwrap.fill(line, break_long_words=False) + '\n'
+        for i, ref in enumerate(self.get_refs()):
+            body += '\n' + '[%d] %s' % (i + 1, ref)
+        body = body.rstrip()
+
+        sendmail = aurweb.config.get('notifications', 'sendmail')
+        sender = aurweb.config.get('notifications', 'sender')
+        reply_to = aurweb.config.get('notifications', 'reply-to')
+
+        for recipient in self.get_recipients():
+            msg = email.mime.text.MIMEText(body, 'plain', 'utf-8')
+            msg['Subject'] = self.get_subject()
+            msg['From'] = sender
+            msg['Reply-to'] = reply_to
+            msg['To'] = recipient
+
+            for key, value in self.get_headers().items():
+                msg[key] = value
+
+            p = subprocess.Popen([sendmail, '-t', '-oi'],
+                                 stdin=subprocess.PIPE)
+            p.communicate(msg.as_bytes())
+
+
+class ResetKeyNotification(Notification):
+    def __init__(self, conn, uid):
+        cur = conn.execute('SELECT UserName, Email, ResetKey FROM Users ' +
+                           'WHERE ID = ?', [uid])
+        self._username, self._to, self._resetkey = cur.fetchone()
+
+    def get_recipients(self):
+        return [self._to]
+
+    def get_subject(self):
+        return 'AUR Password Reset'
+
+    def get_body(self):
+        return 'A password reset request was submitted for the account %s ' \
+               'associated with your email address. If you wish to reset ' \
+               'your password follow the link [1] below, otherwise ignore ' \
+               'this message and nothing will happen.' % (self._username)
+
+    def get_refs(self):
+        return (aur_location + '/passreset/?resetkey=' + self._resetkey,)
+
+
+class WelcomeNotification(ResetKeyNotification):
+    def get_subject(self):
+        return 'Welcome to the Arch User Repository'
+
+    def get_body(self):
+        return 'Welcome to the Arch User Repository! In order to set an ' \
+               'initial password for your new account, please click the ' \
+               'link [1] below. If the link does not work, try copying and ' \
+               'pasting it into your browser.'
+
+
+class CommentNotification(Notification):
+    def __init__(self, conn, uid, pkgbase_id, comment_id):
+        self._user = username_from_id(conn, uid)
+        self._pkgbase = pkgbase_from_id(conn, pkgbase_id)
+        cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' +
+                           'INNER JOIN PackageNotifications ' +
+                           'ON PackageNotifications.UserID = Users.ID WHERE ' +
+                           'Users.CommentNotify = 1 AND ' +
+                           'PackageNotifications.UserID != ? AND ' +
+                           'PackageNotifications.PackageBaseID = ?',
+                           [uid, pkgbase_id])
+        self._to = [row[0] for row in cur.fetchall()]
+        cur = conn.execute('SELECT Comments FROM PackageComments WHERE ID = ?',
+                           [comment_id])
+        self._text = cur.fetchone()[0]
+
+    def get_recipients(self):
+        return self._to
+
+    def get_subject(self):
+        return 'AUR Comment for %s' % (self._pkgbase)
+
+    def get_body(self):
+        body = '%s [1] added the following comment to %s [2]:' % \
+               (self._user, self._pkgbase)
+        body += '\n\n' + self._text + '\n\n'
+        body += 'If you no longer wish to receive notifications about this ' \
+                'package, please go to the package page [2] and select ' \
+                '"%s".' % ('Disable notifications')
+        return body
+
+    def get_refs(self):
+        return (aur_location + '/account/' + self._user + '/',
+                aur_location + '/pkgbase/' + self._pkgbase + '/')
+
+    def get_headers(self):
+        thread_id = '<pkg-notifications-' + self._pkgbase + \
+                    '@aur.archlinux.org>'
+        return headers_reply(thread_id)
+
+
+class UpdateNotification(Notification):
+    def __init__(self, conn, uid, pkgbase_id):
+        self._user = username_from_id(conn, uid)
+        self._pkgbase = pkgbase_from_id(conn, pkgbase_id)
+        cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' +
+                           'INNER JOIN PackageNotifications ' +
+                           'ON PackageNotifications.UserID = Users.ID WHERE ' +
+                           'Users.UpdateNotify = 1 AND ' +
+                           'PackageNotifications.UserID != ? AND ' +
+                           'PackageNotifications.PackageBaseID = ?',
+                           [uid, pkgbase_id])
+        self._to = [row[0] for row in cur.fetchall()]
+
+    def get_recipients(self):
+        return self._to
+
+    def get_subject(self):
+        return 'AUR Package Update: %s' % (self._pkgbase)
+
+    def get_body(self):
+        body = '%s [1] pushed a new commit to %s [2].' % \
+               (self._user, self._pkgbase)
+        body += '\n\n'
+        body += 'If you no longer wish to receive notifications about this ' \
+                'package, please go to the package page [2] and select ' \
+                '"%s".' % ('Disable notifications')
+        return body
+
+    def get_refs(self):
+        return (aur_location + '/account/' + self._user + '/',
+                aur_location + '/pkgbase/' + self._pkgbase + '/')
+
+    def get_headers(self):
+        thread_id = '<pkg-notifications-' + self._pkgbase + \
+                    '@aur.archlinux.org>'
+        return headers_reply(thread_id)
+
+
+class FlagNotification(Notification):
+    def __init__(self, conn, uid, pkgbase_id):
+        self._user = username_from_id(conn, uid)
+        self._pkgbase = pkgbase_from_id(conn, pkgbase_id)
+        cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' +
+                           'LEFT JOIN PackageComaintainers ' +
+                           'ON PackageComaintainers.UsersID = Users.ID ' +
+                           'INNER JOIN PackageBases ' +
+                           'ON PackageBases.MaintainerUID = Users.ID OR ' +
+                           'PackageBases.ID = PackageComaintainers.PackageBaseID ' +
+                           'WHERE PackageBases.ID = ?', [pkgbase_id])
+        self._to = [row[0] for row in cur.fetchall()]
+        cur = conn.execute('SELECT FlaggerComment FROM PackageBases WHERE ' +
+                           'ID = ?', [pkgbase_id])
+        self._text = cur.fetchone()[0]
+
+    def get_recipients(self):
+        return self._to
+
+    def get_subject(self):
+        return 'AUR Out-of-date Notification for %s' % (self._pkgbase)
+
+    def get_body(self):
+        body = 'Your package %s [1] has been flagged out-of-date by ' \
+               '%s [2]:' % (self._pkgbase, self._user)
+        body += '\n\n' + self._text
+        return body
+
+    def get_refs(self):
+        return (aur_location + '/pkgbase/' + self._pkgbase + '/',
+                aur_location + '/account/' + self._user + '/')
+
+
+class OwnershipEventNotification(Notification):
+    def __init__(self, conn, uid, pkgbase_id):
+        self._user = username_from_id(conn, uid)
+        self._pkgbase = pkgbase_from_id(conn, pkgbase_id)
+        cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' +
+                           'INNER JOIN PackageNotifications ' +
+                           'ON PackageNotifications.UserID = Users.ID WHERE ' +
+                           'Users.OwnershipNotify = 1 AND ' +
+                           'PackageNotifications.UserID != ? AND ' +
+                           'PackageNotifications.PackageBaseID = ?',
+                           [uid, pkgbase_id])
+        self._to = [row[0] for row in cur.fetchall()]
+        cur = conn.execute('SELECT FlaggerComment FROM PackageBases WHERE ' +
+                           'ID = ?', [pkgbase_id])
+        self._text = cur.fetchone()[0]
+
+    def get_recipients(self):
+        return self._to
+
+    def get_subject(self):
+        return 'AUR Ownership Notification for %s' % (self._pkgbase)
+
+    def get_refs(self):
+        return (aur_location + '/pkgbase/' + self._pkgbase + '/',
+                aur_location + '/account/' + self._user + '/')
+
+
+class AdoptNotification(OwnershipEventNotification):
+    def get_body(self):
+        return 'The package %s [1] was adopted by %s [2].' % \
+               (self._pkgbase, self._user)
+
+
+class DisownNotification(OwnershipEventNotification):
+    def get_body(self):
+        return 'The package %s [1] was disowned by %s [2].' % \
+               (self._pkgbase, self._user)
+
+
+class ComaintainershipEventNotification(Notification):
+    def __init__(self, conn, uid, pkgbase_id):
+        self._pkgbase = pkgbase_from_id(conn, pkgbase_id)
+        self._to = get_user_email(conn, uid)
+
+    def get_recipients(self):
+        return [self._to]
+
+    def get_subject(self):
+        return 'AUR Co-Maintainer Notification for %s' % (self._pkgbase)
+
+    def get_refs(self):
+        return (aur_location + '/pkgbase/' + self._pkgbase + '/',)
+
+
+class ComaintainerAddNotification(ComaintainershipEventNotification):
+    def get_body(self):
+        return 'You were added to the co-maintainer list of %s [1].' % \
+               (self._pkgbase)
+
+
+class ComaintainerRemoveNotification(ComaintainershipEventNotification):
+    def get_body(self):
+        return 'You were removed from the co-maintainer list of %s [1].' % \
+               (self._pkgbase)
+
+
+class DeleteNotification(Notification):
+    def __init__(self, conn, uid, old_pkgbase_id, new_pkgbase_id=None):
+        self._user = username_from_id(conn, uid)
+        self._old_pkgbase = pkgbase_from_id(conn, old_pkgbase_id)
+        if new_pkgbase_id:
+            self._new_pkgbase = pkgbase_from_id(conn, new_pkgbase_id)
+        else:
+            self._new_pkgbase = None
+        cur = conn.execute('SELECT DISTINCT Users.Email FROM Users ' +
+                           'INNER JOIN PackageNotifications ' +
+                           'ON PackageNotifications.UserID = Users.ID WHERE ' +
+                           'PackageNotifications.UserID != ? AND ' +
+                           'PackageNotifications.PackageBaseID = ?',
+                           [uid, old_pkgbase_id])
+        self._to = [row[0] for row in cur.fetchall()]
+
+    def get_recipients(self):
+        return self._to
+
+    def get_subject(self):
+        return 'AUR Package deleted: %s' % (self._old_pkgbase)
+
+    def get_body(self):
+        if self._new_pkgbase:
+            return '%s [1] merged %s [2] into %s [3].\n\n' \
+                   'If you no longer wish receive notifications about the ' \
+                   'new package, please go to [3] and click "%s".' % \
+                   (self._user, self._old_pkgbase, self._new_pkgbase,
+                    'Disable notifications')
+        else:
+            return '%s [1] deleted %s [2].\n\n' \
+                   'You will no longer receive notifications about this ' \
+                   'package.' % (self._user, self._old_pkgbase)
+
+    def get_refs(self):
+        refs = (aur_location + '/account/' + self._user + '/',
+                aur_location + '/pkgbase/' + self._old_pkgbase + '/')
+        if self._new_pkgbase:
+            refs += (aur_location + '/pkgbase/' + self._new_pkgbase + '/',)
+        return refs
+
+
+class RequestOpenNotification(Notification):
+    def __init__(self, conn, uid, reqid, reqtype, pkgbase_id, merge_into=None):
+        self._user = username_from_id(conn, uid)
+        self._pkgbase = pkgbase_from_id(conn, pkgbase_id)
+        cur = conn.execute('SELECT DISTINCT Users.Email FROM PackageRequests ' +
+                           'INNER JOIN PackageBases ' +
+                           'ON PackageBases.ID = PackageRequests.PackageBaseID ' +
+                           'INNER JOIN Users ' +
+                           'ON Users.ID = PackageRequests.UsersID ' +
+                           'OR Users.ID = PackageBases.MaintainerUID ' +
+                           'WHERE PackageRequests.ID = ?', [reqid])
+        self._to = aurweb.config.get('options', 'aur_request_ml')
+        self._cc = [row[0] for row in cur.fetchall()]
+        cur = conn.execute('SELECT Comments FROM PackageRequests WHERE ID = ?',
+                           [reqid])
+        self._text = cur.fetchone()[0]
+        self._reqid = int(reqid)
+        self._reqtype = reqtype
+        self._merge_into = merge_into
+
+    def get_recipients(self):
+        return [self._to]
+
+    def get_subject(self):
+        return '[PRQ#%d] %s Request for %s' % \
+               (self._reqid, self._reqtype.title(), self._pkgbase)
+
+    def get_body(self):
+        if self._merge_into:
+            body = '%s [1] filed a request to merge %s [2] into %s [3]:' % \
+                   (self._user, self._pkgbase, self._merge_into)
+            body += '\n\n' + self._text
+        else:
+            body = '%s [1] filed a %s request for %s [2]:' % \
+                   (self._user, self._reqtype, self._pkgbase)
+            body += '\n\n' + self._text
+        return body
+
+    def get_refs(self):
+        refs = (aur_location + '/account/' + self._user + '/',
+                aur_location + '/pkgbase/' + self._pkgbase + '/')
+        if self._merge_into:
+            refs += (aur_location + '/pkgbase/' + self._merge_into + '/',)
+        return refs
+
+    def get_headers(self):
+        thread_id = '<pkg-request-' + str(self._reqid) + '@aur.archlinux.org>'
+        # Use a deterministic Message-ID for the first email referencing a
+        # request.
+        headers = headers_msgid(thread_id)
+        headers.update(headers_cc(self._cc))
+        return headers
+
+
+class RequestCloseNotification(Notification):
+    def __init__(self, conn, uid, reqid, reason):
+        self._user = username_from_id(conn, uid) if int(uid) else None
+        cur = conn.execute('SELECT DISTINCT Users.Email FROM PackageRequests ' +
+                           'INNER JOIN PackageBases ' +
+                           'ON PackageBases.ID = PackageRequests.PackageBaseID ' +
+                           'INNER JOIN Users ' +
+                           'ON Users.ID = PackageRequests.UsersID ' +
+                           'OR Users.ID = PackageBases.MaintainerUID ' +
+                           'WHERE PackageRequests.ID = ?', [reqid])
+        self._to = aurweb.config.get('options', 'aur_request_ml')
+        self._cc = [row[0] for row in cur.fetchall()]
+        cur = conn.execute('SELECT ClosureComment FROM PackageRequests ' +
+                           'WHERE ID = ?', [reqid])
+        self._text = cur.fetchone()[0]
+        self._reqid = int(reqid)
+        self._reason = reason
+
+    def get_recipients(self):
+        return [self._to]
+
+    def get_subject(self):
+        return '[PRQ#%d] Request %s' % (self._reqid, self._reason.title())
+
+    def get_body(self):
+        if self._user:
+            body = 'Request #%d has been %s by %s [1]' % \
+                   (self._reqid, self._reason, self._user)
+        else:
+            body = 'Request #%d has been %s automatically by the Arch User ' \
+                   'Repository package request system' % \
+                   (self._reqid, self._reason)
+        if self._text.strip() == '':
+            body += '.'
+        else:
+            body += ':\n\n' + self._text
+        return body
+
+    def get_refs(self):
+        if self._user:
+            return (aur_location + '/account/' + self._user + '/',)
+        else:
+            return ()
+
+    def get_headers(self):
+        thread_id = '<pkg-request-' + str(self._reqid) + '@aur.archlinux.org>'
+        headers = headers_reply(thread_id)
+        headers.update(headers_cc(self._cc))
+        return headers
+
+
+class TUVoteReminderNotification(Notification):
+    def __init__(self, conn, vote_id):
+        self._vote_id = int(vote_id)
+        cur = conn.execute('SELECT Email FROM Users ' +
+                           'WHERE AccountTypeID IN (2, 4) AND ID NOT IN ' +
+                           '(SELECT UserID FROM TU_Votes ' +
+                           'WHERE TU_Votes.VoteID = ?)', [vote_id])
+        self._to = [row[0] for row in cur.fetchall()]
+
+    def get_recipients(self):
+        return self._to
+
+    def get_subject(self):
+        return 'TU Vote Reminder: Proposal %d' % (self._vote_id)
+
+    def get_body(self):
+        return 'Please remember to cast your vote on proposal %d [1]. ' \
+               'The voting period ends in less than 48 hours.' % \
+               (self._vote_id)
+
+    def get_refs(self):
+        return (aur_location + '/tu/?id=' + str(self._vote_id),)
 
 
 def main():
     action = sys.argv[1]
     action_map = {
-        'send-resetkey': send_resetkey,
-        'welcome': welcome,
-        'comment': comment,
-        'update': update,
-        'flag': flag,
-        'adopt': adopt,
-        'disown': disown,
-        'comaintainer-add': comaintainer_add,
-        'comaintainer-remove': comaintainer_remove,
-        'delete': delete,
-        'request-open': request_open,
-        'request-close': request_close,
-        'tu-vote-reminder': tu_vote_reminder,
+        'send-resetkey': ResetKeyNotification,
+        'welcome': WelcomeNotification,
+        'comment': CommentNotification,
+        'update': UpdateNotification,
+        'flag': FlagNotification,
+        'adopt': AdoptNotification,
+        'disown': DisownNotification,
+        'comaintainer-add': ComaintainerAddNotification,
+        'comaintainer-remove': ComaintainerRemoveNotification,
+        'delete': DeleteNotification,
+        'request-open': RequestOpenNotification,
+        'request-close': RequestCloseNotification,
+        'tu-vote-reminder': TUVoteReminderNotification,
     }
 
     conn = aurweb.db.Connection()
 
-    action_map[action](conn, *sys.argv[2:])
+    notification = action_map[action](conn, *sys.argv[2:])
+    notification.send()
 
     conn.commit()
     conn.close()
-- 
2.17.0


More information about the aur-dev mailing list