aurweb/scripts/notify.py
Lukas Fleischer 60cdad28ee Distinguish auto-accepted requests
Before commit 9746a65 (Port notification routines to Python,
2015-06-27), notification emails for automatically closed requests
explicitly stated that the action was taken "automatically by the Arch
User Repository package request system". When porting the notification
routines to Python, this feature was overlooked and emails sent by the
new script always reported that the requester triggered the acceptance
or rejection of a request.

This patch reimplements the old behavior such that notifications no
longer look as if the requester had accepted the request himself.

Reported-by: Johannes Löthberg <johannes@kyriasis.com>
Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
2016-06-30 22:45:26 +02:00

433 lines
15 KiB
Python
Executable file

#!/usr/bin/python3
import configparser
import email.mime.text
import mysql.connector
import os
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 headers_cc(cclist):
return {'Cc': str.join(', ', cclist)}
def headers_msgid(thread_id):
return {'Message-ID': thread_id}
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
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(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_user_email(cur, uid):
cur.execute('SELECT Email FROM Users WHERE ID = %s', [uid])
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 PackageNotifications ' +
'ON PackageNotifications.UserID = Users.ID WHERE ' +
'PackageNotifications.UserID != %s AND ' +
'PackageNotifications.PackageBaseID = %s', [uid, pkgbase_id])
return [row[0] for row in cur.fetchall()]
def get_comment_recipients(cur, pkgbase_id, uid):
cur.execute('SELECT DISTINCT Users.Email FROM Users ' +
'INNER JOIN PackageNotifications ' +
'ON PackageNotifications.UserID = Users.ID WHERE ' +
'Users.CommentNotify = 1 AND ' +
'PackageNotifications.UserID != %s AND ' +
'PackageNotifications.PackageBaseID = %s', [uid, pkgbase_id])
return [row[0] for row in cur.fetchall()]
def get_update_recipients(cur, pkgbase_id, uid):
cur.execute('SELECT DISTINCT Users.Email FROM Users ' +
'INNER JOIN PackageNotifications ' +
'ON PackageNotifications.UserID = Users.ID WHERE ' +
'Users.UpdateNotify = 1 AND ' +
'PackageNotifications.UserID != %s AND ' +
'PackageNotifications.PackageBaseID = %s', [uid, pkgbase_id])
return [row[0] for row in cur.fetchall()]
def get_ownership_recipients(cur, pkgbase_id, uid):
cur.execute('SELECT DISTINCT Users.Email FROM Users ' +
'INNER JOIN PackageNotifications ' +
'ON PackageNotifications.UserID = Users.ID WHERE ' +
'Users.OwnershipNotify = 1 AND ' +
'PackageNotifications.UserID != %s AND ' +
'PackageNotifications.PackageBaseID = %s', [uid, pkgbase_id])
return [row[0] for row in cur.fetchall()]
def get_request_recipients(cur, reqid):
cur.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 = %s', [reqid])
return [row[0] for row in cur.fetchall()]
def get_comment(cur, comment_id):
cur.execute('SELECT Comments FROM PackageComments WHERE ID = %s',
[comment_id])
return cur.fetchone()[0]
def get_flagger_comment(cur, pkgbase_id):
cur.execute('SELECT FlaggerComment FROM PackageBases WHERE ID = %s',
[pkgbase_id])
return cur.fetchone()[0]
def get_request_comment(cur, reqid):
cur.execute('SELECT Comments FROM PackageRequests WHERE ID = %s', [reqid])
return cur.fetchone()[0]
def get_request_closure_comment(cur, reqid):
cur.execute('SELECT ClosureComment FROM PackageRequests WHERE ID = %s',
[reqid])
return cur.fetchone()[0]
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 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(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 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(cur, uid, pkgbase_id, comment_id):
user = username_from_id(cur, uid)
pkgbase = pkgbase_from_id(cur, pkgbase_id)
to = get_comment_recipients(cur, pkgbase_id, uid)
text = get_comment(cur, 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(cur, uid, pkgbase_id):
user = username_from_id(cur, uid)
pkgbase = pkgbase_from_id(cur, pkgbase_id)
to = get_update_recipients(cur, 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(cur, uid, pkgbase_id):
user = username_from_id(cur, uid)
pkgbase = pkgbase_from_id(cur, pkgbase_id)
to = [get_maintainer_email(cur, pkgbase_id)]
text = get_flagger_comment(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 [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(cur, pkgbase_id, uid):
user = username_from_id(cur, uid)
pkgbase = pkgbase_from_id(cur, pkgbase_id)
to = get_ownership_recipients(cur, 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(cur, pkgbase_id, uid):
user = username_from_id(cur, uid)
pkgbase = pkgbase_from_id(cur, pkgbase_id)
to = get_ownership_recipients(cur, 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(cur, pkgbase_id, uid):
pkgbase = pkgbase_from_id(cur, pkgbase_id)
to = [get_user_email(cur, 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 + '\n'
send_notification(to, subject, body, refs)
def comaintainer_remove(cur, pkgbase_id, uid):
pkgbase = pkgbase_from_id(cur, pkgbase_id)
to = [get_user_email(cur, 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 + '\n'
send_notification(to, subject, body, refs)
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)
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(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, reqid)
text = get_request_comment(cur, 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 + '\n'
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(cur, uid, reqid, reason):
to = [aur_request_ml]
cc = get_request_recipients(cur, reqid)
text = get_request_closure_comment(cur, reqid)
subject = '[PRQ#%d] Request %s' % (int(reqid), reason.title())
if int(uid):
user = username_from_id(cur, 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)
if __name__ == '__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,
}
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()