← Back to team overview

cairo-dock-team team mailing list archive

[Merge] lp:~eduardo-mucelli/cairo-dock-plug-ins-extras/Twitter into lp:cairo-dock-plug-ins-extras

 

Eduardo Mucelli Rezende Oliveira has proposed merging lp:~eduardo-mucelli/cairo-dock-plug-ins-extras/Twitter into lp:cairo-dock-plug-ins-extras.

Requested reviews:
  Cairo-Dock Team (cairo-dock-team)

For more details, see:
https://code.launchpad.net/~eduardo-mucelli/cairo-dock-plug-ins-extras/Twitter/+merge/105573

Added support to Identi.ca! Many code changes, refactoring, and improving. Ps.: At the moment is possible only to post a tweet on Identi.ca. The applet post both on Twitter and Identi.ca the same tweet.
-- 
https://code.launchpad.net/~eduardo-mucelli/cairo-dock-plug-ins-extras/Twitter/+merge/105573
Your team Cairo-Dock Team is requested to review the proposed merge of lp:~eduardo-mucelli/cairo-dock-plug-ins-extras/Twitter into lp:cairo-dock-plug-ins-extras.
=== removed file 'Twitter/.keys'
--- Twitter/.keys	2011-12-17 00:14:56 +0000
+++ Twitter/.keys	1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
-OzZoSVpO6PZqByM15MsLlg mKsbuXgpHEO6C2axmUI8cPUt0ZPCbDb67uvT5wOIW1s

=== added file 'Twitter/.keys.cfg'
--- Twitter/.keys.cfg	1970-01-01 00:00:00 +0000
+++ Twitter/.keys.cfg	2012-05-13 01:03:19 +0000
@@ -0,0 +1,7 @@
+[twitter]
+consumer_key: OzZoSVpO6PZqByM15MsLlg
+consumer_secret: mKsbuXgpHEO6C2axmUI8cPUt0ZPCbDb67uvT5wOIW1s
+
+[identica]
+consumer_key: 490af31436e6f4cbd7bbf76e3a84ee5d
+consumer_secret: 9796c4e80d43373135849c014b227182

=== modified file 'Twitter/ChangeLog'
--- Twitter/ChangeLog	2012-04-15 17:42:50 +0000
+++ Twitter/ChangeLog	2012-05-13 01:03:19 +0000
@@ -1,3 +1,4 @@
+0.3: (May/13/2012): Added support to Identi.ca! Many code changes, refactoring, and improving.
 0.2.2: (April/15/2012): Configurable notifications.
 0.2.1: (March/22/2012): Added user timeline.
 0.2: (March/19/2012): Possible to retweet tweets. Tweets being shown with standard menu.

=== modified file 'Twitter/Twitter'
--- Twitter/Twitter	2012-04-17 18:40:21 +0000
+++ Twitter/Twitter	2012-05-13 01:03:19 +0000
@@ -27,154 +27,19 @@
 # To see the received direct messages right-click on the icon -> Twitter -> [New] direct messages. You can reply any of them by left-clicking on it.
 # To see some user's info right-click on the icon -> Twitter -> Info
 
-import os, webbrowser, simplejson, threading, Queue, urllib2
-from oauth import oauth
-from util import *
+import os, webbrowser, Queue
 
-from http import post, get #, stream
-import emblem, menu
-from message import DirectMessage, Tweet
+from networks import Networks                                                   # networks.py
+from user import User                                                           # user.py
+import emblem, menu                                                             # emblem.py, menu.py
+from message import DirectMessage, Tweet                                        # message.py
+from util import *                                                              # util.py
 
 from CDApplet import CDApplet, _
