notify.py 25.8 KB
Newer Older
1
#!/usr/bin/env python3
2
3

import email.mime.text
4
5
import email.utils
import smtplib
6
7
8
9
import subprocess
import sys
import textwrap

10
11
from typing import List, Tuple

12
13
from sqlalchemy import and_, or_

Lukas Fleischer's avatar
Lukas Fleischer committed
14
15
import aurweb.config
import aurweb.db
16
import aurweb.filters
Lukas Fleischer's avatar
Lukas Fleischer committed
17
import aurweb.l10n
18

19
20
21
22
23
24
25
26
from aurweb import db, l10n, logging
from aurweb.models import PackageBase, User
from aurweb.models.package_comaintainer import PackageComaintainer
from aurweb.models.package_comment import PackageComment
from aurweb.models.package_notification import PackageNotification
from aurweb.models.package_request import PackageRequest
from aurweb.models.request_type import RequestType
from aurweb.models.tu_vote import TUVote
27

28
29
logger = logging.get_logger(__name__)

Lukas Fleischer's avatar
Lukas Fleischer committed
30
aur_location = aurweb.config.get('options', 'aur_location')
31
32


33
34
35
def headers_msgid(thread_id):
    return {'Message-ID': thread_id}

36

37
38
39
def headers_reply(thread_id):
    return {'In-Reply-To': thread_id, 'References': thread_id}

40

41
42
43
44
45
46
47
class Notification:
    def get_refs(self):
        return ()

    def get_headers(self):
        return {}

48
49
50
    def get_cc(self):
        return []

Lukas Fleischer's avatar
Lukas Fleischer committed
51
    def get_body_fmt(self, lang):
52
        body = ''
Lukas Fleischer's avatar
Lukas Fleischer committed
53
        for line in self.get_body(lang).splitlines():
54
55
            if line == '--':
                body += '--\n'
56
                continue
57
58
59
            body += textwrap.fill(line, break_long_words=False) + '\n'
        for i, ref in enumerate(self.get_refs()):
            body += '\n' + '[%d] %s' % (i + 1, ref)
Lukas Fleischer's avatar
Lukas Fleischer committed
60
        return body.rstrip()
61

62
    def _send(self) -> None:
63
        sendmail = aurweb.config.get('notifications', 'sendmail')
64
65
        sender = aurweb.config.get('notifications', 'sender')
        reply_to = aurweb.config.get('notifications', 'reply-to')
66
67
68
        reason = self.__class__.__name__
        if reason.endswith('Notification'):
            reason = reason[:-len('Notification')]
69
70

        for recipient in self.get_recipients():
Lukas Fleischer's avatar
Lukas Fleischer committed
71
72
73
74
            to, lang = recipient
            msg = email.mime.text.MIMEText(self.get_body_fmt(lang),
                                           'plain', 'utf-8')
            msg['Subject'] = self.get_subject(lang)
75
76
            msg['From'] = sender
            msg['Reply-to'] = reply_to
Lukas Fleischer's avatar
Lukas Fleischer committed
77
            msg['To'] = to
78
79
            if self.get_cc():
                msg['Cc'] = str.join(', ', self.get_cc())
80
            msg['X-AUR-Reason'] = reason
81
            msg['Date'] = email.utils.formatdate(localtime=True)
82
83
84
85

            for key, value in self.get_headers().items():
                msg[key] = value

86
87
88
89
90
91
92
93
94
95
            sendmail = aurweb.config.get('notifications', 'sendmail')
            if sendmail:
                # send email using the sendmail binary specified in the
                # configuration file
                p = subprocess.Popen([sendmail, '-t', '-oi'],
                                     stdin=subprocess.PIPE)
                p.communicate(msg.as_bytes())
            else:
                # send email using smtplib; no local MTA required
                server_addr = aurweb.config.get('notifications', 'smtp-server')
96
97
98
99
100
101
                server_port = aurweb.config.getint('notifications',
                                                   'smtp-port')
                use_ssl = aurweb.config.getboolean('notifications',
                                                   'smtp-use-ssl')
                use_starttls = aurweb.config.getboolean('notifications',
                                                        'smtp-use-starttls')
102
103
104
                user = aurweb.config.get('notifications', 'smtp-user')
                passwd = aurweb.config.get('notifications', 'smtp-password')

105
106
107
108
109
                classes = {
                    False: smtplib.SMTP,
                    True: smtplib.SMTP_SSL,
                }
                server = classes[use_ssl](server_addr, server_port)
