]> jfr.im git - z_archive/twitter.git/blame - twitter/cmdline.py
docs for pyprompt
[z_archive/twitter.git] / twitter / cmdline.py
CommitLineData
3756d82e 1# encoding: utf-8
7364ea65 2"""
5251ea48 3USAGE:
7364ea65 4
5251ea48 5 twitter [action] [options]
6
086fc282 7
5251ea48 8ACTIONS:
086fc282 9 authorize authorize the command-line tool to interact with Twitter
527c550f 10 follow follow a user
5251ea48 11 friends get latest tweets from your friends (default action)
45688301 12 help print this help text that you are currently reading
527c550f
MV
13 leave stop following a user
14 list get list of a user's lists; give a list name to get
15 tweets from that list
16 mylist get list of your lists; give a list name to get tweets
17 from that list
5251ea48 18 public get latest public tweets
7227ce91
MV
19 pyprompt start a Python prompt for interacting with the twitter
20 object directly
527c550f 21 replies get latest replies to you
6af9fa5f 22 search search twitter (Beware: octothorpe, escape it)
5251ea48 23 set set your twitter status
527c550f 24 shell login to the twitter shell
5251ea48 25
086fc282 26
5251ea48 27OPTIONS:
28
0ea01db7 29 -r --refresh run this command forever, polling every once
30 in a while (default: every 5 minutes)
31 -R --refresh-rate <rate> set the refresh rate (in seconds)
32 -f --format <format> specify the output format for status updates
21e3bd23 33 -c --config <filename> read username and password from given config
39a6f562
MV
34 file (default ~/.twitter)
35 -l --length <count> specify number of status updates shown
36 (default: 20, max: 200)
37 -t --timestamp show time before status lines
527c550f
MV
38 -d --datestamp show date before status lines
39 --no-ssl use less-secure HTTP instead of HTTPS
7f1dd286 40 --oauth <filename> filename to read/store oauth credentials to
086fc282 41
0ea01db7 42FORMATS for the --format option
43
44 default one line per status
45 verbose multiple lines per status, more verbose status info
327e556b
MV
46 urls nothing but URLs
47 ansi ansi colour (rainbow mode)
05b85831 48
086fc282 49
21e3bd23 50CONFIG FILES
51
086fc282
MV
52 The config file should be placed in your home directory and be named .twitter.
53 It must contain a [twitter] header, and all the desired options you wish to
54 set, like so:
21e3bd23 55
56[twitter]
327e556b 57format: <desired_default_format_for_output>
05b85831 58prompt: <twitter_shell_prompt e.g. '[cyan]twitter[R]> '>
086fc282
MV
59
60 OAuth authentication tokens are stored in the file .twitter_oauth in your
61 home directory.
7364ea65 62"""
63
6c527e72
MV
64CONSUMER_KEY='uS6hO2sV6tDKIOeVjhnFnQ'
65CONSUMER_SECRET='MEYTOS97VvlHX7K1rwHPEqVpTSqZ71HtvoK4sVuYk'
66
5251ea48 67import sys
0ea01db7 68import time
f2a7ce46 69from getopt import gnu_getopt as getopt, GetoptError
f068ff42 70from getpass import getpass
0ea01db7 71import re
21e3bd23 72import os.path
73from ConfigParser import SafeConfigParser
a4b5e65b 74import datetime
fd2bc885 75from urllib import quote
6c527e72 76import webbrowser
5251ea48 77
78from api import Twitter, TwitterError
1b31d642
MV
79from oauth import OAuth, write_token_file, read_token_file
80from oauth_dance import oauth_dance
0b9960a3 81import ansi
a5e40197 82from util import smrt_input
5251ea48 83
327e556b 84OPTIONS = {
5251ea48 85 'action': 'friends',
0ea01db7 86 'refresh': False,
87 'refresh_rate': 600,
88 'format': 'default',
05b85831 89 'prompt': '[cyan]twitter[R]> ',
21e3bd23 90 'config_filename': os.environ.get('HOME', '') + os.sep + '.twitter',
6c527e72 91 'oauth_filename': os.environ.get('HOME', '') + os.sep + '.twitter_oauth',
39a6f562
MV
92 'length': 20,
93 'timestamp': False,
94 'datestamp': False,
9a148ed1 95 'extra_args': [],
6c527e72 96 'secure': True,
5251ea48 97}
98
99def parse_args(args, options):
7f1dd286 100 long_opts = ['help', 'format=', 'refresh', 'oauth=',
8ddd8500 101 'refresh-rate=', 'config=', 'length=', 'timestamp',
9a148ed1 102 'datestamp', 'no-ssl']
39a6f562 103 short_opts = "e:p:f:h?rR:c:l:td"
8ddd8500 104 opts, extra_args = getopt(args, short_opts, long_opts)
efa0ba89 105
5251ea48 106 for opt, arg in opts:
086fc282 107 if opt in ('-f', '--format'):
0ea01db7 108 options['format'] = arg
109 elif opt in ('-r', '--refresh'):
110 options['refresh'] = True
111 elif opt in ('-R', '--refresh-rate'):
112 options['refresh_rate'] = int(arg)
39a6f562
MV
113 elif opt in ('-l', '--length'):
114 options["length"] = int(arg)
115 elif opt in ('-t', '--timestamp'):
116 options["timestamp"] = True
117 elif opt in ('-d', '--datestamp'):
118 options["datestamp"] = True
5251ea48 119 elif opt in ('-?', '-h', '--help'):
05b85831 120 options['action'] = 'help'
21e3bd23 121 elif opt in ('-c', '--config'):
122 options['config_filename'] = arg
9a148ed1
MV
123 elif opt == '--no-ssl':
124 options['secure'] = False
7f1dd286
MV
125 elif opt == '--oauth':
126 options['oauth_filename'] = arg
efa0ba89 127
05b85831 128 if extra_args and not ('action' in options and options['action'] == 'help'):
ae1d86aa 129 options['action'] = extra_args[0]
130 options['extra_args'] = extra_args[1:]
a8b5ad3e 131
87be041f 132def get_time_string(status, options, format="%a %b %d %H:%M:%S +0000 %Y"):
39a6f562
MV
133 timestamp = options["timestamp"]
134 datestamp = options["datestamp"]
87be041f 135 t = time.strptime(status['created_at'], format)
a4b5e65b
MV
136 i_hate_timezones = time.timezone
137 if (time.daylight):
138 i_hate_timezones = time.altzone
139 dt = datetime.datetime(*t[:-3]) - datetime.timedelta(
140 seconds=i_hate_timezones)
141 t = dt.timetuple()
39a6f562
MV
142 if timestamp and datestamp:
143 return time.strftime("%Y-%m-%d %H:%M:%S ", t)
144 elif timestamp:
145 return time.strftime("%H:%M:%S ", t)
146 elif datestamp:
147 return time.strftime("%Y-%m-%d ", t)
8ddd8500 148 return ""
5251ea48 149
150class StatusFormatter(object):
a55c0ac8
MV
151 def __call__(self, status, options):
152 return (u"%s%s %s" %(
39a6f562 153 get_time_string(status, options),
0ea01db7 154 status['user']['screen_name'], status['text']))
5251ea48 155
0b9960a3
MV
156class AnsiStatusFormatter(object):
157 def __init__(self):
158 self._colourMap = ansi.ColourMap()
a8b5ad3e 159
39a6f562 160 def __call__(self, status, options):
0b9960a3 161 colour = self._colourMap.colourFor(status['user']['screen_name'])
39a6f562
MV
162 return (u"%s%s%s%s %s" %(
163 get_time_string(status, options),
0b9960a3 164 ansi.cmdColour(colour), status['user']['screen_name'],
05b85831
HN
165 ansi.cmdReset(), status['text']))
166
f068ff42 167class VerboseStatusFormatter(object):
39a6f562 168 def __call__(self, status, options):
f068ff42 169 return (u"-- %s (%s) on %s\n%s\n" %(
170 status['user']['screen_name'],
171 status['user']['location'],
172 status['created_at'],
0ea01db7 173 status['text']))
f068ff42 174
0ea01db7 175class URLStatusFormatter(object):
176 urlmatch = re.compile(r'https?://\S+')
39a6f562 177 def __call__(self, status, options):
0ea01db7 178 urls = self.urlmatch.findall(status['text'])
179 return u'\n'.join(urls) if urls else ""
180
3c2b5e3e 181
69851f4c
AL
182class ListsFormatter(object):
183 def __call__(self, list):
184 if list['description']:
185 list_str = u"%-30s (%s)" % (list['name'], list['description'])
186 else:
187 list_str = u"%-30s" % (list['name'])
188 return u"%s\n" % list_str
189
190class ListsVerboseFormatter(object):
191 def __call__(self, list):
192 list_str = u"%-30s\n description: %s\n members: %s\n mode:%s\n" % (list['name'], list['description'], list['member_count'], list['mode'])
193 return list_str
194
195class AnsiListsFormatter(object):
196 def __init__(self):
197 self._colourMap = ansi.ColourMap()
198
199 def __call__(self, list):
200 colour = self._colourMap.colourFor(list['name'])
201 return (u"%s%-15s%s %s" %(
202 ansi.cmdColour(colour), list['name'],
203 ansi.cmdReset(), list['description']))
204
205
1c11e6d7 206class AdminFormatter(object):
efa0ba89 207 def __call__(self, action, user):
da45d039
MV
208 user_str = u"%s (%s)" %(user['screen_name'], user['name'])
209 if action == "follow":
e02facc9 210 return u"You are now following %s.\n" %(user_str)
da45d039 211 else:
e02facc9 212 return u"You are no longer following %s.\n" %(user_str)
efa0ba89 213
1c11e6d7 214class VerboseAdminFormatter(object):
efa0ba89
MV
215 def __call__(self, action, user):
216 return(u"-- %s: %s (%s): %s" % (
05b85831
HN
217 "Following" if action == "follow" else "Leaving",
218 user['screen_name'],
efa0ba89
MV
219 user['name'],
220 user['url']))
221
87be041f
WD
222class SearchFormatter(object):
223 def __call__(self, result, options):
224 return(u"%s%s %s" %(
225 get_time_string(result, options, "%a, %d %b %Y %H:%M:%S +0000"),
226 result['from_user'], result['text']))
227
228class VerboseSearchFormatter(SearchFormatter):
a8b5ad3e
MV
229 pass #Default to the regular one
230
87be041f
WD
231class URLSearchFormatter(object):
232 urlmatch = re.compile(r'https?://\S+')
233 def __call__(self, result, options):
234 urls = self.urlmatch.findall(result['text'])
235 return u'\n'.join(urls) if urls else ""
236
237class AnsiSearchFormatter(object):
238 def __init__(self):
239 self._colourMap = ansi.ColourMap()
a8b5ad3e 240
87be041f
WD
241 def __call__(self, result, options):
242 colour = self._colourMap.colourFor(result['from_user'])
243 return (u"%s%s%s%s %s" %(
244 get_time_string(result, options, "%a, %d %b %Y %H:%M:%S +0000"),
245 ansi.cmdColour(colour), result['from_user'],
246 ansi.cmdReset(), result['text']))
247
5a77e17a
MV
248_term_encoding = None
249def get_term_encoding():
250 global _term_encoding
251 if not _term_encoding:
252 lang = os.getenv('LANG', 'unknown.UTF-8').split('.')
253 if lang[1:]:
254 _term_encoding = lang[1]
255 else:
256 _term_encoding = 'UTF-8'
257 return _term_encoding
258
87be041f 259formatters = {}
1c11e6d7 260status_formatters = {
0ea01db7 261 'default': StatusFormatter,
262 'verbose': VerboseStatusFormatter,
0b9960a3
MV
263 'urls': URLStatusFormatter,
264 'ansi': AnsiStatusFormatter
05b85831 265}
87be041f 266formatters['status'] = status_formatters
1c11e6d7
WD
267
268admin_formatters = {
efa0ba89
MV
269 'default': AdminFormatter,
270 'verbose': VerboseAdminFormatter,
327e556b
MV
271 'urls': AdminFormatter,
272 'ansi': AdminFormatter
1c11e6d7 273}
87be041f 274formatters['admin'] = admin_formatters
efa0ba89 275
87be041f
WD
276search_formatters = {
277 'default': SearchFormatter,
278 'verbose': VerboseSearchFormatter,
279 'urls': URLSearchFormatter,
280 'ansi': AnsiSearchFormatter
281}
282formatters['search'] = search_formatters
283
69851f4c
AL
284lists_formatters = {
285 'default': ListsFormatter,
286 'verbose': ListsVerboseFormatter,
287 'urls': None,
288 'ansi': AnsiListsFormatter
289}
290formatters['lists'] = lists_formatters
291
87be041f
WD
292def get_formatter(action_type, options):
293 formatters_dict = formatters.get(action_type)
294 if (not formatters_dict):
a8b5ad3e 295 raise TwitterError(
87be041f
WD
296 "There was an error finding a class of formatters for your type (%s)"
297 %(action_type))
298 f = formatters_dict.get(options['format'])
299 if (not f):
efa0ba89 300 raise TwitterError(
87be041f
WD
301 "Unknown formatter '%s' for status actions" %(options['format']))
302 return f()
efa0ba89 303
0ea01db7 304class Action(object):
ec894371
MV
305
306 def ask(self, subject='perform this action', careful=False):
05b85831
HN
307 '''
308 Requests fromt he user using `raw_input` if `subject` should be
309 performed. When `careful`, the default answer is NO, otherwise YES.
310 Returns the user answer in the form `True` or `False`.
311 '''
f47ab046
MV
312 sample = '(y/N)'
313 if not careful:
314 sample = '(Y/n)'
a8b5ad3e 315
05b85831
HN
316 prompt = 'You really want to %s %s? ' %(subject, sample)
317 try:
318 answer = raw_input(prompt).lower()
319 if careful:
f47ab046 320 return answer in ('yes', 'y')
05b85831 321 else:
f47ab046 322 return answer not in ('no', 'n')
05b85831
HN
323 except EOFError:
324 print >>sys.stderr # Put Newline since Enter was never pressed
325 # TODO:
326 # Figure out why on OS X the raw_input keeps raising
327 # EOFError and is never able to reset and get more input
328 # Hint: Look at how IPython implements their console
f47ab046
MV
329 default = True
330 if careful:
331 default = False
05b85831 332 return default
a8b5ad3e 333
05b85831
HN
334 def __call__(self, twitter, options):
335 action = actions.get(options['action'], NoSuchAction)()
336 try:
337 doAction = lambda : action(twitter, options)
338 if (options['refresh'] and isinstance(action, StatusAction)):
339 while True:
340 doAction()
341 time.sleep(options['refresh_rate'])
342 else:
343 doAction()
344 except KeyboardInterrupt:
345 print >>sys.stderr, '\n[Keyboard Interrupt]'
346 pass
347
348class NoSuchActionError(Exception):
0ea01db7 349 pass
350
351class NoSuchAction(Action):
352 def __call__(self, twitter, options):
05b85831 353 raise NoSuchActionError("No such action: %s" %(options['action']))
0ea01db7 354
8ddd8500 355def printNicely(string):
862cce81
MV
356 if sys.stdout.encoding:
357 print string.encode(sys.stdout.encoding, 'replace')
358 else:
359 print string.encode('utf-8')
a8b5ad3e 360
0ea01db7 361class StatusAction(Action):
362 def __call__(self, twitter, options):
39a6f562 363 statuses = self.getStatuses(twitter, options)
87be041f 364 sf = get_formatter('status', options)
0ea01db7 365 for status in statuses:
39a6f562 366 statusStr = sf(status, options)
0ea01db7 367 if statusStr.strip():
862cce81 368 printNicely(statusStr)
1c11e6d7 369
87be041f
WD
370class SearchAction(Action):
371 def __call__(self, twitter, options):
372 # We need to be pointing at search.twitter.com to work, and it is less
373 # tangly to do it here than in the main()
374 twitter.domain="search.twitter.com"
e15fbef3 375 twitter.uriparts=()
fd2bc885
WD
376 # We need to bypass the TwitterCall parameter encoding, so we
377 # don't encode the plus sign, so we have to encode it ourselves
5a77e17a
MV
378 query_string = "+".join(
379 [quote(term.decode(get_term_encoding()))
380 for term in options['extra_args']])
fd2bc885 381
e15fbef3 382 results = twitter.search(q=query_string)['results']
87be041f
WD
383 f = get_formatter('search', options)
384 for result in results:
385 resultStr = f(result, options)
386 if resultStr.strip():
387 printNicely(resultStr)
a8b5ad3e 388
1c11e6d7 389class AdminAction(Action):
efa0ba89 390 def __call__(self, twitter, options):
ec894371 391 if not (options['extra_args'] and options['extra_args'][0]):
e02facc9 392 raise TwitterError("You need to specify a user (screen name)")
87be041f 393 af = get_formatter('admin', options)
e02facc9
MV
394 try:
395 user = self.getUser(twitter, options['extra_args'][0])
396 except TwitterError, e:
397 print "There was a problem following or leaving the specified user."
f47ab046
MV
398 print "You may be trying to follow a user you are already following;"
399 print "Leaving a user you are not currently following;"
400 print "Or the user may not exist."
401 print "Sorry."
e02facc9 402 print
45688301 403 print e
e02facc9 404 else:
862cce81 405 printNicely(af(options['action'], user))
efa0ba89 406
69851f4c
AL
407class ListsAction(StatusAction):
408 def getStatuses(self, twitter, options):
3c2b5e3e
MV
409 if not options['extra_args']:
410 raise TwitterError("Please provide a user to query for lists")
411
412 screen_name = options['extra_args'][0]
413
414 if not options['extra_args'][1:]:
415 lists = twitter.user.lists(user=screen_name)['lists']
416 if not lists:
417 printNicely("This user has no lists.")
418 for list in lists:
69851f4c
AL
419 lf = get_formatter('lists', options)
420 printNicely(lf(list))
3c2b5e3e
MV
421 return []
422 else:
423 return reversed(twitter.user.lists.list.statuses(
424 user=screen_name, list=options['extra_args'][1]))
425
426
427class MyListsAction(ListsAction):
428 def getStatuses(self, twitter, options):
429 screen_name = twitter.account.verify_credentials()['screen_name']
430 options['extra_args'].insert(0, screen_name)
431 return ListsAction.getStatuses(self, twitter, options)
432
69851f4c 433
0ea01db7 434class FriendsAction(StatusAction):
39a6f562
MV
435 def getStatuses(self, twitter, options):
436 return reversed(twitter.statuses.friends_timeline(count=options["length"]))
efa0ba89 437
0ea01db7 438class PublicAction(StatusAction):
39a6f562
MV
439 def getStatuses(self, twitter, options):
440 return reversed(twitter.statuses.public_timeline(count=options["length"]))
0ea01db7 441
9a9f7ae7 442class RepliesAction(StatusAction):
39a6f562
MV
443 def getStatuses(self, twitter, options):
444 return reversed(twitter.statuses.replies(count=options["length"]))
9a9f7ae7 445
1c11e6d7 446class FollowAction(AdminAction):
efa0ba89 447 def getUser(self, twitter, user):
70955aae 448 return twitter.friendships.create(id=user)
efa0ba89 449
1c11e6d7 450class LeaveAction(AdminAction):
efa0ba89 451 def getUser(self, twitter, user):
70955aae 452 return twitter.friendships.destroy(id=user)
1c11e6d7 453
0ea01db7 454class SetStatusAction(Action):
455 def __call__(self, twitter, options):
5a77e17a 456 statusTxt = (" ".join(options['extra_args']).decode(get_term_encoding())
05b85831 457 if options['extra_args']
772fbdd1 458 else unicode(raw_input("message: ")))
459 status = (statusTxt.encode('utf8', 'replace'))
0ea01db7 460 twitter.statuses.update(status=status)
5251ea48 461
05b85831 462class TwitterShell(Action):
ec894371
MV
463
464 def render_prompt(self, prompt):
05b85831
HN
465 '''Parses the `prompt` string and returns the rendered version'''
466 prompt = prompt.strip("'").replace("\\'","'")
467 for colour in ansi.COLOURS_NAMED:
468 if '[%s]' %(colour) in prompt:
469 prompt = prompt.replace(
a8b5ad3e 470 '[%s]' %(colour), ansi.cmdColourNamed(colour))
05b85831
HN
471 prompt = prompt.replace('[R]', ansi.cmdReset())
472 return prompt
a8b5ad3e 473
05b85831
HN
474 def __call__(self, twitter, options):
475 prompt = self.render_prompt(options.get('prompt', 'twitter> '))
476 while True:
ec894371 477 options['action'] = ""
05b85831
HN
478 try:
479 args = raw_input(prompt).split()
480 parse_args(args, options)
481 if not options['action']:
482 continue
483 elif options['action'] == 'exit':
484 raise SystemExit(0)
485 elif options['action'] == 'shell':
486 print >>sys.stderr, 'Sorry Xzibit does not work here!'
487 continue
488 elif options['action'] == 'help':
489 print >>sys.stderr, '''\ntwitter> `action`\n
a8b5ad3e 490 The Shell Accepts all the command line actions along with:
05b85831 491
a8b5ad3e 492 exit Leave the twitter shell (^D may also be used)
05b85831 493
a8b5ad3e 494 Full CMD Line help is appended below for your convinience.'''
05b85831
HN
495 Action()(twitter, options)
496 options['action'] = ''
497 except NoSuchActionError, e:
498 print >>sys.stderr, e
499 except KeyboardInterrupt:
500 print >>sys.stderr, '\n[Keyboard Interrupt]'
501 except EOFError:
502 print >>sys.stderr
503 leaving = self.ask(subject='Leave')
504 if not leaving:
505 print >>sys.stderr, 'Excellent!'
506 else:
507 raise SystemExit(0)
508
a5e40197
MV
509class PythonPromptAction(Action):
510 def __call__(self, twitter, options):
511 try:
512 while True:
513 smrt_input(globals(), locals())
514 except EOFError:
515 pass
516
45688301
MV
517class HelpAction(Action):
518 def __call__(self, twitter, options):
519 print __doc__
520
086fc282
MV
521class DoNothingAction(Action):
522 def __call__(self, twitter, options):
523 pass
524
5251ea48 525actions = {
086fc282 526 'authorize' : DoNothingAction,
05b85831
HN
527 'follow' : FollowAction,
528 'friends' : FriendsAction,
69851f4c 529 'list' : ListsAction,
3c2b5e3e 530 'mylist' : MyListsAction,
05b85831
HN
531 'help' : HelpAction,
532 'leave' : LeaveAction,
533 'public' : PublicAction,
a5e40197 534 'pyprompt' : PythonPromptAction,
05b85831 535 'replies' : RepliesAction,
87be041f 536 'search' : SearchAction,
05b85831
HN
537 'set' : SetStatusAction,
538 'shell' : TwitterShell,
5251ea48 539}
540
21e3bd23 541def loadConfig(filename):
327e556b 542 options = dict(OPTIONS)
21e3bd23 543 if os.path.exists(filename):
544 cp = SafeConfigParser()
545 cp.read([filename])
086fc282 546 for option in ('format', 'prompt'):
327e556b
MV
547 if cp.has_option('twitter', option):
548 options[option] = cp.get('twitter', option)
549 return options
ae1d86aa 550
327e556b
MV
551def main(args=sys.argv[1:]):
552 arg_options = {}
44405280 553 try:
327e556b 554 parse_args(args, arg_options)
44405280
MV
555 except GetoptError, e:
556 print >> sys.stderr, "I can't do that, %s." %(e)
557 print >> sys.stderr
05b85831 558 raise SystemExit(1)
21e3bd23 559
7f22c021 560 config_path = os.path.expanduser(
327e556b 561 arg_options.get('config_filename') or OPTIONS.get('config_filename'))
7f22c021 562 config_options = loadConfig(config_path)
efa0ba89 563
327e556b
MV
564 # Apply the various options in order, the most important applied last.
565 # Defaults first, then what's read from config file, then command-line
566 # arguments.
567 options = dict(OPTIONS)
568 for d in config_options, arg_options:
569 for k,v in d.items():
570 if v: options[k] = v
05b85831 571
e02facc9
MV
572 if options['refresh'] and options['action'] not in (
573 'friends', 'public', 'replies'):
574 print >> sys.stderr, "You can only refresh the friends, public, or replies actions."
0ea01db7 575 print >> sys.stderr, "Use 'twitter -h' for help."
086fc282 576 return 1
05b85831 577
7f22c021
MV
578 oauth_filename = os.path.expanduser(options['oauth_filename'])
579
086fc282 580 if (options['action'] == 'authorize'
7f22c021 581 or not os.path.exists(oauth_filename)):
1b31d642
MV
582 oauth_dance(
583 "the Command-Line Tool", CONSUMER_KEY, CONSUMER_SECRET,
584 options['oauth_filename'])
6c527e72 585
7f22c021 586 oauth_token, oauth_token_secret = read_token_file(oauth_filename)
8ddd8500 587
9a148ed1 588 twitter = Twitter(
086fc282
MV
589 auth=OAuth(
590 oauth_token, oauth_token_secret, CONSUMER_KEY, CONSUMER_SECRET),
1cc9ab0b 591 secure=options['secure'],
9c07e547
MV
592 api_version='1',
593 domain='api.twitter.com')
086fc282 594
5251ea48 595 try:
05b85831
HN
596 Action()(twitter, options)
597 except NoSuchActionError, e:
598 print >>sys.stderr, e
599 raise SystemExit(1)
5251ea48 600 except TwitterError, e:
9c07e547 601 print >> sys.stderr, str(e)
5251ea48 602 print >> sys.stderr, "Use 'twitter -h' for help."
05b85831 603 raise SystemExit(1)