-# TODO import ConfigParser later conver files to config syntax
-
-class TwitterOauth:
-  def __init__(self):
-    self.request_token_url  = 'https://twitter.com/oauth/request_token'
-    self.access_token_url   = 'https://twitter.com/oauth/access_token'
-    self.authorize_url      = 'https://twitter.com/oauth/authorize'
-
-    consumer_key, consumer_secret = read_consumer_key_and_secret()
-    self.consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
-    self.signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
-    self.request_token = None
-    self.access_token = None
-    
-  def get_authorization_url(self):
-    self.request_token = self.get_unauthorized_request_token()
-    oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
-                                                               token = self.request_token,
-                                                               http_url = self.authorize_url)
-    oauth_request.sign_request(self.signature_method, self.consumer, self.request_token)
-    return oauth_request.to_url()
-
-  def get_unauthorized_request_token(self):
-    oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, http_url = self.request_token_url)
-    oauth_request.sign_request(self.signature_method, self.consumer, None)
-    url = oauth_request.to_url()
-    response = get(url)
-    token = oauth.OAuthToken.from_string(response)
-    return token
-
-  # Exchange request token for access token
-  def get_access_token_and_secret(self, pin):
-    oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
-                                                               http_url = self.access_token_url,
-                                                               verifier = pin,
-                                                               token = self.request_token)
-    oauth_request.sign_request(self.signature_method, self.consumer, self.request_token)
-    url = oauth_request.to_url()
-    response = get(url)
-    self.access_token = oauth.OAuthToken.from_string(response)                      # create both .key and .secret attributes
-    return self.access_token.key, self.access_token.secret
-    
-# TODO: Check also the possible inheritance with TwitterOauth
-class API():
-  def __init__(self, access_key, access_secret):
-    self.signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
-    consumer_key, consumer_secret = read_consumer_key_and_secret()
-    self.consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
-    self.access_token = oauth.OAuthToken(access_key, access_secret)
-
-class TwitterStreamAPI(API):
-  def __init__(self, access_key, access_secret, callback):
-    API.__init__(self, access_key, access_secret)
-
-    self.user_stream_url = "https://userstream.twitter.com/2/user.json";
-    self.callback = callback                                                        # called as soon as a new entry on the stream appears
-    thread = threading.Thread(target=self.tweet_streaming)                          # keep checking for new entries on the stream
-    thread.start()                                                                  # run, forrest run
-
-  def tweet_streaming(self):
-    oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
-                                                               token = self.access_token,
-                                                               http_url = self.user_stream_url)
-    oauth_request.sign_request(self.signature_method, self.consumer, self.access_token)
-
-    url = oauth_request.to_url()
-    req = urllib2.urlopen(url)
-   
-    buffer = ''
-    while True:
-      chunk = req.read(1)                                               # read character per character from the connection ...
-      if not chunk:
-        break
-
-      buffer += chunk
-      tweets = buffer.split("\n",1)                                     # ... until find the end of a tweet marked with a '\n'
-      if len(tweets) > 1:
-        content = tweets[0]
-        if "text" in content:
-          content = simplejson.loads(content)
-          logp("Received from Twitter Stream: %s" % content)
-          self.callback(content)                                        # at the moment this method is called 'on_receive_new_entry_into_stream_callback'
-        buffer = tweets[1]
- 
-class TwitterAPI(API):
-  def __init__(self, access_key, access_secret):
-    API.__init__(self, access_key, access_secret)
-    
-    self.update_url               = 'https://api.twitter.com/1/statuses/update.json'
-    self.home_timeline_url        = 'https://api.twitter.com/1/statuses/home_timeline.json'
-    self.direct_messages_url      = 'https://api.twitter.com/1/direct_messages.json'
-    self.new_direct_messages_url  = 'https://api.twitter.com/1/direct_messages/new.json'
-    self.verify_credentials_url   = 'https://api.twitter.com/1/account/verify_credentials.json'
-    self.user_timeline_url        = 'https://api.twitter.com/1/statuses/user_timeline.json'
-    self.retweet_url_prefix       = 'https://api.twitter.com/1/statuses/retweet/'            # lacks the id of the tweet to be retweeted
-    
-  def dispatch(self, url, mode, parameters={}):
-    oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
-                                                             token = self.access_token,
-                                                             http_url = url,
-                                                             parameters = parameters,
-                                                             http_method = mode)
-    oauth_request.sign_request(self.signature_method, self.consumer, self.access_token)
-    if mode == "GET":
-      url = oauth_request.to_url()
-      response = get(url) 
-      return simplejson.loads(response)
-    elif mode == "POST":
-      header = oauth_request.to_header()
-      post(url, parameters, header)  
-      
-  def tweet(self, message):                                                                 # popularly "send a tweet"
-    self.dispatch(self.update_url, "POST", {'status':message})
-    
-  def retweet(self, tweet_id):
-    url = "%s%s.json" % (self.retweet_url_prefix, tweet_id)
-    self.dispatch(url, "POST")
-
-  def new_direct_message(self, message, destinatary):
-    self.dispatch(self.new_direct_messages_url, "POST", {'text':message, 'screen_name':destinatary})
-
-  def home_timeline(self):
-    return self.dispatch(self.home_timeline_url, "GET")
-  
-  #TODO: Use it.
-  def user_timeline(self):
-    return self.dispatch(self.user_timeline_url, "GET")
-
-  def direct_messages(self):
-    return self.dispatch(self.direct_messages_url, "GET")
-
-  def verify_credentials(self):
-    return self.dispatch(self.verify_credentials_url, "GET")
-
-class User:
-  def __init__(self, screen_name="", access_key="", access_secret=""):
-    self.screen_name = screen_name
-    self.access_key = access_key
-    self.access_secret = access_secret
+
+# Twitter ----> Networks |----> twitter  ---> Oauth, TwitterAPI, TwitterStreamAPI
+#                        |
+#                        |----> identica ---> Oauth, IdenticaAPI
 
 class Applet(CDApplet):
 
@@ -187,7 +52,7 @@
   def refresh_emblem_counter(self):
     counter = self.tweet_stream.qsize() + self.message_stream.qsize()
     if counter > 0:
-      self.emblem.update(counter)                                                 # create the emblem with the counter
+      self.emblem.update(counter)                                                               # create the emblem with the counter
       self.icon.SetIcon(self.emblem.emblem)
     else:
       self.icon.SetIcon(os.path.abspath("./icon"))  
@@ -221,14 +86,14 @@
   # TwitterStreamAPI(access_key, access_secret, self.on_receive_new_entry_into_stream_callback)
   def on_receive_new_entry_into_stream_callback(self, entry):
     if 'user' in entry:                                                                     # tweet
-      if not entry['user']['screen_name'] == self.user.screen_name:                         # not sent by the own user
+      if not entry['user']['screen_name'] == self.twitter.user.screen_name:                         # not sent by the own user
         logp("Inserting new tweet on the stream Queue: %s" % entry)
         self.tweet_stream.put(entry)                                                        # put the new tweet on the stream queue
         self.refresh_emblem_counter()
         if self.config['alert']:                                                            # user wants alert?
           self.send_alert()
     elif 'direct_message' in entry:                                                         # direct messages
-      if not entry['direct_message']['sender']['screen_name'] == self.user.screen_name:     # not sent by the own user
+      if not entry['direct_message']['sender']['screen_name'] == self.twitter.user.screen_name:     # not sent by the own user
         logp("Inserting new message on the message Queue: %s" % entry)
         self.message_stream.put(entry)                                                      # put the new message on the message queue
         self.refresh_emblem_counter()
