]> www.wagner.pp.ru Git - oss/ljdump.git/blob - ljdump.py
handle missing .last properly
[oss/ljdump.git] / ljdump.py
1 #!/usr/bin/python
2 #
3 # ljdump.py - livejournal archiver
4 # Greg Hewgill <greg@hewgill.com> http://hewgill.com
5 # Version 1.0.3
6 #
7 # $Id$
8 #
9 # This program reads the journal entries from a livejournal (or compatible)
10 # blog site and archives them in a subdirectory named after the journal name.
11 #
12 # The configuration is read from "ljdump.config". A sample configuration is
13 # provided in "ljdump.config.sample", which should be copied and then edited.
14 # The configuration settings are:
15 #
16 #   server - The XMLRPC server URL. This should only need to be changed
17 #            if you are dumping a journal that is livejournal-compatible
18 #            but is not livejournal itself.
19 #
20 #   username - The livejournal user name. A subdirectory will be created
21 #              with this same name to store the journal entries.
22 #
23 #   password - The account password. This password is never sent in the
24 #              clear; the livejournal "challenge" password mechanism is used.
25 #
26 # This program may be run as often as needed to bring the backup copy up
27 # to date. Both new and updated items are downloaded.
28 #
29 # LICENSE
30 #
31 # This software is provided 'as-is', without any express or implied
32 # warranty.  In no event will the author be held liable for any damages
33 # arising from the use of this software.
34 #
35 # Permission is granted to anyone to use this software for any purpose,
36 # including commercial applications, and to alter it and redistribute it
37 # freely, subject to the following restrictions:
38 #
39 # 1. The origin of this software must not be misrepresented; you must not
40 #    claim that you wrote the original software. If you use this software
41 #    in a product, an acknowledgment in the product documentation would be
42 #    appreciated but is not required.
43 # 2. Altered source versions must be plainly marked as such, and must not be
44 #    misrepresented as being the original software.
45 # 3. This notice may not be removed or altered from any source distribution.
46 #
47 # Copyright (c) 2005 Greg Hewgill
48
49 import codecs, md5, os, pprint, sys, xml.dom.minidom, xmlrpclib
50 from xml.sax import saxutils
51
52 def dochallenge(params, password):
53     challenge = server.LJ.XMLRPC.getchallenge()
54     params.update({
55         'auth_method': "challenge",
56         'auth_challenge': challenge['challenge'],
57         'auth_response': md5.new(challenge['challenge']+md5.new(password).hexdigest()).hexdigest()
58     })
59     return params
60
61 def dumpelement(f, name, e):
62     f.write("<%s>\n" % name)
63     for k in e.keys():
64         if isinstance(e[k], {}.__class__):
65             dumpelement(f, k, e[k])
66         else:
67             s = unicode(str(e[k]), "UTF-8")
68             f.write("<%s>%s</%s>\n" % (k, saxutils.escape(s), k))
69     f.write("</%s>\n" % name)
70
71 def writedump(fn, event):
72     f = codecs.open(fn, "w", "UTF-8")
73     f.write("""<?xml version="1.0"?>\n""")
74     dumpelement(f, "event", event)
75     f.close()
76
77 config = xml.dom.minidom.parse("ljdump.config")
78 Server = config.documentElement.getElementsByTagName("server")[0].childNodes[0].data
79 Username = config.documentElement.getElementsByTagName("username")[0].childNodes[0].data
80 Password = config.documentElement.getElementsByTagName("password")[0].childNodes[0].data
81
82 print "Fetching journal entries for: %s" % Username
83 try:
84     os.mkdir(Username)
85     print "Created subdirectory: %s" % Username
86 except:
87     pass
88
89 server = xmlrpclib.ServerProxy(Server)
90
91 new = 0
92 errors = 0
93
94 last = ""
95 try:
96     f = open("%s/.last" % Username, "r")
97     last = f.readline()
98     if last[-1] == '\n':
99         last = last[:len(last)-1]
100     f.close()
101 except:
102     pass
103 origlast = last
104
105 while True:
106     r = server.LJ.XMLRPC.syncitems(dochallenge({
107         'username': Username,
108         'ver': 1,
109         'lastsync': last,
110     }, Password))
111     #pprint.pprint(r)
112     if len(r['syncitems']) == 0:
113         break
114     for item in r['syncitems']:
115         if item['item'][0] == 'L':
116             print "Fetching journal entry %s (%s)" % (item['item'], item['action'])
117             try:
118                 e = server.LJ.XMLRPC.getevents(dochallenge({
119                     'username': Username,
120                     'ver': 1,
121                     'selecttype': "one",
122                     'itemid': item['item'][2:],
123                 }, Password))
124                 writedump("%s/%s" % (Username, item['item']), e['events'][0])
125                 new += 1
126             except xmlrpclib.Fault, x:
127                 print "Error getting item: %s" % item['item']
128                 pprint.pprint(x)
129                 errors += 1
130         last = item['time']
131 f = open("%s/.last" % Username, "w")
132 f.write("%s\n" % last)
133 f.close()
134 if origlast:
135     print "%d new entries (since %s)" % (new, origlast)
136 else:
137     print "%d new entries" % new
138 if errors > 0:
139     print "%d errors" % errors