X-Git-Url: https://jfr.im/git/z_archive/twitter.git/blobdiff_plain/b1e8ad75b75badc057f7831d0eea64c7165f8070..662f757601dc4f7db85c09211b9e81359106942f:/twitter/cmdline.py diff --git a/twitter/cmdline.py b/twitter/cmdline.py index 4e05522..c70e705 100644 --- a/twitter/cmdline.py +++ b/twitter/cmdline.py @@ -21,22 +21,28 @@ OPTIONS: -R --refresh-rate set the refresh rate (in seconds) -f --format specify the output format for status updates -c --config read username and password from given config - file (default ~/.twitter) + file (default ~/.twitter) + -l --length 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: password: +format: """ import sys @@ -54,7 +60,7 @@ import ansi # If you change it, it will not work. AGENT_STR = "twittercommandlinetoolpy" -options = { +OPTIONS = { 'email': None, 'password': None, 'action': 'friends', @@ -62,13 +68,16 @@ 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:" + '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: @@ -82,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) @@ -91,24 +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): + def __call__(self, status, options): colour = self._colourMap.colourFor(status['user']['screen_name']) - return (u"%s%s%s %s" %( + 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'], @@ -117,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 "" @@ -137,10 +166,6 @@ 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, @@ -151,7 +176,8 @@ status_formatters = { admin_formatters = { 'default': AdminFormatter, 'verbose': VerboseAdminFormatter, - 'urls': URLAdminFormatter + 'urls': AdminFormatter, + 'ansi': AdminFormatter } def get_status_formatter(options): @@ -176,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): @@ -201,27 +233,27 @@ class AdminAction(Action): 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): @@ -246,30 +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:]) + for option in ('email', 'password', 'format'): + if cp.has_option('twitter', option): + options[option] = cp.get('twitter', option) + return options -def main_with_args(args): +def main(args=sys.argv[1:]): + arg_options = {} try: - parse_args(args, options) + parse_args(args, arg_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 + 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." @@ -298,4 +335,3 @@ def main_with_args(args): sys.exit(1) except KeyboardInterrupt: pass -