* make mail messages multipart/alternative messages with a text/plain and
[rss2maildir.git] / rss2maildir.py
1 #!/usr/bin/python
2
3 import mailbox
4 import sys
5 import os
6 import stat
7 import urllib
8
9 import feedparser
10
11 from email.MIMEMultipart import MIMEMultipart
12 from email.MIMEText import MIMEText
13
14 import datetime
15 import random
16 import string
17
18 import socket
19
20 from optparse import OptionParser
21 from ConfigParser import SafeConfigParser
22
23 from base64 import b64encode
24 import md5
25
26 import cgi
27 import dbm
28
29
30 def parse_and_deliver(maildir, url, statedir):
31     md = mailbox.Maildir(maildir)
32     fp = feedparser.parse(url)
33     db = dbm.open(os.path.join(statedir, "seen"), "c")
34     for item in fp["items"]:
35         # have we seen it before?
36         # need to work out what the content is first...
37
38         if item.has_key("content"):
39             content = item["content"][0]["value"]
40         else:
41             content = item["summary"]
42
43         md5sum = md5.md5(content.encode("utf8")).hexdigest()
44
45         if db.has_key(item["link"]):
46             data = db[item["link"]]
47             data = cgi.parse_qs(data)
48             if data["contentmd5"][0] == md5sum:
49                 continue
50
51         try:
52             author = item["author"]
53         except:
54             author = url
55
56         # create a basic email message
57         msg = MIMEMultipart("alternative")
58         messageid = "<" + datetime.datetime.now().strftime("%Y%m%d%H%M") + "." + "".join([random.choice(string.ascii_letters + string.digits) for a in range(0,6)]) + "@" + socket.gethostname() + ">"
59         msg.add_header("Message-ID", messageid)
60         msg.set_unixfrom("\"%s\" <rss2maildir@localhost>" %(url))
61         msg.add_header("From", "\"%s\" <rss2maildir@localhost>" %(author))
62         msg.add_header("To", "\"%s\" <rss2maildir@localhost>" %(url))
63         createddate = datetime.datetime(*item["updated_parsed"][0:6]).strftime("%a, %e %b %Y %T -0000")
64         msg.add_header("Date", createddate)
65         msg.add_header("Subject", item["title"])
66         msg.set_default_type("text/plain")
67
68         htmlpart = MIMEText(content.encode("utf8"), "html", "utf8")
69         textpart = MIMEText(content.encode("utf8"), "plain", "utf8")
70
71         msg.attach(textpart)
72         msg.attach(htmlpart)
73
74         # start by working out the filename we should be writting to, we do
75         # this following the normal maildir style rules
76         fname = str(os.getpid()) + "." + socket.gethostname() + "." + "".join([random.choice(string.ascii_letters + string.digits) for a in range(0,10)]) + "." + datetime.datetime.now().strftime('%s')
77         fn = os.path.join(maildir, "tmp", fname)
78         fh = open(fn, "w")
79         fh.write(msg.as_string())
80         fh.close()
81         # now move it in to the new directory
82         newfn = os.path.join(maildir, "new", fname)
83         os.link(fn, newfn)
84         os.unlink(fn)
85
86         # now add to the database about the item
87         data = urllib.urlencode((("message-id", messageid), ("created", createddate), ("contentmd5", md5sum)))
88         db[item["link"]] = data
89
90     db.close()
91
92 # first off, parse the command line arguments
93
94 oparser = OptionParser()
95 oparser.add_option(
96     "-c", "--conf", dest="conf",
97     help="location of config file"
98     )
99 oparser.add_option(
100     "-s", "--statedir", dest="statedir",
101     help="location of directory to store state in"
102     )
103
104 (options, args) = oparser.parse_args()
105
106 # check for the configfile
107
108 configfile = None
109
110 if options.conf != None:
111     # does the file exist?
112     try:
113         os.stat(options.conf)
114         configfile = options.conf
115     except:
116         # should exit here as the specified file doesn't exist
117         sys.stderr.write("Config file %s does not exist. Exiting.\n" %(options.conf,))
118         sys.exit(2)
119 else:
120     # check through the default locations
121     try:
122         os.stat("%s/.rss2maildir.conf" %(os.environ["HOME"],))
123         configfile = "%s/.rss2maildir.conf" %(os.environ["HOME"],)
124     except:
125         try:
126             os.stat("/etc/rss2maildir.conf")
127             configfile = "/etc/rss2maildir.conf"
128         except:
129             sys.stderr.write("No config file found. Exiting.\n")
130             sys.exit(2)
131
132 # Right - if we've got this far, we've got a config file, now for the hard
133 # bits...
134
135 scp = SafeConfigParser()
136 scp.read(configfile)
137
138 maildir_root = "RSSMaildir"
139 state_dir = "state"
140
141 if options.statedir != None:
142     state_dir = options.statedir
143     try:
144         mode = os.stat(state_dir)[stat.ST_MODE]
145         if not stat.S_ISDIR(mode):
146             sys.stderr.write("State directory (%s) is not a directory\n" %(state_dir))
147             sys.exit(1)
148     except:
149         # try to make the directory
150         try:
151             os.mkdir(state_dir)
152         except:
153             sys.stderr.write("Couldn't create statedir %s" %(state_dir))
154             sys.exit(1)
155 elif scp.has_option("general", "state_dir"):
156     new_state_dir = scp.get("general", "state_dir")
157     try:
158         mode = os.stat(state_dir)[stat.ST_MODE]
159         if not stat.S_ISDIR(mode):
160             sys.stderr.write("State directory (%s) is not a directory\n" %(state_dir))
161             sys.exit(1)
162     except:
163         # try to create it
164         try:
165             os.mkdir(new_state_dir)
166             state_dir = new_state_dir
167         except:
168             sys.stderr.write("Couldn't create state directory %s\n" %(new_state_dir))
169             sys.exit(1)
170 else:
171     try:
172         mode = os.stat(state_dir)[stat.ST_MODE]
173         if not stat.S_ISDIR(mode):
174             sys.stderr.write("State directory %s is not a directory\n" %(state_dir))
175             sys.exit(1)
176     except:
177         try:
178             os.mkdir(state_dir)
179         except:
180             sys.stderr.write("State directory %s could not be created\n" %(state_dir))
181             sys.exit(1)
182
183 if scp.has_option("general", "maildir_root"):
184     maildir_root = scp.get("general", "maildir_root")
185
186 try:
187     mode = os.stat(maildir_root)[stat.ST_MODE]
188     if not stat.S_ISDIR(mode):
189         sys.stderr.write("Maildir Root %s is not a directory\n" %(maildir_root))
190         sys.exit(1)
191 except:
192     try:
193         os.mkdir(maildir_root)
194     except:
195         sys.stderr.write("Couldn't create Maildir Root %s\n" %(maildir_root))
196         sys.exit(1)
197
198 feeds = scp.sections()
199 try:
200     feeds.remove("general")
201 except:
202     pass
203
204 for section in feeds:
205     # check if the directory exists
206     maildir = None
207     try:
208         maildir = scp.get(section, "maildir")
209     except:
210         maildir = section
211
212     maildir = urllib.urlencode(((section, maildir),)).split("=")[1]
213     maildir = os.path.join(maildir_root, maildir)
214
215     try:
216         exists = os.stat(maildir)
217         if stat.S_ISDIR(exists[stat.ST_MODE]):
218             # check if there's a new, cur and tmp directory
219             try:
220                 mode = os.stat(os.path.join(maildir, "cur"))[stat.ST_MODE]
221             except:
222                 os.mkdir(os.path.join(maildir, "cur"))
223                 if not stat.S_ISDIR(mode):
224                     sys.stderr.write("Broken maildir: %s\n" %(maildir))
225             try:
226                 mode = os.stat(os.path.join(maildir, "tmp"))[stat.ST_MODE]
227             except:
228                 os.mkdir(os.path.join(maildir, "tmp"))
229                 if not stat.S_ISDIR(mode):
230                     sys.stderr.write("Broken maildir: %s\n" %(maildir))
231             try:
232                 mode = os.stat(os.path.join(maildir, "new"))[stat.ST_MODE]
233                 if not stat.S_ISDIR(mode):
234                     sys.stderr.write("Broken maildir: %s\n" %(maildir))
235             except:
236                 os.mkdir(os.path.join(maildir, "new"))
237         else:
238             sys.stderr.write("Broken maildir: %s\n" %(maildir))
239     except:
240         try:
241             os.mkdir(maildir)
242         except:
243             sys.stderr.write("Couldn't create root maildir %s\n" %(maildir))
244             sys.exit(1)
245         try:
246             os.mkdir(os.path.join(maildir, "new"))
247             os.mkdir(os.path.join(maildir, "cur"))
248             os.mkdir(os.path.join(maildir, "tmp"))
249         except:
250             sys.stderr.write("Couldn't create required maildir directories for %s\n" %(section,))
251             sys.exit(1)
252
253     # right - we've got the directories, we've got the section, we know the
254     # url... lets play!
255
256     parse_and_deliver(maildir, section, state_dir)