]> jfr.im git - z_archive/twitter.git/blobdiff - twitter/cmdline.py
Transition from httplib to urllib2.
[z_archive/twitter.git] / twitter / cmdline.py
index 9f04df1a600f7074b4a6c8940cc8efa1c7007ac7..061c0f2d04d1c1189367e95ff575b23c0bc1aa3e 100644 (file)
@@ -4,9 +4,12 @@ USAGE:
  twitter [action] [options]
 
 ACTIONS:
-
+ follow         add the specified user to your follow list
  friends        get latest tweets from your friends (default action)
+ help           print this help text that you are currently reading
+ leave          remove the specified user from your following list
  public         get latest public tweets
+ replies        get latest replies
  set            set your twitter status
 
 OPTIONS:
@@ -17,22 +20,39 @@ OPTIONS:
                             in a while (default: every 5 minutes)
  -R --refresh-rate <rate>   set the refresh rate (in seconds)
  -f --format <format>       specify the output format for status updates
+ -c --config <filename>     read username and password from given config
+                              file (default ~/.twitter)
 
 FORMATS for the --format option
 
  default         one line per status
  verbose         multiple lines per status, more verbose status info
  urls            nothing but URLs. Dare you click them?
+
+CONFIG FILES
+
+ The config file should contain a [twitter] header, your email and password
+ like so:
+
+[twitter]
+email: <username>
+password: <password>
 """
 
 import sys
 import time
-from getopt import getopt
+from getopt import getopt, GetoptError
 from getpass import getpass
 import re
+import os.path
+from ConfigParser import SafeConfigParser
 
 from api import Twitter, TwitterError
 
+# Please don't change this, it was provided by the fine folks at Twitter.
+# If you change it, it will not work.
+AGENT_STR = "twittercommandlinetoolpy"
+
 options = {
     'email': None,
     'password': None,
@@ -40,15 +60,16 @@ options = {
     'refresh': False,
     'refresh_rate': 600,
     'format': 'default',
+    'config_filename': os.environ.get('HOME', '') + os.sep + '.twitter',
     'extra_args': []
 }
 
 def parse_args(args, options):
     long_opts = ['email', 'password', 'help', 'format', 'refresh',
-                 'refresh-rate']
-    short_opts = "e:p:f:h?rR:"
-    opts, extra_args = getopt(args, short_opts, long_opts)
-    
+                 'refresh-rate', 'config']
+    short_opts = "e:p:f:h?rR:c:"
+    opts, extra_args = getopt(args, short_opts, long_opts)        
+
     for opt, arg in opts:
         if opt in ('-e', '--email'):
             options['email'] = arg
@@ -63,7 +84,9 @@ def parse_args(args, options):
         elif opt in ('-?', '-h', '--help'):
             print __doc__
             sys.exit(0)
-    
+        elif opt in ('-c', '--config'):
+            options['config_filename'] = arg
+
     if extra_args:
         options['action'] = extra_args[0]
     options['extra_args'] = extra_args[1:]
@@ -87,14 +110,47 @@ class URLStatusFormatter(object):
         urls = self.urlmatch.findall(status['text'])
         return u'\n'.join(urls) if urls else ""
 
-formatters = {
+class AdminFormatter(object):
+    def __call__(self, action, user):
+        user_str = u"%s (%s)" %(user['screen_name'], user['name'])
+        if action == "follow":
+            return u"You are now following %s.\n" %(user_str)
+        else:
+            return u"You are no longer following %s.\n" %(user_str)
+
+class VerboseAdminFormatter(object):
+    def __call__(self, action, user):
+        return(u"-- %s: %s (%s): %s" % (
+            "Following" if action == "follow" else "Leaving", 
+            user['screen_name'], 
+            user['name'],
+            user['url']))
+
+class URLAdminFormatter(object):
+    def __call__(self, action, user):
+        return("Admin actions do not support the URL formatter")
+
+status_formatters = {
     'default': StatusFormatter,
     'verbose': VerboseStatusFormatter,
     'urls': URLStatusFormatter
 }    
-    
+
+admin_formatters = {
+    'default': AdminFormatter,
+    'verbose': VerboseAdminFormatter,
+    'urls': URLAdminFormatter
+}
+
 def get_status_formatter(options):
-    sf = formatters.get(options['format'])
+    sf = status_formatters.get(options['format'])
+    if (not sf):
+        raise TwitterError(
+            "Unknown formatter '%s'" %(options['format']))
+    return sf()
+
+def get_admin_formatter(options):
+    sf = admin_formatters.get(options['format'])
     if (not sf):
         raise TwitterError(
             "Unknown formatter '%s'" %(options['format']))
@@ -116,53 +172,118 @@ class StatusAction(Action):
             statusStr = sf(status)
             if statusStr.strip():
                 print statusStr.encode(sys.stdout.encoding, 'replace')
-        
+
+class AdminAction(Action):
+    def __call__(self, twitter, options):
+        if not options['extra_args'][0]:
+            raise TwitterError("You need to specify a user (screen name)")
+        af = get_admin_formatter(options)
+        try:
+            user = self.getUser(twitter, options['extra_args'][0])
+        except TwitterError, e:
+            print "There was a problem following or leaving the specified user."
+            print "  You may be trying to follow a user you are already following;"
+            print "  Leaving a user you are not currently following;"
+            print "  Or the user may not exist."
+            print "  Sorry."
+            print
+            print e
+        else:
+            print af(options['action'], user).encode(sys.stdout.encoding, 'replace')
+
 class FriendsAction(StatusAction):
     def getStatuses(self, twitter):
         return reversed(twitter.statuses.friends_timeline())
-    
+
 class PublicAction(StatusAction):
     def getStatuses(self, twitter):
         return reversed(twitter.statuses.public_timeline())
 
+class RepliesAction(StatusAction):
+    def getStatuses(self, twitter):
+        return reversed(twitter.statuses.replies())
+
+class FollowAction(AdminAction):
+    def getUser(self, twitter, user):
+        return twitter.notifications.follow(id=user)
+
+class LeaveAction(AdminAction):
+    def getUser(self, twitter, user):
+        return twitter.notifications.leave(id=user)
+
 class SetStatusAction(Action):
     def __call__(self, twitter, options):
-        status = (u" ".join(options['extra_args'])).encode(
-            'utf8', 'replace')
+        statusTxt = (u" ".join(options['extra_args']) 
+                     if options['extra_args'] 
+                     else unicode(raw_input("message: ")))
+        status = (statusTxt.encode('utf8', 'replace'))
         twitter.statuses.update(status=status)
 
+class HelpAction(Action):
+    def __call__(self, twitter, options):
+        print __doc__
+
 actions = {
+    'follow': FollowAction,
     'friends': FriendsAction,
+    'help': HelpAction,
+    'leave': LeaveAction,
     'public': PublicAction,
+    'replies': RepliesAction,
     'set': SetStatusAction,
 }
 
+def loadConfig(filename):
+    email = None
+    password = None
+    if os.path.exists(filename):
+        cp = SafeConfigParser()
+        cp.read([filename])
+        email = cp.get('twitter', 'email', None)
+        password = cp.get('twitter', 'password', None)
+    return email, password
 
 def main():
     return main_with_args(sys.argv[1:])
-    
+
 def main_with_args(args):
-    parse_args(args, options)
-    if options['refresh'] and options['action'] == 'set':
-        print >> sys.stderr, "You can't repeatedly set your status, silly"
+    try:
+        parse_args(args, options)
+    except GetoptError, e:
+        print >> sys.stderr, "I can't do that, %s." %(e)
+        print >> sys.stderr
+        sys.exit(1)
+
+    email, password = loadConfig(options['config_filename'])
+    if not options['email']: options['email'] = email
+    if not options['password']: options['password'] = password
+
+    if options['refresh'] and options['action'] not in (
+        'friends', 'public', 'replies'):
+        print >> sys.stderr, "You can only refresh the friends, public, or replies actions."
         print >> sys.stderr, "Use 'twitter -h' for help."
         sys.exit(1)
+        
     if options['email'] and not options['password']:
         options['password'] = getpass("Twitter password: ")
-    twitter = Twitter(options['email'], options['password'])
+        
+    twitter = Twitter(options['email'], options['password'], agent=AGENT_STR)
     action = actions.get(options['action'], NoSuchAction)()
+    
     try:
         doAction = lambda : action(twitter, options)
-        if (options['refresh']):
+
+        if (options['refresh'] and isinstance(action, StatusAction)):
             while True:
                 doAction()
                 time.sleep(options['refresh_rate'])
         else:
             doAction()
+
     except TwitterError, e:
-        print >> sys.stderr, e.message
+        print >> sys.stderr, e.args[0]
         print >> sys.stderr, "Use 'twitter -h' for help."
         sys.exit(1)
     except KeyboardInterrupt:
         pass
-    
+