← Back to team overview

cairo-dock-team team mailing list archive

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

 

Eduardo Mucelli Rezende Oliveira has proposed merging lp:~eduardo-mucelli/cairo-dock-plug-ins-extras/Quote 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/Quote/+merge/105581

Added Viedemerde.fr, and more documentation on the code. I hope this new documentation can make it easier to ours_en_pluche to get together with me on this applet :)
-- 
https://code.launchpad.net/~eduardo-mucelli/cairo-dock-plug-ins-extras/Quote/+merge/105581
Your team Cairo-Dock Team is requested to review the proposed merge of lp:~eduardo-mucelli/cairo-dock-plug-ins-extras/Quote into lp:cairo-dock-plug-ins-extras.
=== modified file 'Quote/ChangeLog'
--- Quote/ChangeLog	2012-03-28 20:50:03 +0000
+++ Quote/ChangeLog	2012-05-13 12:35:23 +0000
@@ -1,3 +1,4 @@
+0.1:(May/13/2012): Added Viedemerde.fr, and more documentation on the code
 0.0.9:(March/28/2012): Removed Grouphug.us because it is not offering the random quote anymore. Fixing the return from Bash and Qdb.
 0.0.8:(March/17/2010): Added Grouphug.us.
 0.0.7:(January/27/2010): Fixing the message when there were no translation for only one word. Adding Vidademerda.com.br. Adding next quote button in the PopupDialog.

=== modified file 'Quote/Quote'
--- Quote/Quote	2012-03-28 20:50:03 +0000
+++ Quote/Quote	2012-05-13 12:35:23 +0000
@@ -26,6 +26,18 @@
 
 from CDApplet import CDApplet, _
 
+# To add a new parser you have to:
+# 1 - Create a file NameOfTheSiteParser.py, see BashParser.py, QdbParser.py, etc
+# 1.1 - For specific parts on the creation of the parser, refer to BashParser, some documentation is there
+# 2 - Import it "from MyWebSiteParser (module name) import MyWebSiteParser (class name)"
+# 3 - Add one identificator for the parser, for example, "quotationspage" = 1. Look that we already have
+#     seven parsers = range(8). For each new one you add, you have  increment the parameter on the range method
+# 4 - On the fetch method, add a new block
+#     elif (self.source == mywebsite):
+#       parser = MyNewWebSiteParser
+#     And check if it is necessary to remove some extra character, check the documented part on the fetch method
+# 5 - Add the website option on the Quote.conf, see the [Configuration] section inside it
+
 from BashParser import BashParser                                       # Bash.org
 from QdbParser import QdbParser                                         # Qdb.us
 from XkcdbParser import XkcdbParser                                     # Xkcdb.com
@@ -33,8 +45,10 @@
 from DanstonchatParser import DanstonchatParser                         # Danstonchat.fr
 from JokestogoParser import JokestogoParser                             # Jokes2go.com
 from VidademerdaParser import VidademerdaParser                         # Vidademerda.com.br
+from ViedemerdeParser import ViedemerdeParser                           # Viedemerde.fr
 
-quotationspage, bash, xkcdb, qdb, danstonchat, jokestogo, vidademerda = range(7)  # quotationspage = 0, bash = 1, xkcdb = 2, qdb = 3, danstonchat = 4, jokestogo = 5, vidademerda = 6
+# quotationspage = 0, bash = 1, xkcdb = 2, qdb = 3, danstonchat = 4, jokestogo = 5, vidademerda = 6, viedemerde = 7
+quotationspage, bash, xkcdb, qdb, danstonchat, jokestogo, vidademerda, viedemerde = range(8)  
 
 class AgentOpener(FancyURLopener):
   """Masked user-agent otherwise the access would be forbidden"""
@@ -43,9 +57,9 @@
 class Interface:
 
   def __init__(self, source):
-    self.source = source
-    self.author = []
-    self.quote = []
+    self.source = source                                                # The source of the quotes, it is the 
+    self.author = []                                                    # List of authors of the current source
+    self.quote = []                                                     # List of quotes of the current source
 
   def fetch(self):
     if (self.source == quotationspage):