110
111
112
113
114
115
116
117

                if use_starttls:
                    server.ehlo()
                    server.starttls()
                    server.ehlo()

                if user and passwd:
                    server.login(user, passwd)
118
119

                server.set_debuglevel(0)
120
                deliver_to = [to] + self.get_cc()
121
                server.sendmail(sender, deliver_to, msg.as_bytes())
122
                server.quit()
123

124
125
126
127
128
129
130
131
    def send(self) -> None:
        try:
            self._send()
        except OSError as exc:
            logger.error("Unable to emit notification due to an "
                         "OSError (precise exception following).")
            logger.error(str(exc))

132

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
class ServerErrorNotification(Notification):
    """ A notification used to represent an internal server error. """

    def __init__(self, traceback_id: int, version: str, utc: int):
        """
        Construct a ServerErrorNotification.

        :param traceback_id: Traceback ID
        :param version: aurweb version
        :param utc: UTC timestamp
        """
        self._tb_id = traceback_id
        self._version = version
        self._utc = utc

        postmaster = aurweb.config.get("notifications", "postmaster")
        self._to = postmaster

        super().__init__()

    def get_recipients(self) -> List[Tuple[str, str]]:
        from aurweb.auth import AnonymousUser
        user = (db.query(User).filter(User.Email == self._to).first()
                or AnonymousUser())
        return [(self._to, user.LangPreference)]

    def get_subject(self, lang: str) -> str:
        return l10n.translator.translate("AUR Server Error", lang)

    def get_body(self, lang: str) -> str:
        """ A forcibly English email body. """
164
        dt = aurweb.filters.timestamp_to_datetime(self._utc)
165
166
167
168
169
170
171
172
173
        dts = dt.strftime("%Y-%m-%d %H:%M")
        return (f"Traceback ID: {self._tb_id}\n"
                f"Location: {aur_location}\n"
                f"Version: {self._version}\n"
                f"Datetime: {dts} UTC\n")

    def get_refs(self):
        return (aur_location,)

174
175

class ResetKeyNotification(Notification):
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
    def __init__(self, uid):

        user = db.query(User).filter(
            and_(User.ID == uid, User.Suspended == 0)
        ).with_entities(
            User.Username,
            User.Email,
            User.BackupEmail,
            User.LangPreference,
            User.ResetKey
        ).order_by(User.Username.asc()).first()

        self._username = user.Username
        self._to = user.Email
        self._backup = user.BackupEmail
        self._lang = user.LangPreference
        self._resetkey = user.ResetKey

Lukas Fleischer's avatar
Lukas Fleischer committed
194
        super().__init__()
195
196

    def get_recipients(self):
197
198
199
200
        if self._backup:
            return [(self._to, self._lang), (self._backup, self._lang)]
        else:
            return [(self._to, self._lang)]
201

Lukas Fleischer's avatar
Lukas Fleischer committed
202
    def get_subject(self, lang):
203
        return aurweb.l10n.translator.translate('AUR Password Reset', lang)
204

Lukas Fleischer's avatar
Lukas Fleischer committed
205
    def get_body(self, lang):
206
207
208
209
210
211
        return aurweb.l10n.translator.translate(
            'A password reset request was submitted for the account '
            '{user} 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.',
            lang).format(user=self._username)
212
213
214
215
216
217

    def get_refs(self):
        return (aur_location + '/passreset/?resetkey=' + self._resetkey,)


class WelcomeNotification(ResetKeyNotification):
Lukas Fleischer's avatar
Lukas Fleischer committed
218
    def get_subject(self, lang):
219
220
221
        return aurweb.l10n.translator.translate(
            'Welcome to the Arch User Repository',
            lang)
222

Lukas Fleischer's avatar
Lukas Fleischer committed
223
    def get_body(self, lang):
224
225
226
227
228
        return aurweb.l10n.translator.translate(
            '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.', lang)
229
230
231


class CommentNotification(Notification):
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
    def __init__(self, uid, pkgbase_id, comment_id):

        self._user = db.query(User.Username).filter(
            User.ID == uid).first().Username
        self._pkgbase = db.query(PackageBase.Name).filter(
            PackageBase.ID == pkgbase_id).first().Name

        query = db.query(User).join(PackageNotification).filter(
            and_(User.CommentNotify == 1,
                 PackageNotification.UserID != uid,
                 PackageNotification.PackageBaseID == pkgbase_id,
                 User.Suspended == 0)
        ).with_entities(
            User.Email,
            User.LangPreference
        ).distinct()
        self._recipients = [(u.Email, u.LangPreference) for u in query]

        pkgcomment = db.query(PackageComment.Comments).filter(
            PackageComment.ID == comment_id).first()
        self._text = pkgcomment.Comments