@@ -238,7 +103,7 @@
   def show_user_timeline(self):
     self.inform_start_of_waiting_process()
     
-    timeline = self.api.user_timeline()
+    timeline = self.twitter_api.user_timeline()
     if len(timeline) > 0:
       ut_menu = menu.Menu(self.icon)                                                        # callback not set since there is no action when clicking ...
       for status in timeline:                                                               # ... on the menu generated from this list
@@ -272,7 +137,7 @@
   def show_home_timeline(self):
     self.inform_start_of_waiting_process()
     
-    timeline = self.api.home_timeline()
+    timeline = self.twitter_api.home_timeline()
     if len(timeline) > 0:
       ht_menu = menu.Menu(self.icon, self.on_tweet_list_menu_clicked)
       for status in timeline:
@@ -305,7 +170,7 @@
   def show_direct_messages(self):
     self.inform_start_of_waiting_process()
 
-    messages = self.api.direct_messages()
+    messages = self.twitter_api.direct_messages()
     if len(messages) > 0:
       dm_menu = menu.Menu(self.icon, self.on_direct_messages_list_menu_clicked)
       for status in messages:
@@ -328,99 +193,143 @@
 
   def tweet(self, message):                                                         # popularly "send a tweet"
     self.inform_start_of_waiting_process()
-    self.api.update_status(message)
+    self.twitter_api.update_status(message)
     self.inform_end_of_waiting_process()
 
   def show_credentials(self):
     self.inform_start_of_waiting_process()
-    credentials = self.api.verify_credentials()
+    credentials = self.twitter_api.verify_credentials()
     message = _("%s [<b>%s</b>]\nFollowers: %s\nFriends: %s\nTweets: %s\n") % (credentials['name'], credentials['screen_name'], credentials['followers_count'], credentials['friends_count'], credentials['statuses_count'])
     dialog = {'use-markup':True}
     self.inform_end_of_waiting_process()
     self.show_popup_message(message, dialog)
 
-  # Applet methods
+  # Dialogs for some kind of input
 
   def ask_for_direct_message_reply(self, destinatary):
     dialog = {'buttons':'ok;cancel'}
     widget = {'widget-type':'text-entry', 'nb-chars':self.tweet_length + 1}         # 140 characters max, a direct message
-    self.show_popup_message((_("%s, write a reply to %s")) % (self.user.screen_name, destinatary), dialog, widget)
+    self.show_popup_message((_("%s, write a reply to %s")) % (self.twitter.user.screen_name, destinatary), dialog, widget)
     self.dialog_type = self.responding_sending_direct_message_reply
     self.replying_direct_message_to = destinatary
+    
+  def responding_sending_direct_message_reply(self, content):
+    logp("Sending a direct message '%s'" % content)
+    self.twitter_api.new_direct_message(content, self.replying_direct_message_to)
 
-  # Opens the dialog to be filled with the tweet and also
-  # informs about tweet longer than 140 characters
+  # Opens the dialog to be filled with the tweet and also informs about tweet longer than 140 characters
   def ask_for_tweet(self, default="", warning=False):
     dialog = {'buttons':'ok;cancel'}
     widget = {'widget-type':'text-entry', 'nb-chars':self.tweet_length + 1, 'initial-value':default}  # 140 characters max, a tweet :)
     if not warning:
-      message = _("%s, send a tweet") % self.user.screen_name
+      message = _("%s, send a tweet") % self.twitter.user.screen_name
       self.dialog_type = self.responding_tweet
     else:                                                                                             # user entered more than 140 characters
       message = _("Your tweet has more than %d characters, please make it shorter") % self.tweet_length
       self.dialog_type = self.responding_tweet
     self.show_popup_message(message, dialog, widget)
     
+  def responding_to_tweet(self, content):
+    if len(content) > self.tweet_length:
+      self.ask_for_tweet(content, warning=True)
+    else:
+      logp("Sending a tweet '%s'" % content)
+      self.twitter_api.tweet(content)
+      self.identica_api.tweet(content)
+    
   def ask_for_retweet(self, tweet_id):
     dialog = {'buttons':'ok;cancel'}
-    self.show_popup_message((_("%s, retweet?")) % self.user.screen_name, dialog)
+    self.show_popup_message((_("%s, retweet?")) % self.twitter.user.screen_name, dialog)
     self.dialog_type = self.responding_retweet
     self.retweet_tweet_id = tweet_id
-
-  # TODO: Implement it as a config file using screen_name as section index
-  def read_user_data(self):
-    """Read the users file formated as Screen Name<space>Access Key<space>Access Secret"""
-    found = False
-    if os.path.exists(self.user_file):
-      if os.path.getsize(self.user_file) > 0:
-        f = open(self.user_file, "rb")
-        data = f.read()
-        self.user.screen_name, self.user.access_key, self.user.access_secret = data.split()   # split the line by space token
-        f.close()
-        found = True
-    return found
-
-  def write_user_data(self):
-    f = open(self.user_file, 'w')
-    f.write("%s %s %s" % (self.user.screen_name, self.user.access_key, self.user.access_secret))
-    f.close()
+    
+  def responding_to_retweet(self):
+    logp("Retweeting")
+    self.twitter_api.retweet(self.retweet_tweet_id)
+
+  # Dialogs for the initial wizard to authorize the user
+  # Initial Information -> Ask for screen name (username) -> Ask for authorization (open browser) -> Insert PIN -> Succesful connected
 
   def show_initial_informations(self):
