diff -up gwibber/gwibber/microblog/__init__.py.plurk gwibber/gwibber/microblog/__init__.py --- gwibber/gwibber/microblog/__init__.py.plurk 2009-04-21 14:14:02.000000000 -0400 +++ gwibber/gwibber/microblog/__init__.py 2009-04-21 17:30:30.000000000 -0400 @@ -3,7 +3,7 @@ import operator, traceback from . import can from . import twitter, jaiku, identica, laconica, pownce, friendfeed from . import digg, flickr, brightkite, rss, pingfm, facebook -from . import greader +from . import greader, plurk # i18n magic import gettext @@ -24,6 +24,7 @@ PROTOCOLS = { "pingfm": pingfm, "greader": greader, #"brightkite": brightkite, + "plurk": plurk, } def supports(a, feature): diff -up /dev/null gwibber/gwibber/microblog/plurk.py --- /dev/null 2009-04-21 13:05:06.247003930 -0400 +++ gwibber/gwibber/microblog/plurk.py 2009-04-21 17:30:30.000000000 -0400 @@ -0,0 +1,88 @@ + +""" +Plurk interface for Gwibber +- 04/21/2009 +Copyright (C) 2009 Tom "spot" Callaway + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +""" + +from . import can, support +import urllib2, re +import time +import sys + +PROTOCOL_INFO = { + "name": "Plurk", + "version": 0.1, + + "config": [ + "private:password", + "username", + "message_color", + "receive_enabled", + "send_enabled", + ], + + "features": [ + can.SEND, + can.RECEIVE, + ], +} + +class Message: + def __init__(self, client, data): + self.client = client + self.plurk_api = support.plurkapi.PlurkAPI() + self.account = client.account + self.protocol = client.account["protocol"] + self.username = client.account["username"] + self.sender = self.plurk_api.uidToUserinfo(data["owner_id"])["full_name"] + self.sender_nick = self.plurk_api.uidToUserinfo(data["owner_id"])["nick_name"] + self.sender_id = data["owner_id"] + self.time = support.parse_time(data["posted"]) + # self.text = data["content"] mangles links + self.text = data["content_raw"] + avatar = self.plurk_api.uidToUserinfo(data["owner_id"])["avatar"] + if avatar: + self.image = "http://avatars.plurk.com/%s-big%s.jpg" %( data["owner_id"], avatar ) + else: + self.image = "http://avatars.plurk.com/%s-big.jpg" % data["owner_id"] + self.bgcolor = "message_color" + self.profile_url = "http://www.plurk.com/%s" % self.plurk_api.uidToUserinfo(data["owner_id"])["nick_name"] + +class Client: + def __init__(self, acct): + self.account = acct + self.plurk_api = support.plurkapi.PlurkAPI() + + def login(self): + return self.plurk_api.login(self.account["username"], self.account["private:password"]) + + def receive_enabled(self): + return self.account["receive_enabled"] and \ + self.account["username"] != None and \ + self.account["private:password"] != None + + def receive(self): + self.login() + plurks = self.plurk_api.getPlurks() + + for data in plurks: + yield Message(self, data) + + def send(self, message): + self.login() + self.plurk_api.addPlurk(qualifier='says', content=message) diff -up gwibber/gwibber/microblog/support/__init__.py.plurk gwibber/gwibber/microblog/support/__init__.py --- gwibber/gwibber/microblog/support/__init__.py.plurk 2009-04-21 14:14:02.000000000 -0400 +++ gwibber/gwibber/microblog/support/__init__.py 2009-04-21 17:30:30.000000000 -0400 @@ -6,7 +6,7 @@ SegPhault (Ryan Paul) - 07/25/2008 """ -from . import facelib +from . import facelib, plurkapi import re, os, locale, mx.DateTime, urllib2 import gettext diff -up /dev/null gwibber/gwibber/microblog/support/plurkapi.py --- /dev/null 2009-04-21 13:05:06.247003930 -0400 +++ gwibber/gwibber/microblog/support/plurkapi.py 2009-04-21 17:30:30.000000000 -0400 @@ -0,0 +1,262 @@ +""" + plurkapi.py + Copyright 2008 David Blume + Portions Copyright 2008 Ryan Lim + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +2008-07-12 : Original, based on RLPlurkAPI.php, by Ryan Lim + +""" + +import time +import urllib +import urllib2 +import cookielib +import simplejson # http://www.undefined.org/python/ +import re + +class PlurkError(Exception): pass + +def permalinkToPlurkID(permalink): + base36number = permalink[len('http://www.plurk.com/p/'):] + return int(base36number, 36) + +def _baseN(num,b): + return ((num == 0) and "0" ) or ( _baseN(num // b, b).lstrip("0") + "0123456789abcdefghijklmnopqrstuvwxyz"[num % b]) + +def PlurkIDToPermalink(plurk_id): + return 'http://www.plurk.com/p/' + _baseN(int(plurk_id), 36) + +class PlurkAPI: + + def __init__(self): + self._logged_in = False + self._uid = -1 + self._nickname = None + self._friends = {} + self._cookies = cookielib.CookieJar() + self._opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self._cookies)) + urllib2.install_opener(self._opener) + + def login(self, nickname, password): + """ login to plurk and keep a session cookie """ + post = urllib.urlencode({'nick_name': nickname, 'password': password}) + request = urllib2.Request( self._plurk_paths['login'], post) + response = urllib2.urlopen( request ) + self._cookies.extract_cookies( response, request ) + for cookie in self._cookies: + if cookie.name.startswith('plurkcookie'): + self._logged_in = True + break + if self._logged_in == True: + response = urllib2.urlopen('http://www.plurk.com/user/%s' % (nickname) ) + page = response.read() + uid_pat = re.compile('var GLOBAL = \{.*"uid": ([\d]+),.*\}') + matches = uid_pat.findall(page) + if len(matches): + self._uid = matches[0] + else: + raise PlurkError, "Could not find user_id." + self._nickname = nickname; + post = urllib.urlencode({ 'user_id': self._uid }) + response = urllib2.urlopen(self._plurk_paths['getCompletion'], post ) + self._friends = simplejson.load(response) + return self._logged_in + + def addPlurk(self, lang='en', qualifier='says', content='', allow_comments=True, limited_to=[]): + """ Add a plurk to your timeline. (Must be logged in.) """ + if self._logged_in == False: + return False + + if allow_comments == True: + no_comments = '0' + else: + no_comments = '1' + + params = {'posted': time.strftime('%Y-%m-%dT%H:%M:%S', time.gmtime()), + 'qualifier': qualifier, + 'content': content, + 'lang': lang, + 'uid': self._uid, + 'no_comments': no_comments } + + if len(limited_to): + params['limited_to'] = '[%s]' % ','.join(limited_to) + + post = urllib.urlencode(params) + response = urllib2.urlopen( self._plurk_paths['plurk_add'], post) + page = response.read() + if page.find('anti-flood') != -1: + raise PlurkError, 'Anti-flood rules prohibited the plurk.' + matches = re.compile('"error":\s(\S+)}').findall(page) + if len(matches) and matches[0] != 'null': + raise PlurkError, matches[0] + + return True + + def getPlurks(self, uid = None, date_from = None, only_responded = False): + """ If logged in, gets yours and your friends' plurks. + If not logged in, gets only the plurks for the specified user id. + date_from should be of the form 2008-9-5T19:07:29 """ + if uid == None: + uid = self._uid + + params = { 'user_id': uid, } + if date_from != None: + params['offset'] = date_from + if only_responded != False: + params['only_responded'] = 1 + post = urllib.urlencode(params) + response = urllib2.urlopen(self._plurk_paths['plurk_get'], post ) + page = response.read() + # The following two lines are a hack around the fact that + # simplejson doesn't create Date objects. + date_pat = re.compile('\"posted\": new Date\((\".+?\")\)') + data = simplejson.loads(re.sub(date_pat, '"posted": \g<1>', page)) + return data + + def getPlurksById(self, ids = []): + """ Gets individual plurks by their ids. """ + + params = { 'ids': '[%s]' % (','.join([str(id) for id in ids]), ), } + post = urllib.urlencode(params) + response = urllib2.urlopen(self._plurk_paths['plurk_get_by_id'], post ) + page = response.read() + # The following two lines are a hack around the fact that + # simplejson doesn't create Date objects. + date_pat = re.compile('\"posted\": new Date\((\".+?\")\)') + data = simplejson.loads(re.sub(date_pat, '"posted": \g<1>', page)) + return data + + def getPlurkResponses(self, plurk_id): + """ Gets individual plurks by their ids. """ + + post = urllib.urlencode({ 'plurk_id': plurk_id }) + response = urllib2.urlopen(self._plurk_paths['plurk_get_responses'], post ) + page = response.read() + # The following two lines are a hack around the fact that + # simplejson doesn't create Date objects. + date_pat = re.compile('\"posted\": new Date\((\".+?\")\)') + data = simplejson.loads(re.sub(date_pat, '"posted": \g<1>', page)) + return data + + def respondToPlurk(self, plurk_id, lang='en', qualifier='says', content=''): + """ Respond to the specified plurk. """ + if self._logged_in == False or len(content) > 140: + return False + params = {'posted': time.strftime('%Y-%m-%dT%H:%M:%S', time.gmtime()), + 'qualifier': qualifier, + 'content': content, + 'lang': lang, + 'p_uid': self._uid, + 'uid': self._uid, + 'plurk_id': plurk_id } + post = urllib.urlencode(params) + response = urllib2.urlopen( self._plurk_paths['plurk_respond'], post) + return response.read() + + def getAlerts(self): + """ Parse any pending Alerts and returns a list of user ids requesting friendship. """ + if self._logged_in == False: + return False + response = urllib2.urlopen( self._plurk_paths['notification'] ) + page = response.read() + notification_pat = re.compile('DI\s*\(\s*Notifications\.render\(\s*(\d+),\s*0\)\s*\);') + matches = notification_pat.findall(page) + return matches + + def befriend(self, uids, befriend=True): + """ Accept or deny friendship requests. """ + if self._logged_in == False: + return False + + string_path_accept_deny = self._plurk_paths['notification_accept'] + if not befriend: + string_path_accept_deny = self._plurk_paths['notification_deny'] + + for uid in uids: + post = urllib.urlencode({ 'friend_id': uid, }) + response = urllib2.urlopen( string_path_accept_deny, post ) + return True + + def uidToUserinfo(self, uid): + """ Returns a dict of available user info for the given uid. + TODO: Make the dict be a class? http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52308 """ + post = urllib.urlencode({ 'user_id': uid }) + response = urllib2.urlopen(self._plurk_paths['user_get_info'], post ) + page = response.read() + + # The following two lines are a hack around the fact that + # simplejson doesn't create Date objects. + date_pat = re.compile(': new Date\((\".+?\")\)') + return simplejson.loads(re.sub(date_pat, ': \g<1>', page)) + + def removeFriend(self, uid): + """ Removes the user with the specified uid as a friend """ + if self._logged_in == False: + return False + post = urllib.urlencode({ 'friend_id': uid }) + response = urllib2.urlopen(self._plurk_paths['remove_friend'], post ) + return response.read() + + def uidToNickname(self, uid): + """ Returns the nickname for the user with the given user id. """ + uid = str(uid) + if uid == self._uid: + return self._nickname + if self._friends.has_key(uid): + return self._friends[uid]['nick_name'] + else: + return 'User %s' % uid + + uid = property(lambda self: self._uid) + logged_in = property(lambda self: self._logged_in) + nickname = property(lambda self: self._nickname) + friends = property(lambda self: self._friends) + + _plurk_paths = { + 'http_base' : 'http://www.plurk.com', + 'login' : 'http://www.plurk.com/Users/login?redirect_page=main', + 'getCompletion' : 'http://www.plurk.com/Users/getCompletion', + 'plurk_add' : 'http://www.plurk.com/TimeLine/addPlurk', + 'plurk_respond' : 'http://www.plurk.com/Responses/add', + 'plurk_get' : 'http://www.plurk.com/TimeLine/getPlurks', + 'plurk_get_by_id' : 'http://www.plurk.com/TimeLine/getPlurksById', + 'plurk_get_responses' : 'http://www.plurk.com/Responses/get2', + 'plurk_get_unread' : 'http://www.plurk.com/TimeLine/getUnreadPlurks', + 'plurk_mute' : 'http://www.plurk.com/TimeLine/setMutePlurk', + 'plurk_delete' : 'http://www.plurk.com/TimeLine/deletePlurk', + 'notification' : 'http://www.plurk.com/Notifications', + 'notification_accept' : 'http://www.plurk.com/Notifications/allow', + 'notification_makefan' : 'http://www.plurk.com/Notifications/allowDontFollow', + 'notification_deny' : 'http://www.plurk.com/Notifications/deny', + 'friends_get' : 'http://www.plurk.com/Users/friends_get', + 'friends_block' : 'http://www.plurk.com/Friends/blockUser', + 'friends_remove_block' : 'http://www.plurk.com/Friends/removeBlock', + 'friends_get_blocked' : 'http://www.plurk.com/Friends/getBlockedByOffset', + 'user_get_info' : 'http://www.plurk.com/Users/fetchUserInfo', + 'remove_friend' : 'http://www.plurk.com/Users/removeFriend' + } + + _qualifiers = { 'en': (':', 'loves', 'likes', 'shares', 'gives', 'hates', 'wants', 'wishes', + 'needs', 'will', 'hopes', 'asks', 'has', 'was', 'wonders', 'feels', 'thinks', 'says', 'is') + } + + diff -up gwibber/gwibber/microblog/support/README.plurkapi.plurk gwibber/gwibber/microblog/support/README.plurkapi --- gwibber/gwibber/microblog/support/README.plurkapi.plurk 2009-04-21 17:31:56.000000000 -0400 +++ gwibber/gwibber/microblog/support/README.plurkapi 2009-04-21 17:31:52.000000000 -0400 @@ -0,0 +1,4 @@ +The plurkapi code comes from http://code.google.com/p/plurkapipy/ +It is because of their work that it was so simple to enable plurk support in gwibber, thanks! + +~Tom "spot" Callaway, Tue Apr 21 2009 diff -up gwibber/ui/preferences.glade.plurk gwibber/ui/preferences.glade --- gwibber/ui/preferences.glade.plurk 2009-04-21 14:14:02.000000000 -0400 +++ gwibber/ui/preferences.glade 2009-04-21 17:30:30.000000000 -0400 @@ -2130,6 +2130,257 @@ a Gwibber login code from Facebook. + + 5 + center-on-parent + dialog + + + True + 2 + + + True + 10 + + + True + 0 + none + + + True + 10 + 12 + + + True + 2 + 2 + 5 + 5 + + + True + True + False + + + 1 + 2 + 1 + 2 + + + + + True + True + + + 1 + 2 + + + + + True + Password: + + + 1 + 2 + + + + + True + Username: + + + + + + + + + True + <b>Account Information</b> + True + + + label_item + + + + + False + 0 + + + + + True + 0 + none + + + True + 10 + 12 + + + True + + + _Receive Messages + True + True + False + True + True + + + 0 + + + + + _Send Messages + True + True + False + True + True + + + 1 + + + + + + + + + True + <b>Account Status</b> + True + + + label_item + + + + + False + 1 + + + + + True + 0 + none + + + True + 10 + 12 + + + True + + + True + Message Color: + + + False + False + 0 + + + + + True + True + True + #000000000000 + + + False + False + 5 + 1 + + + + + + + + + True + <b>Appearance</b> + True + + + label_item + + + + + False + 2 + + + + + 1 + + + + + True + + + gtk-delete + True + True + True + True + + + False + False + 0 + + + + + gtk-close + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + + + + 5 center-on-parent