Lukas Fleischer's avatar
Lukas Fleischer committed
254
        super().__init__()
255
256

    def get_recipients(self):
Lukas Fleischer's avatar
Lukas Fleischer committed
257
        return self._recipients
258

Lukas Fleischer's avatar
Lukas Fleischer committed
259
    def get_subject(self, lang):
260
261
262
        return aurweb.l10n.translator.translate(
            'AUR Comment for {pkgbase}',
            lang).format(pkgbase=self._pkgbase)
263

Lukas Fleischer's avatar
Lukas Fleischer committed
264
    def get_body(self, lang):
265
266
267
        body = aurweb.l10n.translator.translate(
            '{user} [1] added the following comment to {pkgbase} [2]:',
            lang).format(user=self._user, pkgbase=self._pkgbase)
268
        body += '\n\n' + self._text + '\n\n--\n'
269
270
271
272
273
274
        dnlabel = aurweb.l10n.translator.translate(
            'Disable notifications', lang)
        body += aurweb.l10n.translator.translate(
            'If you no longer wish to receive notifications about this '
            'package, please go to the package page [2] and select '
            '"{label}".', lang).format(label=dnlabel)
275
276
277
278
279
280
281
282
283
284
285
286
287
        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):
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
    def __init__(self, uid, pkgbase_id):

        self._user = db.query(User.Username).filter(
            User.ID == uid).first().Username
        self._pkgbase = db.query(PackageBase.Name).filter(
            PackageBase.ID == pkgbase_id).first().Name

        query = db.query(User).join(PackageNotification).filter(
            and_(User.UpdateNotify == 1,
                 PackageNotification.UserID != uid,
                 PackageNotification.PackageBaseID == pkgbase_id,
                 User.Suspended == 0)
        ).with_entities(
            User.Email,
            User.LangPreference
        ).distinct()
        self._recipients = [(u.Email, u.LangPreference) for u in query]

Lukas Fleischer's avatar
Lukas Fleischer committed
306
        super().__init__()
307
308

    def get_recipients(self):
Lukas Fleischer's avatar
Lukas Fleischer committed
309
        return self._recipients
310

Lukas Fleischer's avatar
Lukas Fleischer committed
311
    def get_subject(self, lang):
312
313
314
        return aurweb.l10n.translator.translate(
            'AUR Package Update: {pkgbase}',
            lang).format(pkgbase=self._pkgbase)
315

Lukas Fleischer's avatar
Lukas Fleischer committed
316
    def get_body(self, lang):
317
318
319
        body = aurweb.l10n.translator.translate(
            '{user} [1] pushed a new commit to {pkgbase} [2].',
            lang).format(user=self._user, pkgbase=self._pkgbase)
320
        body += '\n\n--\n'
321
322
323
324
325
326
        dnlabel = aurweb.l10n.translator.translate(
            'Disable notifications', lang)
        body += aurweb.l10n.translator.translate(
            'If you no longer wish to receive notifications about this '
            'package, please go to the package page [2] and select '
            '"{label}".', lang).format(label=dnlabel)
327
328
329
330
331
332
333
334
335
336
337
338
339
        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):
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
    def __init__(self, uid, pkgbase_id):

        self._user = db.query(User.Username).filter(
            User.ID == uid).first().Username
        self._pkgbase = db.query(PackageBase.Name).filter(
            PackageBase.ID == pkgbase_id).first().Name

        query = db.query(User).join(PackageComaintainer, isouter=True).join(
            PackageBase,
            or_(PackageBase.MaintainerUID == User.ID,
                PackageBase.ID == PackageComaintainer.PackageBaseID)
        ).filter(
            and_(PackageBase.ID == pkgbase_id,
                 User.Suspended == 0)
        ).with_entities(
            User.Email,
            User.LangPreference
        ).distinct()
        self._recipients = [(u.Email, u.LangPreference) for u in query]

        pkgbase = db.query(PackageBase.FlaggerComment).filter(
            PackageBase.ID == pkgbase_id).first()
        self._text = pkgbase.FlaggerComment

Lukas Fleischer's avatar
Lukas Fleischer committed
364
        super().__init__()