-    message = _("Twitter Applet needs your nickname, and an authorization\nthat you accept it to connect on your Twitter account")
+    if not self.adding_identica:
+      message = _("Twitter Applet needs your nickname, and an authorization\nthat you accept it to connect on your Twitter account")
+    else:
+      message = _("Identi.ca Applet needs your nickname, and an authorization\nthat you accept it to connect on your Identi.ca account")
     dialog = {'buttons':'next'}
     self.show_popup_message(message, dialog)
     self.dialog_type = self.responding_initial_informations
+    
+  def responding_to_initial_informations(self):
+    self.ask_for_screen_name()
 
   def ask_for_screen_name(self):
-    message = _("What is your Twitter nickname?")
+    if not self.adding_identica:
+      message = _("What is your Twitter nickname?")
+    else:
+      message = _("What is your Identi.ca nickname?")
     dialog = {'buttons':'next'}
     widget = {'widget-type':'text-entry'}
     self.show_popup_message(message, dialog, widget)
     self.dialog_type = self.responding_screen_name
 
+  def responding_to_screen_name(self, content):
+    logp("Receiving screen name '%s'" % content)
+    if not self.adding_identica:
+      self.twitter.user.screen_name = content
+    else:
+      self.identica.user.screen_name = content
+    self.ask_for_authorization()
+
   def ask_for_authorization(self):
-    authorization_url = self.twitter_auth.get_authorization_url()
+    if not self.adding_identica:
+      authorization_url = self.twitter_auth.get_authorization_url()
+      network = "Twitter"
+    else:
+      authorization_url = self.identica_auth.get_authorization_url()
+      network = "Identi.ca"
     logp("Opening the auth URL '%s'" % authorization_url)
     dialog = {'buttons':'next'}
     try:
       webbrowser.open(authorization_url)
-      message = _("Twitter applet needs you to give the authorization. Authorization page was opened on your browser. As soon as you authorize it, copy the PIN number that will be shown, and close this dialog")
+      message = _("%s applet needs you to give the authorization. Authorization page was opened on your browser. As soon as you authorize it, copy the PIN number that will be shown, and go to the next dialog" % network)
       self.show_popup_message(message, dialog)
     except webbrowser.Error:    
-      message = _("Twitter applet needs you to give the authorization. Copy the address bellow and access it with your browser. Copy the PIN number that will be shown as soon as you authorize")
+      message = _("%s applet needs you to give the authorization. Copy the address bellow and access it with your browser. Copy the PIN number that will be shown as soon as you authorize" % network)
       widget = {'widget-type':'text-entry', 'initial-value':authorization_url}
       self.show_popup_message(message, dialog, widget)
     self.dialog_type = self.responding_authorization
 
+  def responding_to_authorization(self):
+    logp("Asking for PIN")
+    self.ask_for_pin_number()                                                           # ask for the PIN number received when acessed the auth URL
+
   def ask_for_pin_number(self):
-    message = _("Enter the PIN number on the authorization page")
+    message = _("Enter the PIN number shown on the authorization page")
     dialog = {'buttons':'next'}
     widget = {'widget-type':'text-entry'}
     self.show_popup_message(message, dialog, widget)
     self.dialog_type = self.responding_pin
-
-  def show_popup_successful_connection(self):
-    self.show_popup_message(_("Successfully connected with Twitter"))
+    
+  def responding_to_pin(self, content):
+    logp("Receiving PIN: %s" % content)
+    if not self.adding_identica:
+      self.twitter.user.access_key, self.twitter.user.access_secret = self.twitter_auth.get_access_token_and_secret(content)
+      logp("Writing user data")
+      self.twitter.user.write()                                                           # writing user's access token and access secret to be ...
+      self.twitter_api = self.twitter.get_api(self.on_receive_new_entry_into_stream_callback)     # ... used here, check twitter.py method get_api
+      if self.twitter_api:
+        self.show_popup_message(_("Successfully connected with Twitter"))
+      else:
+        logm("A problem has occurred while getting access to Twitter API")
+    else:
+      self.identica.user.access_key, self.identica.user.access_secret = self.identica_auth.get_access_token_and_secret(content)
+      logp("Writing user data")
+      self.identica.user.write()                                                           # writing user's access token and access secret to be ...
+      self.identica_api = self.identica.get_api()                                          # ... used here, check identica.py method get_api
+      if self.identica_api:
+        self.show_popup_message(_("Successfully connected with Identi.ca"))
+      else:
+        logm("A problem has occurred while getting access to the Identi.ca API")
 
   def show_popup_message(self, message, dialog={}, widget={}):
     dialog_attributes = {'message':message}
@@ -429,6 +338,8 @@
     widget_attributes.update(widget)
     self.icon.PopupDialog (dialog_attributes, widget_attributes)
 
+  # Menus
+
   def build_direct_messages_menu(self):
     direct_messages_menu = []
     if self.message_stream.empty():
@@ -477,14 +388,37 @@
         'icon'  : os.path.abspath("./data/tweet.png")
     })
     self.icon.AddMenuItems(user_timeline_menu)
+    
+  def build_identica_menu(self):
+    identica_menu = []
+    menu = {
+        'type'  : CDApplet.MENU_SUB_MENU,
+        'label' : _("Identi.ca"),
+        'id'    : self.identica_menu_id,
+        'icon'  : os.path.abspath("./data/identica.png")
+    }
+    sub_menu = {
+        'type'  : CDApplet.MENU_ENTRY,
+        'label' : _("Add"),
+        'id'    : self.add_identica_menu_id,
+        'menu'  : self.identica_menu_id
+    }
+    identica_menu.append (menu)
+    identica_menu.append (sub_menu)
+    self.icon.AddMenuItems(identica_menu)
 
   def __init__(self):