@@ -60,8 +74,10 @@
       parser = DanstonchatParser()                                      # DanstonchatParser.py
     elif (self.source == jokestogo):
       parser = JokestogoParser()
-    else:                                                               # VidademerdaParser.py
+    elif (self.source == vidademerda):                                  # VidademerdaParser.py
       parser = VidademerdaParser()
+    else:                                                               # ViedemerdeParser.py
+      parser = ViedemerdeParser()
 
     opener = AgentOpener()                                              # opens the web connection with masked user-agent
 
@@ -70,21 +86,24 @@
     except IOError:
       log ("Problem to open %s" % (parser.url))
     else:
-      parser.parse(page.read())                                         # feed the parser to get the specific content: translated text
-      page.close()                                                      # lets close the page connection
+      parser.parse(page.read())                                         # feed the parser with the page's content
+      page.close()                                                      # close the page connection
+      
+      # Handle different kind of returns from the parser. It is necessary because some sources return quotes with extra
+      # characters that we need to filter here. Some come with extra '', others come with linebreaks, etc.
       if (self.source == quotationspage):  
         self.quote = parser.quote
         self.author = parser.author
-      elif (self.source == bash or self.source == xkcdb or self.source == qdb or self.source == danstonchat):
+      elif (self.source == bash or self.source == xkcdb or self.source == qdb or self.source == danstonchat or self.source == viedemerde):
         self.quote = filter(None, parser.quote)                         # remove the '' from the array
       elif self.source == jokestogo:                                    # jokestogo
-        self.quote = filter(self.breakline, parser.quote)
+        self.quote = filter(self.linebreak, parser.quote)               # remove linebreak from the array
       else:                                                             # vidademerda
         self.quote = [''.join(parser.quote)]
         self.author = parser.author
     return self.quote, self.author
 
-  def breakline(self, item):
+  def linebreak(self, item):
     return not item == '\n'
 
 class Applet(CDApplet):
@@ -97,7 +116,7 @@
     self.copy_current_quote_key = 0
     self.go_next_quote_key = 1
     self.source = quotationspage
-    self.cSiteNames = ['Quotationspage.com','Bash.org','Xkcdb.com','Qdb.us','Danstonchat.com','Jokes2go.com','Vidademerda.com.br']
+    self.cSiteNames = ['Quotationspage.com','Bash.org','Xkcdb.com','Qdb.us','Danstonchat.com','Jokes2go.com','Vidademerda.com.br', 'Viedemerde.fr']
     
     CDApplet.__init__(self)                                             # call high-level init
   
@@ -108,26 +127,26 @@
 
   # Quotes
   def get_quotes_from_web(self):
-    self.inform_start_of_waiting_process()                              # ...
+    self.inform_start_of_waiting_process()                              # write "..." on the icon
     interface = Interface(self.source)
     quote, author = interface.fetch()
-    if (self.source == quotationspage):                                 # quotationspage nao da quotes diferentes por acesso ...
+    if (self.source == quotationspage):                                 # sources that gives the same quotes per fetch, we need circular iterator
       self.quotes = itertools.cycle(quote)
       self.authors = itertools.cycle(author)
-    else:                                                               # ... os outros dao quotes diferentes por acesso entao
-      self.quotes = iter(quote)                                         # nao precisa usar iterador circular, pois depois de mostrar
-      self.authors = iter(author)                                       # todas, busca-se novas quotes com mais uma chamada deste metodo
-    self.inform_end_of_waiting_process()                                # done
+    else:                                                               # all the rest of sources that gives different quotes per fetch, for example, 
+      self.quotes = iter(quote)                                         # pages that have "random" section from where we always fetch new quotes
+      self.authors = iter(author)                                       # from where we do not need to use circular iterator
+    self.inform_end_of_waiting_process()                                # remove the "..." from the icon
 
   def show_quote(self):
     if (self.source == quotationspage):