365
366

    def get_recipients(self):
Lukas Fleischer's avatar
Lukas Fleischer committed
367
        return self._recipients
368

Lukas Fleischer's avatar
Lukas Fleischer committed
369
    def get_subject(self, lang):
370
371
372
        return aurweb.l10n.translator.translate(
            'AUR Out-of-date Notification for {pkgbase}',
            lang).format(pkgbase=self._pkgbase)
373

Lukas Fleischer's avatar
Lukas Fleischer committed
374
    def get_body(self, lang):
375
376
377
378
        body = aurweb.l10n.translator.translate(
            'Your package {pkgbase} [1] has been flagged out-of-date by '
            '{user} [2]:', lang).format(pkgbase=self._pkgbase,
                                        user=self._user)
379
380
381
382
383
384
385
386
387
        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):
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
    def __init__(self, uid, pkgbase_id):

        self._user = db.query(User.Username).filter(
            User.ID == uid).first().Username
        self._pkgbase = db.query(PackageBase.Name).filter(
            PackageBase.ID == pkgbase_id).first().Name

        query = db.query(User).join(PackageNotification).filter(
            and_(User.OwnershipNotify == 1,
                 PackageNotification.UserID != uid,
                 PackageNotification.PackageBaseID == pkgbase_id,
                 User.Suspended == 0)
        ).with_entities(
            User.Email,
            User.LangPreference
        ).distinct()
        self._recipients = [(u.Email, u.LangPreference) for u in query]

        pkgbase = db.query(PackageBase.FlaggerComment).filter(
            PackageBase.ID == pkgbase_id).first()
        self._text = pkgbase.FlaggerComment

Lukas Fleischer's avatar
Lukas Fleischer committed
410
        super().__init__()
411
412

    def get_recipients(self):
Lukas Fleischer's avatar
Lukas Fleischer committed
413
        return self._recipients
414

Lukas Fleischer's avatar
Lukas Fleischer committed
415
    def get_subject(self, lang):
416
417
418
        return aurweb.l10n.translator.translate(
            'AUR Ownership Notification for {pkgbase}',
            lang).format(pkgbase=self._pkgbase)
419
420
421
422
423
424
425

    def get_refs(self):
        return (aur_location + '/pkgbase/' + self._pkgbase + '/',
                aur_location + '/account/' + self._user + '/')


class AdoptNotification(OwnershipEventNotification):
Lukas Fleischer's avatar
Lukas Fleischer committed
426
    def get_body(self, lang):
427
428
429
        return aurweb.l10n.translator.translate(
            'The package {pkgbase} [1] was adopted by {user} [2].',
            lang).format(pkgbase=self._pkgbase, user=self._user)
430
431
432


class DisownNotification(OwnershipEventNotification):
433
    def get_body(self, lang):
434
435
436
437
        return aurweb.l10n.translator.translate(
            'The package {pkgbase} [1] was disowned by {user} '
            '[2].', lang).format(pkgbase=self._pkgbase,
                                 user=self._user)
438
439
440


class ComaintainershipEventNotification(Notification):
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
    def __init__(self, uid, pkgbase_id):

        self._pkgbase = db.query(PackageBase.Name).filter(
            PackageBase.ID == pkgbase_id).first().Name

        user = db.query(User).filter(
            and_(User.ID == uid,
                 User.Suspended == 0)
        ).with_entities(
            User.Email,
            User.LangPreference
        ).first()

        self._to = user.Email
        self._lang = user.LangPreference

Lukas Fleischer's avatar
Lukas Fleischer committed
457
        super().__init__()
458
459

    def get_recipients(self):
Lukas Fleischer's avatar
Lukas Fleischer committed
460
        return [(self._to, self._lang)]
461

Lukas Fleischer's avatar
Lukas Fleischer committed
462
    def get_subject(self, lang):
463
464
465
        return aurweb.l10n.translator.translate(
            'AUR Co-Maintainer Notification for {pkgbase}',
            lang).format(pkgbase=self._pkgbase)
466
467
468
469
470
471

    def get_refs(self):
        return (aur_location + '/pkgbase/' + self._pkgbase + '/',)


class ComaintainerAddNotification(ComaintainershipEventNotification):
Lukas Fleischer's avatar
Lukas Fleischer committed
472
    def get_body(self, lang):
473
474
475
        return aurweb.l10n.translator.translate(
            'You were added to the co-maintainer list of {pkgbase} [1].',
            lang).format(pkgbase=self._pkgbase)
