root/python/ml/ml.py

Revision 52, 3.5 kB (checked in by sgk, 5 months ago)

Allow '㍑' character.

  • Property svn:executable set to *
Line 
1#!/usr/bin/python
2#vim:fileencoding=utf-8
3
4'''
5ml.py -- simple mailing list to just distribute messages in a group.
6
7Copyright (c) 2007,2010 Shigeru KANEMOTO
8All rights reserved.
9'''
10
11LIST_ADDRESS = 'list@example.com'
12LIST_SUBSCRIBERS = {
13  # 'email address (lower case)': u'real name',
14  'somebody@example.com': u'なまえ',
15}
16
17import sys
18import re
19import email.FeedParser
20import email.Header
21import email.Utils
22import smtplib
23
24BUFFER_SIZE = 1024
25
26
27class MLError(RuntimeError):
28  pass
29
30
31def message_from_fileobj(fileobj):
32  parser = email.FeedParser.FeedParser()
33  while True:
34    buffer = fileobj.read(BUFFER_SIZE)
35    if not buffer:
36      return parser.close()
37    parser.feed(buffer)
38
39
40def alter_message_for_forwarding(message):
41  # Check the destination
42  recipients = message.get_all('to', [])
43  recipients += message.get_all('cc', [])
44  recipients += message.get_all('resent-to', [])
45  recipients += message.get_all('resent-cc', [])
46  recipients = email.Utils.getaddresses(recipients)
47  for (realname, address) in recipients:
48    if address.lower() == LIST_ADDRESS:
49      break
50  else:
51    raise MLError, 'Not sent to this list.'
52
53  # Get nickname of the sender
54  sender = message['from']
55  if not sender:
56    raise MLError, 'No "from" header found.'
57  (realname, sender) = email.Utils.parseaddr(sender)
58  if not sender:
59    raise MLError, '"from" header not recognized.'
60  sender = sender.lower()
61  try:
62    nickname = LIST_SUBSCRIBERS[sender]
63  except KeyError:
64    raise MLError, 'Sender "%s" not allowed.' % sender
65
66  # Get the subject
67  subject = message.get('subject', '')
68  subject = email.Header.decode_header(subject)
69  (s, charset) = subject[0]
70  s = re.sub(r'^(Re|RE|re)(\d*|\[\d+\])[:>]\s*(Bcc:\s*|)', '', s, 1)
71  subject[0] = (s, charset)
72  subject = email.Header.make_header(subject)
73
74  # Prepare the new message
75  del message['to']
76  del message['cc']
77  del message['resent-to']
78  del message['resent-cc']
79  del message['from']
80  del message['reply-to']
81  del message['subject']
82  message['To'] = LIST_ADDRESS
83  message['From'] = LIST_ADDRESS
84  message['Subject'] = subject
85
86  def insert_nickname(m):
87    if m.get_content_maintype() != 'text':
88      return
89    charset = m.get_content_charset().lower()
90    body = m.get_payload(i=None, decode=True)
91
92    # Decode
93    if charset == 'iso-2022-jp':
94      #XXX 「㍑」対応
95      body = body.replace('\x1b$B', '\x1b$(Q').decode('iso-2022-jp-2004')
96    else:
97      body = body.decode(charset)
98
99    # Insert
100    if m.get_content_subtype() == 'html':
101      body = re.sub(r'^(.*<(body|BODY).*>|)', r'\1[%s]<br />' % nickname, body, 1)
102    else:
103      body = ('[%s]\r\n' % nickname) + body
104
105    # Encode
106    #XXX 「㍑」対応
107    body = body.encode('iso-2022-jp-2004').replace('\033$(Q', '\033$B')
108    del m['content-transfer-encoding']
109    m.set_payload(body, 'iso-2022-jp')
110
111  if message.is_multipart():
112    for m in message.get_payload():
113      insert_nickname(m)
114  else:
115    insert_nickname(message)
116
117  return sender
118
119
120def deliver_message(sender, message):
121  recipients = LIST_SUBSCRIBERS.keys()
122  recipients.remove(sender)
123  smtp = smtplib.SMTP('localhost')
124  smtp.sendmail(LIST_ADDRESS, recipients, message.as_string())
125  smtp.close()
126
127
128if __name__ == '__main__':
129  try:
130    message = message_from_fileobj(sys.stdin)
131    sender = alter_message_for_forwarding(message)
132    deliver_message(sender, message)
133  except MLError, e:
134    print >>sys.stderr, str(e)
135    # Courier suspends delivery to same address for a while after exiting
136    # with exit code except zero.
137    sys.exit(0)
Note: See TracBrowser for help on using the browser.