]> jfr.im git - z_archive/twitter.git/blobdiff - twitter/cmdline.py
Merge branch 'select_num_statuses'
[z_archive/twitter.git] / twitter / cmdline.py
index f57dbf93b0e45d97213ef781f4270ee62358c9cf..c70e7054cf69b88bfedac4cb19d138ffe65c5a4c 100644 (file)
@@ -6,6 +6,7 @@ USAGE:
 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
@@ -20,35 +21,46 @@ OPTIONS:
  -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)
+                            file (default ~/.twitter)
+ -l --length <count>        specify number of status updates shown
+                            (default: 20, max: 200)
+ -t --timestamp             show time before status lines
+ -d --datestamp             shoe date before status lines
 
 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?
-
+ urls            nothing but URLs
+ ansi            ansi colour (rainbow mode)
 CONFIG FILES
 
- The config file should contain a [twitter] header, your email and password
- like so:
+ The config file should contain a [twitter] header, and all the desired options
you wish to set, like so:
 
 [twitter]
 email: <username>
 password: <password>
+format: <desired_default_format_for_output>
 """
 
 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
+import ansi
+
+# 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 = {
+OPTIONS = {
     'email': None,
     'password': None,
     'action': 'friends',
@@ -56,14 +68,17 @@ options = {
     'refresh_rate': 600,
     'format': 'default',
     'config_filename': os.environ.get('HOME', '') + os.sep + '.twitter',
+    'length': 20,
+    'timestamp': False,
+    'datestamp': False,
     'extra_args': []
 }
 
 def parse_args(args, options):
     long_opts = ['email', 'password', 'help', 'format', 'refresh',
-                 'refresh-rate', 'config']
-    short_opts = "e:p:f:h?rR:c:"
-    opts, extra_args = getopt(args, short_opts, long_opts)
+                 'refresh-rate', 'config', 'length', 'timestamp', 'datestamp']
+    short_opts = "e:p:f:h?rR:c:l:td"
+    opts, extra_args = getopt(args, short_opts, long_opts)        
 
     for opt, arg in opts:
         if opt in ('-e', '--email'):
@@ -76,6 +91,12 @@ def parse_args(args, options):
             options['refresh'] = True
         elif opt in ('-R', '--refresh-rate'):
             options['refresh_rate'] = int(arg)
+        elif opt in ('-l', '--length'):
+            options["length"] = int(arg)
+        elif opt in ('-t', '--timestamp'):
+            options["timestamp"] = True
+        elif opt in ('-d', '--datestamp'):
+            options["datestamp"] = True
         elif opt in ('-?', '-h', '--help'):
             print __doc__
             sys.exit(0)
@@ -85,14 +106,38 @@ def parse_args(args, options):
     if extra_args:
         options['action'] = extra_args[0]
     options['extra_args'] = extra_args[1:]
+    
+def get_time_string(status, options):
+    timestamp = options["timestamp"]
+    datestamp = options["datestamp"]
+    t = time.strptime(status['created_at'], "%a %b %d %H:%M:%S +0000 %Y")
+    if timestamp and datestamp:
+        return time.strftime("%Y-%m-%d %H:%M:%S ", t)
+    elif timestamp:
+        return time.strftime("%H:%M:%S ", t)
+    elif datestamp:
+        return time.strftime("%Y-%m-%d ", t)
+    return ""                             
 
 class StatusFormatter(object):
     def __call__(self, status):
-        return (u"%s %s" %(
+        return (u"%S%s %s" %(
+            get_time_string(status, options),
             status['user']['screen_name'], status['text']))
 
+class AnsiStatusFormatter(object):
+    def __init__(self):
+        self._colourMap = ansi.ColourMap()
+        
+    def __call__(self, status, options):
+        colour = self._colourMap.colourFor(status['user']['screen_name'])
+        return (u"%s%s%s%s %s" %(
+            get_time_string(status, options),
+            ansi.cmdColour(colour), status['user']['screen_name'],
+            ansi.cmdReset(), status['text']))    
+    
 class VerboseStatusFormatter(object):
-    def __call__(self, status):
+    def __call__(self, status, options):
         return (u"-- %s (%s) on %s\n%s\n" %(
             status['user']['screen_name'],
             status['user']['location'],
@@ -101,7 +146,7 @@ class VerboseStatusFormatter(object):
 
 class URLStatusFormatter(object):
     urlmatch = re.compile(r'https?://\S+')
-    def __call__(self, status):
+    def __call__(self, status, options):
         urls = self.urlmatch.findall(status['text'])
         return u'\n'.join(urls) if urls else ""
 
@@ -121,20 +166,18 @@ class VerboseAdminFormatter(object):
             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
+    'urls': URLStatusFormatter,
+    'ansi': AnsiStatusFormatter
 }    
 
 admin_formatters = {
     'default': AdminFormatter,
     'verbose': VerboseAdminFormatter,
-    'urls': URLAdminFormatter
+    'urls': AdminFormatter,
+    'ansi': AdminFormatter
 }
 
 def get_status_formatter(options):
@@ -159,14 +202,20 @@ class NoSuchAction(Action):
         print >> sys.stderr, "No such action: ", options['action']
         sys.exit(1)
 
+def printNicely(string):        
+    if sys.stdout.encoding:
+        print string.encode(sys.stdout.encoding, 'replace')
+    else:
+        print string.encode('utf-8')
+        
 class StatusAction(Action):
     def __call__(self, twitter, options):
-        statuses = self.getStatuses(twitter)
+        statuses = self.getStatuses(twitter, options)
         sf = get_status_formatter(options)
         for status in statuses:
-            statusStr = sf(status)
+            statusStr = sf(status, options)
             if statusStr.strip():
-                print statusStr.encode(sys.stdout.encoding, 'replace')
+                printNicely(statusStr)
 
 class AdminAction(Action):
     def __call__(self, twitter, options):
@@ -182,28 +231,29 @@ class AdminAction(Action):
             print "  Or the user may not exist."
             print "  Sorry."
             print
+            print e
         else:
-            print af(options['action'], user).encode(sys.stdout.encoding, 'replace')
+            printNicely(af(options['action'], user))
 
 class FriendsAction(StatusAction):
-    def getStatuses(self, twitter):
-        return reversed(twitter.statuses.friends_timeline())
+    def getStatuses(self, twitter, options):
+        return reversed(twitter.statuses.friends_timeline(count=options["length"]))
 
 class PublicAction(StatusAction):
-    def getStatuses(self, twitter):
-        return reversed(twitter.statuses.public_timeline())
+    def getStatuses(self, twitter, options):
+        return reversed(twitter.statuses.public_timeline(count=options["length"]))
 
 class RepliesAction(StatusAction):
-    def getStatuses(self, twitter):
-        return reversed(twitter.statuses.replies())
+    def getStatuses(self, twitter, options):
+        return reversed(twitter.statuses.replies(count=options["length"]))
 
 class FollowAction(AdminAction):
     def getUser(self, twitter, user):
-        return twitter.notifications.follow(id=user)
+        return twitter.friendships.create(id=user)
 
 class LeaveAction(AdminAction):
     def getUser(self, twitter, user):
-        return twitter.notifications.leave(id=user)
+        return twitter.friendships.destroy(id=user)
 
 class SetStatusAction(Action):
     def __call__(self, twitter, options):
@@ -213,9 +263,14 @@ class SetStatusAction(Action):
         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,
@@ -223,25 +278,35 @@ actions = {
 }
 
 def loadConfig(filename):
-    email = None
-    password = None
+    options = dict(OPTIONS)
     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)
+        for option in ('email', 'password', 'format'):
+            if cp.has_option('twitter', option):
+                options[option] = cp.get('twitter', option)
+    return options
 
-    email, password = loadConfig(options['config_filename'])
-    if not options['email']: options['email'] = email
-    if not options['password']: options['password'] = password
+def main(args=sys.argv[1:]):
+    arg_options = {}
+    try:
+        parse_args(args, arg_options)
+    except GetoptError, e:
+        print >> sys.stderr, "I can't do that, %s." %(e)
+        print >> sys.stderr
+        sys.exit(1)
 
+    config_options = loadConfig(
+        arg_options.get('config_filename') or OPTIONS.get('config_filename'))
+
+    # Apply the various options in order, the most important applied last.
+    # Defaults first, then what's read from config file, then command-line
+    # arguments.
+    options = dict(OPTIONS)
+    for d in config_options, arg_options:
+        for k,v in d.items():
+            if v: options[k] = v
+    
     if options['refresh'] and options['action'] not in (
         'friends', 'public', 'replies'):
         print >> sys.stderr, "You can only refresh the friends, public, or replies actions."
@@ -250,8 +315,10 @@ def main_with_args(args):
         
     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)
 
@@ -268,4 +335,3 @@ def main_with_args(args):
         sys.exit(1)
     except KeyboardInterrupt:
         pass
-