476
477
478


class ComaintainerRemoveNotification(ComaintainershipEventNotification):
Lukas Fleischer's avatar
Lukas Fleischer committed
479
    def get_body(self, lang):
480
481
482
        return aurweb.l10n.translator.translate(
            'You were removed from the co-maintainer list of {pkgbase} '
            '[1].', lang).format(pkgbase=self._pkgbase)
483
484
485


class DeleteNotification(Notification):
486
487
488
489
490
491
492
493
    def __init__(self, uid, old_pkgbase_id, new_pkgbase_id=None):

        self._user = db.query(User.Username).filter(
            User.ID == uid).first().Username
        self._old_pkgbase = db.query(PackageBase.Name).filter(
            PackageBase.ID == old_pkgbase_id).first().Name

        self._new_pkgbase = None
494
        if new_pkgbase_id:
495
496
497
498
499
500
501
502
503
504
505
506
507
            self._new_pkgbase = db.query(PackageBase.Name).filter(
                PackageBase.ID == new_pkgbase_id).first().Name

        query = db.query(User).join(PackageNotification).filter(
            and_(PackageNotification.UserID != uid,
                 PackageNotification.PackageBaseID == old_pkgbase_id,
                 User.Suspended == 0)
        ).with_entities(
            User.Email,
            User.LangPreference
        ).distinct()
        self._recipients = [(u.Email, u.LangPreference) for u in query]

Lukas Fleischer's avatar
Lukas Fleischer committed
508
        super().__init__()
509
510

    def get_recipients(self):
Lukas Fleischer's avatar
Lukas Fleischer committed
511
        return self._recipients
512

Lukas Fleischer's avatar
Lukas Fleischer committed
513
    def get_subject(self, lang):
514
515
516
        return aurweb.l10n.translator.translate(
            'AUR Package deleted: {pkgbase}',
            lang).format(pkgbase=self._old_pkgbase)
517

Lukas Fleischer's avatar
Lukas Fleischer committed
518
    def get_body(self, lang):
519
        if self._new_pkgbase:
520
521
522
523
            dnlabel = aurweb.l10n.translator.translate(
                'Disable notifications', lang)
            return aurweb.l10n.translator.translate(
                '{user} [1] merged {old} [2] into {new} [3].\n\n'
524
                '--\n'
525
526
527
528
                'If you no longer wish receive notifications about the '
                'new package, please go to [3] and click "{label}".',
                lang).format(user=self._user, old=self._old_pkgbase,
                             new=self._new_pkgbase, label=dnlabel)
529
        else:
530
531
532
533
534
            return aurweb.l10n.translator.translate(
                '{user} [1] deleted {pkgbase} [2].\n\n'
                'You will no longer receive notifications about this '
                'package.', lang).format(user=self._user,
                                         pkgbase=self._old_pkgbase)
535
536
537
538
539
540
541
542
543
544

    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):
545
546
547
548
549
550
551
    def __init__(self, uid, reqid, reqtype, pkgbase_id, merge_into=None):

        self._user = db.query(User.Username).filter(
            User.ID == uid).first().Username
        self._pkgbase = db.query(PackageBase.Name).filter(
            PackageBase.ID == pkgbase_id).first().Name

552
        self._to = aurweb.config.get('options', 'aur_request_ml')
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574

        query = db.query(PackageRequest).join(PackageBase).join(
            PackageComaintainer,
            PackageComaintainer.PackageBaseID == PackageRequest.PackageBaseID,
            isouter=True
        ).join(
            User,
            or_(User.ID == PackageRequest.UsersID,
                User.ID == PackageBase.MaintainerUID,
                User.ID == PackageComaintainer.UsersID)
        ).filter(
            and_(PackageRequest.ID == reqid,
                 User.Suspended == 0)
        ).with_entities(
            User.Email
        ).distinct()
        self._cc = [u.Email for u in query]

        pkgreq = db.query(PackageRequest.Comments).filter(
            PackageRequest.ID == reqid).first()

        self._text = pkgreq.Comments
575
576
577
578
579
        self._reqid = int(reqid)
        self._reqtype = reqtype
        self._merge_into = merge_into

    def get_recipients(self):
Lukas Fleischer's avatar
Lukas Fleischer committed
580
        return [(self._to, 'en')]
581

582
583
584
    def get_cc(self):
        return self._cc

Lukas Fleischer's avatar
Lukas Fleischer committed
585
    def get_subject(self, lang):