-      self.quotation = "\"%s\" ~ %s" % (self.quotes.next(), self.authors.next()) # N-esima quote refere-se ao N-esimo autor."quote[x]~author[x]"
-    elif (self.source == bash or self.source == xkcdb or self.source == qdb or self.source == danstonchat):
-      try:                                                              # ve a possibilidade de mostrar quotes ja armazenadas
+      self.quotation = "\"%s\" ~ %s" % (self.quotes.next(), self.authors.next()) # quote[x] ~ author[x]
+    elif (self.source == bash or self.source == xkcdb or self.source == qdb or self.source == danstonchat or self.source == viedemerde):
+      try:                                                              # check if it is possible to show the already stored quotes
         current = self.quotes.next()
-      except StopIteration:                                             # todas ja foram mostradas
-        self.get_quotes_from_web()                                      # buscar mais
-        current = self.quotes.next()                                    # posicionar na primeira para mostra-la
+      except StopIteration:                                             # all were already shown
+        self.get_quotes_from_web()                                      # fetch more
+        current = self.quotes.next()                                    # get the first
       self.quotation = "%s" % current
     elif (self.source == jokestogo):                                    # jokestogo provides only one quote per request ...
       self.quotation = "%s" % self.quotes.next().rstrip()                 
@@ -166,7 +185,7 @@
 
   def on_answer_dialog(self, key, content):
     if (key == self.copy_current_quote_key):                            # cancel button = 1, and copy_current_quote_key = 0
-      self.set_to_clipboard(self.quotation)                             # copia para a area de transferencia a quotation atual
+      self.set_to_clipboard(self.quotation)                             # copy to the transfer area the current quotation
     elif (key == self.go_next_quote_key or key == CDApplet.DIALOG_KEY_ENTER):
       self.show_quote()
 

=== modified file 'Quote/Quote.conf'
--- Quote/Quote.conf	2012-03-28 20:50:03 +0000
+++ Quote/Quote.conf	2012-05-13 12:35:23 +0000
@@ -1,4 +1,4 @@
-#0.0.9
+#0.1
 
 #[gtk-about]
 [Icon]
@@ -102,5 +102,5 @@
 
 #[gtk-preferences]
 [Configuration]
-#l[Quotationspage.com;Bash.org;Xkcdb.com;Qdb.us;Danstonchat.com;Jokes2go.com;Vidademerda.com.br] Quote source:
-source = 0
+#l[Quotationspage.com;Bash.org;Xkcdb.com;Qdb.us;Danstonchat.com;Jokes2go.com;Vidademerda.com.br;Viedemerde.fr] Quote source:
+source = 1

=== modified file 'Quote/auto-load.conf'
--- Quote/auto-load.conf	2011-05-19 23:57:32 +0000
+++ Quote/auto-load.conf	2012-05-13 12:35:23 +0000
@@ -4,13 +4,13 @@
 author = Eduardo Mucelli Rezende Oliveira
 
 # A short description of the applet and how to use it.
-description = This applet provides a "Quote of the day" feature from some internet sources such as:\n Quotationspage.com, Bash.org, Xkcdb.com, Qdb.us, Danstonchat.com, Jokes2go.com, and Vidademerda.com.br
+description = This applet provides a "Quote of the day" feature from some internet sources such as:\n Quotationspage.com, Bash.org, Xkcdb.com, Qdb.us, Danstonchat.com, Jokes2go.com, Vidademerda.com.br, and Viedemerde.fr
 
 # Category of the applet : 2 = files, 3 = internet, 4 = Desktop, 5 = accessory, 6 = system, 7 = fun
 category = 7
 
 # 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.0.9
+version = 0.1
 
 # Whether the applet can be instanciated several times or not.
 multi-instance = true

=== 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 12:35:23 +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 12:35:23 +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 12:35:23 +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 12:35:23 +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 12:35:23 +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 12:35:23 +0000 differ
=== modified file 'Twitter/emblem.py'
--- Twitter/emblem.py	2012-03-21 00:02:15 +0000
+++ Twitter/emblem.py	2012-05-13 12:35:23 +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 12:35:23 +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 12:35:23 +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 12:35:23 +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 12:35:23 +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 12:35:23 +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 12:35:23 +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