]>
jfr.im git - z_archive/twitter.git/blob - twitter/cmdline.py
4 twitter [action] [options]
7 follow add the specified user to your follow list
8 friends get latest tweets from your friends (default action)
9 help print this help text that you are currently reading
10 leave remove the specified user from your following list
11 public get latest public tweets
12 replies get latest replies
13 set set your twitter status
14 shell login the twitter shell
18 -e --email <email> your email to login to twitter
19 -p --password <password> your twitter password
20 -r --refresh run this command forever, polling every once
21 in a while (default: every 5 minutes)
22 -R --refresh-rate <rate> set the refresh rate (in seconds)
23 -f --format <format> specify the output format for status updates
24 -c --config <filename> read username and password from given config
25 file (default ~/.twitter)
26 -l --length <count> specify number of status updates shown
27 (default: 20, max: 200)
28 -t --timestamp show time before status lines
29 -d --datestamp shoe date before status lines
31 FORMATS for the --format option
33 default one line per status
34 verbose multiple lines per status, more verbose status info
36 ansi ansi colour (rainbow mode)
40 The config file should contain a [twitter] header, and all the desired options
41 you wish to set, like so:
46 format: <desired_default_format_for_output>
47 prompt: <twitter_shell_prompt e.g. '[cyan]twitter[R]> '>
52 from getopt
import gnu_getopt
as getopt
, GetoptError
53 from getpass
import getpass
56 from ConfigParser
import SafeConfigParser
59 from api
import Twitter
, TwitterError
62 # Please don't change this, it was provided by the fine folks at Twitter.
63 # If you change it, it will not work.
64 AGENT_STR
= "twittercommandlinetoolpy"
73 'prompt': '[cyan]twitter[R]> ',
74 'config_filename': os
.environ
.get('HOME', '') + os
.sep
+ '.twitter',
81 def parse_args(args
, options
):
82 long_opts
= ['email', 'password', 'help', 'format', 'refresh',
83 'refresh-rate', 'config', 'length', 'timestamp', 'datestamp']
84 short_opts
= "e:p:f:h?rR:c:l:td"
85 opts
, extra_args
= getopt(args
, short_opts
, long_opts
)
88 if opt
in ('-e', '--email'):
89 options
['email'] = arg
90 elif opt
in ('-p', '--password'):
91 options
['password'] = arg
92 elif opt
in ('-f', '--format'):
93 options
['format'] = arg
94 elif opt
in ('-r', '--refresh'):
95 options
['refresh'] = True
96 elif opt
in ('-R', '--refresh-rate'):
97 options
['refresh_rate'] = int(arg
)
98 elif opt
in ('-l', '--length'):
99 options
["length"] = int(arg
)
100 elif opt
in ('-t', '--timestamp'):
101 options
["timestamp"] = True
102 elif opt
in ('-d', '--datestamp'):
103 options
["datestamp"] = True
104 elif opt
in ('-?', '-h', '--help'):
105 options
['action'] = 'help'
106 elif opt
in ('-c', '--config'):
107 options
['config_filename'] = arg
109 if extra_args
and not ('action' in options
and options
['action'] == 'help'):
110 options
['action'] = extra_args
[0]
111 options
['extra_args'] = extra_args
[1:]
113 def get_time_string(status
, options
):
114 timestamp
= options
["timestamp"]
115 datestamp
= options
["datestamp"]
116 t
= time
.strptime(status
['created_at'], "%a %b %d %H:%M:%S +0000 %Y")
117 i_hate_timezones
= time
.timezone
119 i_hate_timezones
= time
.altzone
120 dt
= datetime
.datetime(*t
[:-3]) - datetime
.timedelta(
121 seconds
=i_hate_timezones
)
123 if timestamp
and datestamp
:
124 return time
.strftime("%Y-%m-%d %H:%M:%S ", t
)
126 return time
.strftime("%H:%M:%S ", t
)
128 return time
.strftime("%Y-%m-%d ", t
)
131 class StatusFormatter(object):
132 def __call__(self
, status
, options
):
133 return (u
"%s%s %s" %(
134 get_time_string(status
, options
),
135 status
['user']['screen_name'], status
['text']))
137 class AnsiStatusFormatter(object):
139 self
._colourMap
= ansi
.ColourMap()
141 def __call__(self
, status
, options
):
142 colour
= self
._colourMap
.colourFor(status
['user']['screen_name'])
143 return (u
"%s%s%s%s %s" %(
144 get_time_string(status
, options
),
145 ansi
.cmdColour(colour
), status
['user']['screen_name'],
146 ansi
.cmdReset(), status
['text']))
148 class VerboseStatusFormatter(object):
149 def __call__(self
, status
, options
):
150 return (u
"-- %s (%s) on %s\n%s\n" %(
151 status
['user']['screen_name'],
152 status
['user']['location'],
153 status
['created_at'],
156 class URLStatusFormatter(object):
157 urlmatch
= re
.compile(r
'https?://\S+')
158 def __call__(self
, status
, options
):
159 urls
= self
.urlmatch
.findall(status
['text'])
160 return u
'\n'.join(urls
) if urls
else ""
162 class AdminFormatter(object):
163 def __call__(self
, action
, user
):
164 user_str
= u
"%s (%s)" %(user
['screen_name'], user
['name'])
165 if action
== "follow":
166 return u
"You are now following %s.\n" %(user_str)
168 return u
"You are no longer following %s.\n" %(user_str)
170 class VerboseAdminFormatter(object):
171 def __call__(self
, action
, user
):
172 return(u
"-- %s: %s (%s): %s" % (
173 "Following" if action
== "follow" else "Leaving",
178 status_formatters
= {
179 'default': StatusFormatter
,
180 'verbose': VerboseStatusFormatter
,
181 'urls': URLStatusFormatter
,
182 'ansi': AnsiStatusFormatter
186 'default': AdminFormatter
,
187 'verbose': VerboseAdminFormatter
,
188 'urls': AdminFormatter
,
189 'ansi': AdminFormatter
192 def get_status_formatter(options
):
193 sf
= status_formatters
.get(options
['format'])
196 "Unknown formatter '%s'" %(options
['format']))
199 def get_admin_formatter(options
):
200 sf
= admin_formatters
.get(options
['format'])
203 "Unknown formatter '%s'" %(options
['format']))
206 class Action(object):
208 def ask(self
, subject
='perform this action', careful
=False):
210 Requests fromt he user using `raw_input` if `subject` should be
211 performed. When `careful`, the default answer is NO, otherwise YES.
212 Returns the user answer in the form `True` or `False`.
218 prompt
= 'You really want to %s %s? ' %(subject
, sample
)
220 answer
= raw_input(prompt
).lower()
222 return answer
in ('yes', 'y')
224 return answer
not in ('no', 'n')
226 print >>sys
.stderr
# Put Newline since Enter was never pressed
228 # Figure out why on OS X the raw_input keeps raising
229 # EOFError and is never able to reset and get more input
230 # Hint: Look at how IPython implements their console
236 def __call__(self
, twitter
, options
):
237 action
= actions
.get(options
['action'], NoSuchAction
)()
239 doAction
= lambda : action(twitter
, options
)
240 if (options
['refresh'] and isinstance(action
, StatusAction
)):
243 time
.sleep(options
['refresh_rate'])
246 except KeyboardInterrupt:
247 print >>sys
.stderr
, '\n[Keyboard Interrupt]'
250 class NoSuchActionError(Exception):
253 class NoSuchAction(Action
):
254 def __call__(self
, twitter
, options
):
255 raise NoSuchActionError("No such action: %s" %(options
['action']))
257 def printNicely(string
):
258 if sys
.stdout
.encoding
:
259 print string
.encode(sys
.stdout
.encoding
, 'replace')
261 print string
.encode('utf-8')
263 class StatusAction(Action
):
264 def __call__(self
, twitter
, options
):
265 statuses
= self
.getStatuses(twitter
, options
)
266 sf
= get_status_formatter(options
)
267 for status
in statuses
:
268 statusStr
= sf(status
, options
)
269 if statusStr
.strip():
270 printNicely(statusStr
)
272 class AdminAction(Action
):
273 def __call__(self
, twitter
, options
):
274 if not (options
['extra_args'] and options
['extra_args'][0]):
275 raise TwitterError("You need to specify a user (screen name)")
276 af
= get_admin_formatter(options
)
278 user
= self
.getUser(twitter
, options
['extra_args'][0])
279 except TwitterError
, e
:
280 print "There was a problem following or leaving the specified user."
281 print "You may be trying to follow a user you are already following;"
282 print "Leaving a user you are not currently following;"
283 print "Or the user may not exist."
288 printNicely(af(options
['action'], user
))
290 class FriendsAction(StatusAction
):
291 def getStatuses(self
, twitter
, options
):
292 return reversed(twitter
.statuses
.friends_timeline(count
=options
["length"]))
294 class PublicAction(StatusAction
):
295 def getStatuses(self
, twitter
, options
):
296 return reversed(twitter
.statuses
.public_timeline(count
=options
["length"]))
298 class RepliesAction(StatusAction
):
299 def getStatuses(self
, twitter
, options
):
300 return reversed(twitter
.statuses
.replies(count
=options
["length"]))
302 class FollowAction(AdminAction
):
303 def getUser(self
, twitter
, user
):
304 return twitter
.friendships
.create(id=user
)
306 class LeaveAction(AdminAction
):
307 def getUser(self
, twitter
, user
):
308 return twitter
.friendships
.destroy(id=user
)
310 class SetStatusAction(Action
):
311 def __call__(self
, twitter
, options
):
312 statusTxt
= (u
" ".join(options
['extra_args'])
313 if options
['extra_args']
314 else unicode(raw_input("message: ")))
315 status
= (statusTxt
.encode('utf8', 'replace'))
316 twitter
.statuses
.update(status
=status
)
318 class TwitterShell(Action
):
320 def render_prompt(self
, prompt
):
321 '''Parses the `prompt` string and returns the rendered version'''
322 prompt
= prompt
.strip("'").replace("\\'","'")
323 for colour
in ansi
.COLOURS_NAMED
:
324 if '[%s]' %(colour) in prompt
:
325 prompt
= prompt
.replace(
326 '[%s]' %(colour), ansi
.cmdColourNamed(colour
))
327 prompt
= prompt
.replace('[R]', ansi
.cmdReset())
330 def __call__(self
, twitter
, options
):
331 prompt
= self
.render_prompt(options
.get('prompt', 'twitter> '))
333 options
['action'] = ""
335 args
= raw_input(prompt
).split()
336 parse_args(args
, options
)
337 if not options
['action']:
339 elif options
['action'] == 'exit':
341 elif options
['action'] == 'shell':
342 print >>sys
.stderr
, 'Sorry Xzibit does not work here!'
344 elif options
['action'] == 'help':
345 print >>sys
.stderr
, '''\ntwitter> `action`\n
346 The Shell Accepts all the command line actions along with:
348 exit Leave the twitter shell (^D may also be used)
350 Full CMD Line help is appended below for your convinience.'''
351 Action()(twitter
, options
)
352 options
['action'] = ''
353 except NoSuchActionError
, e
:
354 print >>sys
.stderr
, e
355 except KeyboardInterrupt:
356 print >>sys
.stderr
, '\n[Keyboard Interrupt]'
359 leaving
= self
.ask(subject
='Leave')
361 print >>sys
.stderr
, 'Excellent!'
365 class HelpAction(Action
):
366 def __call__(self
, twitter
, options
):
370 'follow' : FollowAction
,
371 'friends' : FriendsAction
,
373 'leave' : LeaveAction
,
374 'public' : PublicAction
,
375 'replies' : RepliesAction
,
376 'set' : SetStatusAction
,
377 'shell' : TwitterShell
,
380 def loadConfig(filename
):
381 options
= dict(OPTIONS
)
382 if os
.path
.exists(filename
):
383 cp
= SafeConfigParser()
385 for option
in ('email', 'password', 'format', 'prompt'):
386 if cp
.has_option('twitter', option
):
387 options
[option
] = cp
.get('twitter', option
)
390 def main(args
=sys
.argv
[1:]):
393 parse_args(args
, arg_options
)
394 except GetoptError
, e
:
395 print >> sys
.stderr
, "I can't do that, %s." %(e)
399 config_options
= loadConfig(
400 arg_options
.get('config_filename') or OPTIONS
.get('config_filename'))
402 # Apply the various options in order, the most important applied last.
403 # Defaults first, then what's read from config file, then command-line
405 options
= dict(OPTIONS
)
406 for d
in config_options
, arg_options
:
407 for k
,v
in d
.items():
410 if options
['refresh'] and options
['action'] not in (
411 'friends', 'public', 'replies'):
412 print >> sys
.stderr
, "You can only refresh the friends, public, or replies actions."
413 print >> sys
.stderr
, "Use 'twitter -h' for help."
416 if options
['email'] and not options
['password']:
417 options
['password'] = getpass("Twitter password: ")
419 twitter
= Twitter(options
['email'], options
['password'], agent
=AGENT_STR
)
421 Action()(twitter
, options
)
422 except NoSuchActionError
, e
:
423 print >>sys
.stderr
, e
425 except TwitterError
, e
:
426 print >> sys
.stderr
, e
.args
[0]
427 print >> sys
.stderr
, "Use 'twitter -h' for help."