586
587
588
        return '[PRQ#%d] %s Request for %s' % \
               (self._reqid, self._reqtype.title(), self._pkgbase)

Lukas Fleischer's avatar
Lukas Fleischer committed
589
    def get_body(self, lang):
590
591
592
593
594
        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:
595
596
597
            an = 'an' if self._reqtype[0] in 'aeiou' else 'a'
            body = '%s [1] filed %s %s request for %s [2]:' % \
                   (self._user, an, self._reqtype, self._pkgbase)
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
            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)
        return headers


class RequestCloseNotification(Notification):
Kevin Morris's avatar
Kevin Morris committed
617

618
619
620
621
    def __init__(self, uid, reqid, reason):
        user = db.query(User.Username).filter(User.ID == uid).first()
        self._user = user.Username if user else None

622
        self._to = aurweb.config.get('options', 'aur_request_ml')
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652

        query = db.query(PackageRequest).join(PackageBase).join(
            PackageComaintainer,
            PackageComaintainer.PackageBaseID == PackageRequest.PackageBaseID,
            isouter=True
        ).join(
            User,
            or_(User.ID == PackageRequest.UsersID,
                User.ID == PackageBase.MaintainerUID,
                User.ID == PackageComaintainer.UsersID)
        ).filter(
            and_(PackageRequest.ID == reqid,
                 User.Suspended == 0)
        ).with_entities(
            User.Email
        ).distinct()
        self._cc = [u.Email for u in query]

        pkgreq = db.query(PackageRequest).join(RequestType).filter(
            PackageRequest.ID == reqid
        ).with_entities(
            PackageRequest.ClosureComment,
            RequestType.Name,
            PackageRequest.PackageBaseName
        ).first()

        self._text = pkgreq.ClosureComment
        self._reqtype = pkgreq.Name
        self._pkgbase = pkgreq.PackageBaseName

653
654
655
656
        self._reqid = int(reqid)
        self._reason = reason

    def get_recipients(self):
Lukas Fleischer's avatar
Lukas Fleischer committed
657
        return [(self._to, 'en')]
658

659
660
661
    def get_cc(self):
        return self._cc

Lukas Fleischer's avatar
Lukas Fleischer committed
662
    def get_subject(self, lang):
663
664
665
666
        return '[PRQ#%d] %s Request for %s %s' % (self._reqid,
                                                  self._reqtype.title(),
                                                  self._pkgbase,
                                                  self._reason.title())
667

Lukas Fleischer's avatar
Lukas Fleischer committed
668
    def get_body(self, lang):
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
        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)
        return headers


class TUVoteReminderNotification(Notification):
695
    def __init__(self, vote_id):
696
        self._vote_id = int(vote_id)
697
698
699
700
701
702
703
704
705
706
707

        subquery = db.query(TUVote.UserID).filter(TUVote.VoteID == vote_id)
        query = db.query(User).filter(
            and_(User.AccountTypeID.in_((2, 4)),
                 ~User.ID.in_(subquery),
                 User.Suspended == 0)
        ).with_entities(
            User.Email, User.LangPreference
        )
        self._recipients = [(u.Email, u.LangPreference) for u in query]

Lukas Fleischer's avatar
Lukas Fleischer committed
708
        super().__init__()
709
710

    def get_recipients(self):
Lukas Fleischer's avatar
Lukas Fleischer committed
711
        return self._recipients
712

Lukas Fleischer's avatar
Lukas Fleischer committed
713
    def get_subject(self, lang):
714
715
716
        return aurweb.l10n.translator.translate(
            'TU Vote Reminder: Proposal {id}',
            lang).format(id=self._vote_id)
717

Lukas Fleischer's avatar
Lukas Fleischer committed
718
    def get_body(self, lang):
719
720
721
722
        return aurweb.l10n.translator.translate(
            'Please remember to cast your vote on proposal {id} [1]. '
            'The voting period ends in less than 48 hours.',
            lang).format(id=self._vote_id)
723
724
725

    def get_refs(self):
        return (aur_location + '/tu/?id=' + str(self._vote_id),)
726
727


728
def main():
729
    db.get_engine()
730
731
    action = sys.argv[1]
    action_map = {
732
733
734
735
736
737
738
739
740
741
742
743
744
        '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,
745
746
    }

747
748
    with db.begin():
        notification = action_map[action](*sys.argv[2:])
749
    notification.send()
750

751
752
753

if __name__ == '__main__':
    main()