-    self.user = User()
-    self.user_file = os.path.abspath(os.path.join(os.getcwd(),'..','..','.twitter_users'))      # ~/.config/cairo-dock/.twitter_users
-    self.twitter_auth = TwitterOauth()
-    
-    self.api = None
-    self.stream_api = None
+    
+    self.networks = Networks()
+    
+    self.twitter = self.networks.twitter()
+    self.twitter_auth = self.twitter.Oauth()
+    self.twitter_api = None
+
+    self.identica = self.networks.identica()
+    self.identica_auth = self.identica.Oauth()
+    self.identica_api = None
+    self.adding_identica = False
     
     (self.responding_screen_name, self.responding_authorization, self.responding_pin,
     self.responding_success, self.responding_tweet, self.responding_initial_informations,
@@ -497,6 +431,8 @@
     self.credentials_menu_id      = 2000
     self.tweets_menu_id           = 3000
     self.user_timeline_menu_id    = 4000
+    self.identica_menu_id         = 5000
+    self.add_identica_menu_id     = 5001
     
     self.tweet_length             = 140
     
@@ -508,14 +444,14 @@
   # Inherited methods from CDApplet
   def begin(self):
     logp("Looking for user ...")
-    if not self.read_user_data():                                                                     # first time for the user
+    if self.twitter.user.read():                                                                      # first time for the user
+      logp("User '%s' found" % self.twitter.user.screen_name)
+      self.twitter_api = self.twitter.get_api(self.on_receive_new_entry_into_stream_callback)         # pass stream api callback, kind of ugly it yet
+      if self.identica.user.read():
+        self.identica_api = self.identica.get_api()
+    else:                                                                                             # user not found
       logm("User not found")
       self.show_initial_informations()                                                                # start the wizard
-    else:                                                                                             # user not found
-      logp("User '%s' found" % self.user.screen_name)
-      self.api = TwitterAPI(self.user.access_key, self.user.access_secret)                            # getting control over the api
-      # setting the callback to receive the data of every entry on the stream
-      self.stream_api = TwitterStreamAPI(self.user.access_key, self.user.access_secret, self.on_receive_new_entry_into_stream_callback)
 
   def get_config(self, keyfile):
     self.config['emblem_size'] = keyfile.get('Configuration', 'emblem_size')
@@ -532,36 +468,19 @@
   def on_answer_dialog(self, key, content):
     if (key == 0 or key == -1):                                                                       # ... and pressed Ok or Enter
       if self.dialog_type == self.responding_initial_informations:
-        self.ask_for_screen_name()
+        self.responding_to_initial_informations()
       elif self.dialog_type == self.responding_screen_name:                                           # user typed screen name ...
-        logp("Receiving screen name '%s'" % content)
-        self.user.screen_name = content
-        self.ask_for_authorization()
+        self.responding_to_screen_name(content)
       elif self.dialog_type == self.responding_authorization:
-        logp("Asking for PIN")
-        self.ask_for_pin_number()                                                     # ask for the PIN number received when acessed the auth URL
-      elif self.dialog_type == self.responding_pin:                                   # user typed the PIN number
-        logp("Receiving PIN: %s" % content)
-        self.user.access_key, self.user.access_secret = self.twitter_auth.get_access_token_and_secret(content)
-        logp("Writing user data")
-        self.write_user_data()                                                        # writing the new users data
-        self.api = TwitterAPI(self.user.access_key, self.user.access_secret)          # getting control over the api
-        if self.api:
-          self.show_popup_successful_connection()
-        else:
-          logm("A problem has occurred while getting access to the API")
+        self.responding_to_authorization()
+      elif self.dialog_type == self.responding_pin:                                                   # user typed the PIN number
+        self.responding_to_pin(content)
       elif self.dialog_type == self.responding_tweet:
-        if len(content) > self.tweet_length:
-            self.ask_for_tweet(content, True)
-        else:
-          logp("Sending a tweet '%s'" % content)
-          self.api.tweet(content)
+        self.responding_to_tweet(content)
       elif self.dialog_type == self.responding_sending_direct_message_reply:
-        logp("Sending a direct message '%s'" % content)
-        self.api.new_direct_message(content, self.replying_direct_message_to)
+        self.responding_to_sending_direct_message_reply(content)
       elif self.dialog_type == self.responding_retweet:
-        logp("Retweeting")
-        self.api.retweet(self.retweet_tweet_id)
+        self.responding_to_retweet()
 
   # If there is nothing to be seen, the left click opens the dialog to write a tweet
   # Otherwise we check if there is something new, likely to be what the user wants
@@ -582,6 +501,8 @@
     self.build_user_timeline_menu()
     self.build_direct_messages_menu()
     self.build_tweets_menu()
+    if not self.identica_api:
+      self.build_identica_menu()
 
   def on_menu_select(self, selected_menu):
     if selected_menu == self.direct_messages_menu_id:
@@ -598,6 +519,9 @@
         self.show_new_tweets()                                                        # show new tweets
     elif selected_menu == self.user_timeline_menu_id:
       self.show_user_timeline()
+    elif selected_menu == self.add_identica_menu_id:
+      self.adding_identica = True
+      self.show_initial_informations()
 
 if __name__ == '__main__':
   Applet().run()

=== modified file 'Twitter/Twitter.conf'
--- Twitter/Twitter.conf	2012-04-22 15:10:32 +0000
+++ Twitter/Twitter.conf	2012-05-13 01:03:19 +0000
@@ -1,4 +1,4 @@
-#0.2.3
+#0.3
 
 #[gtk-about]
 [Icon]

=== modified file 'Twitter/auto-load.conf'
--- Twitter/auto-load.conf	2012-04-22 15:10:32 +0000
+++ Twitter/auto-load.conf	2012-05-13 01:03:19 +0000
@@ -10,7 +10,7 @@
 category = 3
 
 # Version of the applet; change it everytime you change something in the config file. Don't forget to update the version both in this file and in the config file.
-version = 0.2.3
+version = 0.3
 
 # Whether the applet can be instanciated several times or not.
 multi-instance = true

=== added file 'Twitter/data/identica.png'
Binary files Twitter/data/identica.png	1970-01-01 00:00:00 +0000 and Twitter/data/identica.png	2012-05-13 01:03:19 +0000 differ
=== modified file 'Twitter/emblem.py'
--- Twitter/emblem.py	2012-03-21 00:02:15 +0000
+++ Twitter/emblem.py	2012-05-13 01:03:19 +0000
@@ -5,6 +5,16 @@
 #
 # Author: Eduardo Mucelli Rezende Oliveira
 # E-mail: edumucelli@xxxxxxxxx or eduardom@xxxxxxxxxxx
+#
+# 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 3 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.
 
 import os
 

=== added file 'Twitter/identica.py'
--- Twitter/identica.py	1970-01-01 00:00:00 +0000
+++ Twitter/identica.py	2012-05-13 01:03:19 +0000
@@ -0,0 +1,113 @@
+#!/usr/bin/python
+
+# This is a part of the external Twitter applet for Cairo-Dock
+#
+# Author: Eduardo Mucelli Rezende Oliveira
+# E-mail: edumucelli@xxxxxxxxx or eduardom@xxxxxxxxxxx
+#
+# 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 3 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.
+
+from oauth import oauth
+import urllib2, urllib
+
+from user import User
+from http import post, get
+from util import *
+
+class Identica:
+  def __init__(self):
+    self.name = "identica"
+    self.user = User(network=self.name)
+    
+  def get_api(self):
+    if self.user_exists():
+      logp("Getting Identi.ca API")
+      return self.IdenticaAPI(self.user.access_key, self.user.access_secret)
+    else:
+      return False
+      
+  def user_exists(self):
+    return self.user.access_secret and self.user.access_secret
+    
+  class Oauth:
+    def __init__(self):
+      self.request_token_url  = 'https://identi.ca/api/oauth/request_token'
+      self.access_token_url   = 'https://identi.ca/api/oauth/access_token'
+      self.authorize_url      = 'https://identi.ca/api/oauth/authorize'
+
+      consumer_key, consumer_secret = read_consumer_key_and_secret("identica")
+      self.consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
+      self.signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
+      
+    def get_authorization_url(self):
+      self.request_token = self.get_unauthorized_request_token()
+      oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
+                                                                 token = self.request_token,
+                                                                 http_url = self.authorize_url)
+      oauth_request.sign_request(self.signature_method, self.consumer, self.request_token)
+      return oauth_request.to_url()
+
+    def get_unauthorized_request_token(self):
+      oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, http_method="POST", callback="oob", parameters={"source": "Cairo-Dock"}, http_url = self.request_token_url)
+      oauth_request.sign_request(self.signature_method, self.consumer, None)
+      url = oauth_request.to_url()
+      header = oauth_request.to_header()
+      response = post(url, "", header)
+      token = oauth.OAuthToken.from_string(response)
+      return token
+      
+    # Exchange request token for access token
+    def get_access_token_and_secret(self, pin):
+      oauth_request = oauth.OAuthRequest.from_consumer_and_token(oauth_consumer=self.consumer, 
+            token = self.request_token,  
+            http_method="POST",
+            callback="oob",
+            verifier = pin,
+            parameters={"oauth_verifier": str(pin), "source": "Cairo-Dock"},
+            http_url=self.access_token_url)
+
+      oauth_request.sign_request(self.signature_method, self.consumer, self.request_token)
+      url = oauth_request.to_url()
+      header = oauth_request.to_header()
+      response = post(url, "", header)
+      access_token = oauth.OAuthToken.from_string(response)
+      return access_token.key, access_token.secret
+      
+  class IdenticaAPI():
+    def __init__(self, access_key, access_secret):
+      self.signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
+      consumer_key, consumer_secret = read_consumer_key_and_secret("identica")
+      self.consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
+      self.access_token = oauth.OAuthToken(access_key, access_secret)
+      print "=================="
+      print self.access_token
+      print "=================="
+      
+      self.update_url = 'http://identi.ca/api/statuses/update.json'
+
+    def dispatch(self, url, mode, parameters={}):
+      oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
+                                                               token = self.access_token,
+                                                               http_url = url,
+                                                               parameters = parameters,
+                                                               http_method = mode)
+      oauth_request.sign_request(self.signature_method, self.consumer, self.access_token)
+      print oauth_request.to_url()
+      if mode == "GET":
+        url = oauth_request.to_url()
+        response = get(url) 
+        return simplejson.loads(response)
+      elif mode == "POST":
+        header = oauth_request.to_header()
+        post(url, parameters, header)  
+
+    def tweet(self, message):                                                                 # popularly "send a tweet"
+      self.dispatch(self.update_url, "POST", {'status':message})

