~ubuntu-branches/ubuntu/maverick/awn-extras-applets/maverick

« back to all changes in this revision

Viewing changes to applets/maintained/feeds/classes.py

  • Committer: Bazaar Package Importer
  • Author(s): Julien Lavergne
  • Date: 2010-08-29 14:29:52 UTC
  • mto: This revision was merged to the branch mainline in revision 21.
  • Revision ID: james.westby@ubuntu.com-20100829142952-rhvuetyms9bv5uu7
Tags: upstream-0.4.0+bzr1372
ImportĀ upstreamĀ versionĀ 0.4.0+bzr1372

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
import dbus
35
35
import dbus.service
36
36
import dbus.glib
 
37
from desktopagnostic import Color
 
38
from desktopagnostic.config import GROUP_DEFAULT
37
39
import feedparser
 
40
import gtk
38
41
 
39
42
from awn import extras
40
43
from awn.extras import _, awnlib
68
71
 
69
72
        return 'OK'
70
73
 
 
74
class PlaceHolder(gtk.DrawingArea):
 
75
    def __init__(self):
 
76
        gtk.DrawingArea.__init__(self)
 
77
 
 
78
        self.connect('expose-event', self.draw)
 
79
 
 
80
    def draw(self, widget, event):
 
81
        w = self.allocation.width
 
82
        h = self.allocation.height
 
83
 
 
84
        gdk_color = self.get_style().text[gtk.STATE_NORMAL].to_string()
 
85
        color = Color.from_string(gdk_color + 'ffff')
 
86
        cr = self.window.cairo_create()
 
87
        cr.set_source_rgba(*color.get_cairo_color())
 
88
        cr.set_dash((1, 0, 0, 1))
 
89
        cr.rectangle(0.0, 0.0, w, h)
 
90
        cr.stroke()
 
91
 
71
92
#Used for storing tokens for service logins
72
93
#(Also works as a conventient pickle wraparound)
73
94
class Tokens:
119
140
        self['new'] = new
120
141
        self['notify'] = notify
121
142
 
 
143
    def basic(self):
 
144
        return {'url': self['url'], 'title': self['title']}
 
145
 
122
146
#Base class for all types of sources
123
147
class FeedSource:
124
148
    io_error = False
154
178
        pass
155
179
 
