First attempt at real response email.
[be.git] / interfaces / email / interactive / be-handle-mail
1 #!/usr/bin/env python
2 #
3 # Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 """Provide and email interface to the distributed bugtracker Bugs
19 Everywhere.  Recieves incoming email via procmail and allows users to
20 select actions with their subject lines.  Subject lines follow the
21 format
22   [be-bug] command (options) (args)
23 With the body of the email being used as the final argument for the
24 commands "new" and "comment", and ignored otherwise.  The options and
25 arguments are split on whitespace, so don't use whitespace inside a
26 single argument.
27
28 Eventually we'll commit after every message.
29 """
30
31 import libbe.cmdutil, libbe.utility
32 import email
33 import send_pgp_mime
34 import sys
35 import time
36
37 SUBJECT_COMMENT = "[be-bug]"
38 HANDLER_ADDRESS = "wking@thor.physics.drexel.edu"
39
40 ALLOWED_COMMANDS = ["new", "comment", "list", "show", "help"]
41
42 class InvalidEmail (ValueError):
43     def __init__(self, msg, message):
44         ValueError.__init__(self, message)
45         self.msg = msg
46
47 class InvalidSubject (InvalidEmail):
48     pass
49
50 class InvalidCommand (InvalidEmail):
51     def __init__(self, msg, command):
52         message = "Invalid command '%s'" % command
53         ValueError.__init__(self, msg, message)
54         self.command = command
55
56 def get_body_type(msg):
57     for part in msg.walk():
58         if part.is_multipart():
59             continue
60         return (part.get_payload(decode=1), part.get_content_type())
61
62 def run_message(msg_text):
63     encoding = libbe.encoding.get_encoding()
64     libbe.encoding.set_IO_stream_encodings(encoding)
65     
66     p=email.Parser.Parser()
67     msg=p.parsestr(msg_text)
68     
69     if "subject" not in msg:
70         raise InvalidSubject(msg, "Email must contain a subject")
71     author = msg["from"]
72     id = msg["message-id"]
73     args = msg["subject"].split()
74     if len(args) < 1 or args[0] != SUBJECT_COMMENT:
75         raise InvalidSubject(msg, "Subject must start with '%s '" % SUBJECT_COMMENT)        
76     elif len(args) < 2:
77         raise InvalidCommand(msg, "")
78     command = args[1]
79     if command not in ALLOWED_COMMANDS:
80         raise InvalidCommand(msg, command)
81     if len(args) > 2:
82         command_args = args[2:]
83     else:
84         command_args = []
85     if command in ["new", "comment"]:
86         body,type = get_body_type(msg)
87         if command == "new":
88             if "--reporter" not in args and "-r" not in args:
89                 command_args = ["--reporter", author] + command_args
90             body = body.strip().split("\n", 1)[0] # only take first line
91         elif command == "comment":
92             if "--author" not in args and "-a" not in args:
93                 command_args = ["--author", author] + command_args
94             if "--content-type" not in args and "-c" not in args:
95                 command_args = ["--content-type", type] + command_args
96             if "--alt-id" not in args:
97                 command_args = ["--alt-id", msg["message-id"]] + command_args
98         command_args.append(body)
99     # catch stdout and stderr
100     orig_stdout = sys.stdout
101     orig_stderr = sys.stderr
102     sys.stdout = StringIO.StringIO()
103     sys.stderr = StringIO.StringIO()
104     # run the command
105     ret = libbe.cmdutil.execute(command, command_args)
106     # restore stdout and stderr
107     out_text = sys.stdout.getvalue()
108     err_text = sys.stderr.getvalue()
109     sys.stdout = orig_stdout
110     sys.stderr = orig_stderr
111
112     response_header = [u"From: %s" % HANDLER_ADDRESS,
113                        u"To: %s" % author,
114                        u"Date: %s" % , libbe.utility.time_to_str(time.time()),
115                        u"Content-Type: text/plain; charset=%s" % encoding,
116                        u"Content-Transfer-Encoding: 8bit",
117                        u"In-reply-to: %s" % (id),
118                        u"Subject: %s Re: %s" % (SUBJECT_COMMENT, command),
119                        ]
120     response_body = [u"Results of running: (exit code %d)" % ret,
121                      u"  %s %s" % (command, " ".join(command_args)),]
122     if len(out_text) > 0:
123         response_body.extend([u"", u"stdout:", u"", out_text])
124     if len(err_text) > 0:
125         response_body.extend([u"", u"stderr:", u"", err_text])
126     response_body.append(u"")
127     response_email = send_pgp_mime.Mail(u"\n".join(response_header),
128                                         u"\n".join(response_body))
129     return response_email
130
131 def main():
132     msg_text = sys.stdin.read()
133     response_email = handle_message(msg_text)
134     send_pgp_mime.mail(response_email, send_pgp_mime.sendmail)
135
136 if __name__ == "__main__":
137     main()