=== modified file 'Twitter/message.py'
--- Twitter/message.py	2012-03-19 00:02:27 +0000
+++ Twitter/message.py	2012-05-13 01:03:19 +0000
@@ -4,6 +4,16 @@
 #
 # Author: Eduardo Mucelli Rezende Oliveira
 # E-mail: edumucelli@xxxxxxxxx or eduardom@xxxxxxxxxxx
+#
+# 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 3 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.
 
 # Can be either a Tweet or a Direct Message
 class Message:

=== added file 'Twitter/networks.py'
--- Twitter/networks.py	1970-01-01 00:00:00 +0000
+++ Twitter/networks.py	2012-05-13 01:03:19 +0000
@@ -0,0 +1,26 @@
+#!/usr/bin/python
+
+# This is a part of the external Twitter applet for Cairo-Dock
+#
+# Author: Eduardo Mucelli Rezende Oliveira
+# E-mail: edumucelli@xxxxxxxxx or eduardom@xxxxxxxxxxx
+#
+# 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 3 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.
+
+class Networks:
+
+  def twitter(self):
+    from twitter import Twitter
+    return Twitter()
+  
+  def identica(self):
+    from identica import Identica
+    return Identica()

=== added file 'Twitter/twitter.py'
--- Twitter/twitter.py	1970-01-01 00:00:00 +0000
+++ Twitter/twitter.py	2012-05-13 01:03:19 +0000
@@ -0,0 +1,181 @@
+#!/usr/bin/python
+
+# This is a part of the external Twitter applet for Cairo-Dock
+#
+# Author: Eduardo Mucelli Rezende Oliveira
+# E-mail: edumucelli@xxxxxxxxx or eduardom@xxxxxxxxxxx
+#
+# 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 3 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.
+
+from oauth import oauth
+import simplejson, threading, urllib2
+
+from user import User
+from http import post, get #, stream
+from util import *
+
+class Twitter:
+  def __init__(self):
+    self.name = "twitter"
+    self.user = User(network=self.name)
+    
+  def get_api(self, stream_api_callback):
+    # start StreamAPI and return the instance of TwitterAPI
+    if self.user_exists():
+      logp("Getting Twitter API")
+      self.TwitterStreamAPI(self.user.access_key, self.user.access_secret, stream_api_callback)
+      return self.TwitterAPI(self.user.access_key, self.user.access_secret)
+    else:
+      return False
+      
+  def user_exists(self):
+    return self.user.access_secret and self.user.access_secret
+  
+  class Oauth():
+    def __init__(self):
+      self.request_token_url  = 'https://twitter.com/oauth/request_token'
+      self.access_token_url   = 'https://twitter.com/oauth/access_token'
+      self.authorize_url      = 'https://twitter.com/oauth/authorize'
+
+      consumer_key, consumer_secret = read_consumer_key_and_secret("twitter")
+      self.consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
+      self.signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
+
+      self.request_token = None
+      self.access_token = None
+      
+    def get_authorization_url(self):
+      self.request_token = self.get_unauthorized_request_token()
+      oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
+                                                                 token = self.request_token,
+                                                                 http_url = self.authorize_url)
+      oauth_request.sign_request(self.signature_method, self.consumer, self.request_token)
+      return oauth_request.to_url()
+
+    def get_unauthorized_request_token(self):
+      oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer, http_url = self.request_token_url)
+      oauth_request.sign_request(self.signature_method, self.consumer, None)
+      url = oauth_request.to_url()
+      response = get(url)
+      token = oauth.OAuthToken.from_string(response)
+      return token
+
+    # Exchange request token for access token
+    def get_access_token_and_secret(self, pin):
+      oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
+                                                                 http_url = self.access_token_url,
+                                                                 verifier = pin,
+                                                                 token = self.request_token)
+      oauth_request.sign_request(self.signature_method, self.consumer, self.request_token)
+      url = oauth_request.to_url()
+      response = get(url)
+      self.access_token = oauth.OAuthToken.from_string(response)                      # create both .key and .secret attributes
+      return self.access_token.key, self.access_token.secret
+      
+  class API():
+    def __init__(self, access_key, access_secret):
+      self.signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
+      consumer_key, consumer_secret = read_consumer_key_and_secret()
+      self.consumer = oauth.OAuthConsumer(consumer_key, consumer_secret)
+      self.access_token = oauth.OAuthToken(access_key, access_secret)
+
+  class TwitterStreamAPI(API):
+    def __init__(self, access_key, access_secret, callback):
+      Twitter.API.__init__(self, access_key, access_secret)
+
+      self.user_stream_url = "https://userstream.twitter.com/2/user.json";
+      self.callback = callback                                                        # called as soon as a new entry on the stream appears
+      thread = threading.Thread(target=self.tweet_streaming)                          # keep checking for new entries on the stream
+      thread.start()                                                                  # run, forrest run
+
+    def tweet_streaming(self):
+      oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
+                                                                 token = self.access_token,
+                                                                 http_url = self.user_stream_url)
+      oauth_request.sign_request(self.signature_method, self.consumer, self.access_token)
+
+      url = oauth_request.to_url()
+      req = urllib2.urlopen(url)
+     
+      buffer = ''
+      while True:
+        chunk = req.read(1)                                             # read character per character from the connection ...
+        if not chunk:
+          break
+
+        buffer += chunk
+        tweets = buffer.split("\n",1)                                   # ... until find the end of a tweet marked with a '\n'
+        if len(tweets) > 1:
+          content = tweets[0]
+          if "text" in content:
+            content = simplejson.loads(content)
+            logp("Received from Twitter Stream: %s" % content)
+            self.callback(content)                                      # callback == Twitter.on_receive_new_entry_into_stream_callback
+          buffer = tweets[1]
+   
+  class TwitterAPI(API):
+    def __init__(self, access_key, access_secret):
+      Twitter.API.__init__(self, access_key, access_secret)
+      
+      self.update_url               = 'https://api.twitter.com/1/statuses/update.json'
+      self.home_timeline_url        = 'https://api.twitter.com/1/statuses/home_timeline.json'
+      self.direct_messages_url      = 'https://api.twitter.com/1/direct_messages.json'
+      self.new_direct_messages_url  = 'https://api.twitter.com/1/direct_messages/new.json'
+      self.verify_credentials_url   = 'https://api.twitter.com/1/account/verify_credentials.json'
+      self.user_timeline_url        = 'https://api.twitter.com/1/statuses/user_timeline.json'
+      self.retweet_url_prefix       = 'https://api.twitter.com/1/statuses/retweet/'            # lacks the id of the tweet to be retweeted
+      
+    def dispatch(self, url, mode, parameters={}):
+      oauth_request = oauth.OAuthRequest.from_consumer_and_token(self.consumer,
+                                                               token = self.access_token,
+                                                               http_url = url,
+                                                               parameters = parameters,
+                                                               http_method = mode)
+      oauth_request.sign_request(self.signature_method, self.consumer, self.access_token)
+      if mode == "GET":
+        url = oauth_request.to_url()
+        response = get(url) 
+        return simplejson.loads(response)
+      elif mode == "POST":
+        header = oauth_request.to_header()
+        post(url, parameters, header)  
+
+    # TODO: If an user tries to post the same tweet twice on a short period of time,
+    # twitter is not going to allow, and a error 403 is thrown.
+    # This method should return True based on something like:
+    # try:
+    #   self.dispatch(self.update_url, "POST", {'status':message})
+    # except urllib2.HTTPError, err:
+    #   if err.code == 403:
+    #     return False
+    #   else:
+    #     return True
+    def tweet(self, message):                                                                 # popularly "send a tweet"
+      self.dispatch(self.update_url, "POST", {'status':message})
+      
+    def retweet(self, tweet_id):
+      url = "%s%s.json" % (self.retweet_url_prefix, tweet_id)
+      self.dispatch(url, "POST")
+
+    def new_direct_message(self, message, destinatary):
+      self.dispatch(self.new_direct_messages_url, "POST", {'text':message, 'screen_name':destinatary})
+
+    def home_timeline(self):
+      return self.dispatch(self.home_timeline_url, "GET")
+    
+    def user_timeline(self):
+      return self.dispatch(self.user_timeline_url, "GET")
+
+    def direct_messages(self):
+      return self.dispatch(self.direct_messages_url, "GET")
+
+    def verify_credentials(self):
+      return self.dispatch(self.verify_credentials_url, "GET")