156
180
    def post_data(self, uri,
 
181
                  headers={},
157
182
                  data=None,
158
183
                  timeout=30,
159
184
                  server_headers=False,
167
192
            error_cb = self.error
168
193
 
169
194
        if self.applet:
170
 
            self.applet.network_handler.post_data(uri, data, timeout, server_headers, opener, \
 
195
            self.applet.network_handler.post_data(uri, headers, data, timeout, server_headers, opener, \
171
196
                callback=cb, error=error_cb)
172
197
 
173
198
    #Also convenience
231
256
        self.icon = os.path.join(cache_dir, self._favicon_siteid + '.ico')
232
257
        self.applet.got_favicon(self)
233
258
 
234
 
        del self._favicon_siteid
235
 
 
236
259
    #Do something if the feed icon was clicked.
237
260
    def icon_clicked(self):
238
261
        pass
244
267
#TODO: Still need a better name. This is used if the feed may have items that are not considered new.
245
268
class StandardNew:
246
269
    newest = None
 
270
    last_new = []
247
271
    notified = []
248
272
 
249
273
    #Call this after getting the entries, but before calling applet.feed_updated()
250
274
    def get_new(self):
 
275
        new_new = []
 
276
 
 
277
        if not self.applet.client.get_bool(GROUP_DEFAULT, 'keep_unread'):
 
278
            self.last_new = []
 
279
 
251
280
        #See if the feed was updated, etc...
252
281
        if self.newest is not None and self.newest['url'] != self.entries[0]['url'] and \
253
282
          self.newest['title'] != self.entries[0]['title']:
267
296
 
268
297
        #Mark the new feeds as new
269
298
        for i, entry in enumerate(self.entries):
270
 
            entry['new'] = bool(i < self.num_new)
271
 
 
272
 
            if entry['new'] and [entry['url'], entry['title']] not in self.notified:
273
 
                self.notified.append([entry['url'], entry['title']])
274
 
                entry['notify'] = True
275
 
                self.num_notify += 1
276
 
 
277
 
            else:
278
 
                entry['notify'] = False
 
299
            entry['new'] = bool(i < self.num_new) or entry.basic() in self.last_new
 
300
 
 
301
            if entry['new']:
 
302
                new_new.append(entry.basic())
 
303
 
 
304
                if not i < self.num_new:
 
305
                    self.num_new += 1
 
306
 
 
307
                if [entry['url'], entry['title']] not in self.notified:
 
308
                    self.notified.append([entry['url'], entry['title']])
 
309
                    entry['notify'] = True
 
310
                    self.num_notify += 1
 
311
 
 
312
                else:
 
313
                    entry['notify'] = False
 
314
 
 
315
        self.last_new = new_new
279
316
 
280
317
#Used for logging in
281
318
class KeySaver:
324
361
class GoogleFeed(KeySaver):
325
362
    opener = None
326
363
    SID = None
 
364
    Auth = None
327
365
    logged_in = False
328
366
    title = _("Google")
329
367
    client_login_url = 'https://www.google.com/accounts/ClientLogin'
378
416
 
379
417
        #Now authenticate
380
418
        self.post_data(self.service_login_auth_url,
 
419
                       {},
381
420
                       data,
382
421
                       opener=self.opener,
383
422
                       cb=self.did_auth,
429
468
                        'continue': 'http://www.google.com/'})
430
469
 
431
470
                    #Send the data to get the SID
432
 
                    self.post_data(self.client_login_url, data, 15,
 
471
                    self.post_data(self.client_login_url, {}, data, 15,
433
472
                        cb=self.got_sid, error_cb=self.login_error)
434
473
 
435
474
    def got_sid(self, data):
441
480
            return
442
481
 
443
482
        #Save the SID so we don't have to re-login every update
444
 
        self.SID = data.split('=')[1].split('\n')[0]
 
483
        for line in data.split('\n'):
 
484
            if line.find('SID=') == 0:
 
485
                self.SID = line.split('=')[1]
 
486
            elif line.find('Auth=') == 0:
 
487
                self.Auth = line.split('=')[1]
 
488
 
445
489
        self.logged_in = True
446
490
 
447
491
        if self.should_update:
451
495
    def get_search_results(self, query, cb, _error_cb):
452
496
        search_url = self.feed_search_url + urllib.urlencode({'q': query})
453
497
 
454
 
        if self.SID is not None:
 
498
        if None not in (self.SID, self.Auth):
455
499
            self.get_data(search_url, {'Cookie': 'SID=' + self.SID}, False,
456
500
                user_data=(cb, _error_cb), cb=self.got_search_results, error_cb=_error_cb)
457
501
 
513
557
 
514
558
        else:
515
559
            #Load the reading list with that magic SID as a cookie
516
 
            self.get_data(self.fetch_url, {'Cookie': 'SID=' + self.SID}, True, cb=self.got_parsed)
 
560
            self.get_data(self.fetch_url, {'Cookie': 'SID=' + self.SID,
 
561
                'Authorization': 'GoogleLogin auth=' + self.Auth}, True, cb=self.got_parsed)
517
562
 
518
563
    def got_parsed(self, parsed):
519
564
        self.entries = []
619
664
    title = _("Reddit Inbox")
620
665
    orangered_url = 'http://www.reddit.com/static/mail.png'
621
666
    login = 'https://www.reddit.com/api/login/%s' # % username
622
 
    #RSS uses slightly less bandwidth, but JSON provides newness info
623
667
    messages_url = 'http://www.reddit.com/message/inbox/.json?mark=false'
624
 
    mark_as_read = 'http://www.reddit.com/message/inbox/.rss?mark=true'
625
668
    inbox_url = 'http://www.reddit.com/message/messages/'
626
669
 
627
670
    def __init__(self, applet, username, password=None):
633
676
        self.cookie = None
634
677
        self.should_update = False
635
678
        self.already_notified_about = []
 
679
        self.marked_read = []
636
680
        self.init_network_error = False
637
681
 
638
682
        #Get ready to update the feed, but don't actually do so.
654
698
                    self.error()
655
699
 
656
700
                else:
657
 
                    self.post_data(self.login % self.username.lower(), data,
 
701
                    self.post_data(self.login % self.username.lower(), {}, data,
658
702
                        server_headers = True, cb=self.got_reddit_cookie, error_cb=self.cookie_error)
659
703
 
660
704
    def cookie_error(self, *args):
722
766
                else:
723
767
                    title = _("Post reply from %s") % message['data']['author']
724
768
 
725
 
            new = message['data']['new']
 
769
            new = message['data']['id'] not in self.marked_read and message['data']['new']
726
770
 
727
771
            if new:
728
772
                self.num_new += 1
733
777
                notify = True
734
778
                self.num_notify += 1
735
779
 
736
 
            self.entries.append(Entry(url, title, new, notify))
 
780
            entry = Entry(url, title, new, notify)
 
781
            entry['id'] = message['data']['id']
 
782
            self.entries.append(entry)
737
783
 
738
784
        self.applet.feed_updated(self)
739
785
 
748
794
    #Unfortunately, we can only mark all messages as read, not individual ones.
749
795
    def item_clicked(self, i):
750
796
        if self.entries[i]['new'] == True:
751
 
            if self.num_new == 1:
752
 
                self.num_new = 0
753
 
                deboldify(self.applet.feed_labels[self.url])
754
 
                deboldify(self.applet.displays[self.url].get_children()[i], True)
755
 
                self.get_favicon('www.reddit.com')
756
 
 
757
 
                self.get_data(self.mark_as_read, {'Cookie': self.cookie})
758
 
 
759
 
            else:
760
 
                self.num_new -= 1
 
797
            self.entries[i]['new'] = False
 
798
            self.num_new -= 1
 
799
            deboldify(self.applet.displays[self.url].get_children()[i], True)
 
800
 
 
801
            self.marked_read.append(self.entries[i]['id'])
 
802
 
 
803
            if self.num_new == 0:
 
804
                 deboldify(self.applet.feed_labels[self.url])
 
805
                 self.get_favicon('www.reddit.com')
761
806
 
762
807
class Twitter(FeedSource, StandardNew, KeySaver):
763
808
    base_id = 'twitter'
893
938
 
894
939
        try:
895
940
            self.web_url = parsed.feed.link
 
941
            if self.web_url[0] == '/':
 
942
                self.web_url = '/'.join(self.url.split('/')[:3]) + self.web_url
896
943
        except:
897
944
            self.web_url = ''
898
945
 
899
946
        try:
900
947
            for entry in parsed.entries[:5]:
901
 
                self.entries.append(Entry(entry.link, entry.title))
 
948
                if entry.link[0] == '/':
 
949
                    self.entries.append(Entry('/'.join(self.web_url.split('/')[:3]) + entry.link,
 
950
                        entry.title))
 
951
                else:
 
952
                    self.entries.append(Entry(entry.link, entry.title))
902
953
        except:
903
954
            self.error()
904
955
            return
922
973
    if button:
923
974
        widget = widget.child
924
975
 
925
 
    widget.set_markup('<span font_weight="bold">%s</span>' % safify(widget.get_text()))
 
976
    widget.set_markup('<span font_weight="bold">%s</span>' % widget.get_text())
926
977
 
927
978
def deboldify(widget, button=False):
928
979
    if button:
931
982
    widget.set_markup(widget.get_text())
932
983
 
933
984
def safify(text):
934
 
  return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;')
 
985
    text = text.replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;')
 
986
    text2 = list(text)
 
987
    for char, i in enumerate(text2):
 
988
        if char == '&':
 
989
            ok = False
 
990
            if len(text2) >= i:
 
991
                if text2[i + 1] == '#' and (text2[i + 4] == ';' or text2[i + 6] == ';'):
 
992
                    ok = True
 
993
                elif len(text2) - 1 >= i + 3:
 
994
                    if text2[i + 1:3] == 'lt;':
 
995
                        ok = True
 
996
                    elif text2[i + 1:3] == 'gt;':
 
997
                        ok = True
 
998
                    elif len(text2) -1 >= i + 4:
 
999
                        if text2[i + 1:4] == 'quot;':
 
1000
                            ok = True
 
1001
 
 
1002
            if not ok:
 
1003
                text2[i] = '&amp;'
 
1004
 
 
1005
    return ''.join(text2)