=== added file 'Twitter/user.py'
--- Twitter/user.py	1970-01-01 00:00:00 +0000
+++ Twitter/user.py	2012-05-13 01:03:19 +0000
@@ -0,0 +1,44 @@
+#!/usr/bin/python
+
+# This is a part of the external Twitter applet for Cairo-Dock
+#
+# Author: Eduardo Mucelli Rezende Oliveira
+# E-mail: edumucelli@xxxxxxxxx or eduardom@xxxxxxxxxxx
+#
+# 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 3 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.
+
+import os
+
+class User:
+  def __init__(self, screen_name="", access_key="", access_secret="", network="twitter"):
+    self.screen_name = screen_name
+    self.access_key = access_key
+    self.access_secret = access_secret
+    self.user_file = os.path.abspath(os.path.join(os.getcwd(),'..','..','.%s_users' % network))      # ~/.config/cairo-dock/.twitter_users
+    
+  # TODO: Implement it as a config file using screen_name as section index
+  def read(self):
+    """Read the users file formated as Screen Name<space>Access Key<space>Access Secret"""
+    found = False
+    if os.path.exists(self.user_file):
+      if os.path.getsize(self.user_file) > 0:
+        f = open(self.user_file, "rb")
+        data = f.read()
+        self.screen_name, self.access_key, self.access_secret = data.split()                    # split the line by space token
+        f.close()
+        found = True
+    return found
+
+  def write(self):
+    f = open(self.user_file, 'w')
+    f.write("%s %s %s" % (self.screen_name, self.access_key, self.access_secret))
+    f.close()
+

=== modified file 'Twitter/util.py'
--- Twitter/util.py	2011-12-17 03:28:23 +0000
+++ Twitter/util.py	2012-05-13 01:03:19 +0000
@@ -4,6 +4,18 @@
 #
 # Author: Eduardo Mucelli Rezende Oliveira
 # E-mail: edumucelli@xxxxxxxxx or eduardom@xxxxxxxxxxx
+#
+# 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 3 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.
+
+import ConfigParser
 
 def logp (string):
     print "[+] Twitter: %s" % string
@@ -12,12 +24,19 @@
     print "[-] Twitter: %s" % string
 
 # Read the user's consumer key and secret necessary for the requests
-def read_consumer_key_and_secret():
-		try:
-			f = open('.keys')
-			data = f.read()
-			f.close()
-		except IOError:
-			logm("It was not possible to read the consumer key and secret, check the .keys file")
-		else:
-			return data.split()
+def read_consumer_key_and_secret(network="twitter"):
+  try:
+    config = ConfigParser.ConfigParser()
+    config.read('.keys.cfg')
+    logp("%s: Consumer key: %s\nConsumer secret: %s" % (network, config.get(network, 'consumer_key'), config.get(network, 'consumer_secret')) )
+    return config.get(network, 'consumer_key'), config.get(network, 'consumer_secret')
+  except configparser.Error:
+    logm("It was not possible to read the consumer key and secret for '%s', check the .keys.cgf file" % network)
+#		try:
+#			f = open('.keys')
+#			data = f.read()
+#			f.close()
+#		except IOError:
+#			logm("It was not possible to read the consumer key and secret, check the .keys file")
+#		else:
+#			return data.split()


Follow ups