]> jfr.im git - z_archive/twitter.git/blame - twitter/cmdline.py
Merge branch 'master' into python3
[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
f7e63802 73from configparser import SafeConfigParser
a4b5e65b 74import datetime
f7e63802 75from urllib.parse import quote
6c527e72 76import webbrowser
5251ea48 77
f7e63802
MV
78from .api import Twitter, TwitterError
79from .oauth import OAuth, write_token_file, read_token_file
80from .oauth_dance import oauth_dance
81from . import ansi
7bfe7d97 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 151 def __call__(self, status, options):
f7e63802 152 return ("%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'])
f7e63802 162 return ("%s%s%s%s %s" %(
39a6f562 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):
f7e63802 169 return ("-- %s (%s) on %s\n%s\n" %(
f068ff42 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'])
f7e63802 179 return '\n'.join(urls) if urls else ""
0ea01db7 180
3c2b5e3e 181
69851f4c
AL
182class ListsFormatter(object):
183 def __call__(self, list):
184 if list['description']:
f7e63802 185 list_str = "%-30s (%s)" % (list['name'], list['description'])
69851f4c 186 else:
f7e63802
MV
187 list_str = "%-30s" % (list['name'])
188 return "%s\n" % list_str
69851f4c
AL
189
190class ListsVerboseFormatter(object):
191 def __call__(self, list):
f7e63802 192 list_str = "%-30s\n description: %s\n members: %s\n mode:%s\n" % (list['name'], list['description'], list['member_count'], list['mode'])
69851f4c
AL
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'])
f7e63802 201 return ("%s%-15s%s %s" %(
69851f4c
AL
202 ansi.cmdColour(colour), list['name'],
203 ansi.cmdReset(), list['description']))
204
205
1c11e6d7 206class AdminFormatter(object):
efa0ba89 207 def __call__(self, action, user):
f7e63802 208 user_str = "%s (%s)" %(user['screen_name'], user['name'])
da45d039 209 if action == "follow":
f7e63802 210 return "You are now following %s.\n" %(user_str)
da45d039 211 else:
f7e63802 212 return "You are no longer following %s.\n" %(user_str)
efa0ba89 213
1c11e6d7 214class VerboseAdminFormatter(object):
efa0ba89 215 def __call__(self, action, user):
f7e63802 216 return("-- %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):
f7e63802 224 return("%s%s %s" %(
87be041f
WD
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'])
f7e63802 235 return '\n'.join(urls) if urls else ""
87be041f
WD
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'])
f7e63802 243 return ("%s%s%s%s %s" %(
87be041f
WD
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:
f7e63802 318 answer = input(prompt).lower()
05b85831 319 if careful:
f47ab046 320 return answer in ('yes', 'y')
05b85831 321 else:
f47ab046 322 return answer not in ('no', 'n')
05b85831 323 except EOFError:
f7e63802 324 print(file=sys.stderr) # Put Newline since Enter was never pressed
05b85831
HN
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:
f7e63802 345 print('\n[Keyboard Interrupt]', file=sys.stderr)
05b85831
HN
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):
d1719c90
MV
356 sys.stdout.buffer.write(string.encode('utf8'))
357 print()
a8b5ad3e 358
0ea01db7 359class StatusAction(Action):
360 def __call__(self, twitter, options):
39a6f562 361 statuses = self.getStatuses(twitter, options)
87be041f 362 sf = get_formatter('status', options)
0ea01db7 363 for status in statuses:
39a6f562 364 statusStr = sf(status, options)
0ea01db7 365 if statusStr.strip():
862cce81 366 printNicely(statusStr)
1c11e6d7 367
87be041f
WD
368class SearchAction(Action):
369 def __call__(self, twitter, options):
370 # We need to be pointing at search.twitter.com to work, and it is less
371 # tangly to do it here than in the main()
372 twitter.domain="search.twitter.com"
e15fbef3 373 twitter.uriparts=()
fd2bc885
WD
374 # We need to bypass the TwitterCall parameter encoding, so we
375 # don't encode the plus sign, so we have to encode it ourselves
5a77e17a 376 query_string = "+".join(
7fd7f08a 377 [quote(term)
5a77e17a 378 for term in options['extra_args']])
fd2bc885 379
e15fbef3 380 results = twitter.search(q=query_string)['results']
87be041f
WD
381 f = get_formatter('search', options)
382 for result in results:
383 resultStr = f(result, options)
384 if resultStr.strip():
385 printNicely(resultStr)
a8b5ad3e 386
1c11e6d7 387class AdminAction(Action):
efa0ba89 388 def __call__(self, twitter, options):
ec894371 389 if not (options['extra_args'] and options['extra_args'][0]):
e02facc9 390 raise TwitterError("You need to specify a user (screen name)")
87be041f 391 af = get_formatter('admin', options)
e02facc9
MV
392 try:
393 user = self.getUser(twitter, options['extra_args'][0])
f7e63802
MV
394 except TwitterError as e:
395 print("There was a problem following or leaving the specified user.")
396 print("You may be trying to follow a user you are already following;")
397 print("Leaving a user you are not currently following;")
398 print("Or the user may not exist.")
399 print("Sorry.")
400 print()
401 print(e)
e02facc9 402 else:
862cce81 403 printNicely(af(options['action'], user))
efa0ba89 404
69851f4c
AL
405class ListsAction(StatusAction):
406 def getStatuses(self, twitter, options):
3c2b5e3e
MV
407 if not options['extra_args']:
408 raise TwitterError("Please provide a user to query for lists")
409
410 screen_name = options['extra_args'][0]
411
412 if not options['extra_args'][1:]:
413 lists = twitter.user.lists(user=screen_name)['lists']
414 if not lists:
415 printNicely("This user has no lists.")
416 for list in lists:
69851f4c
AL
417 lf = get_formatter('lists', options)
418 printNicely(lf(list))
3c2b5e3e
MV
419 return []
420 else:
421 return reversed(twitter.user.lists.list.statuses(
422 user=screen_name, list=options['extra_args'][1]))
423
424
425class MyListsAction(ListsAction):
426 def getStatuses(self, twitter, options):
427 screen_name = twitter.account.verify_credentials()['screen_name']
428 options['extra_args'].insert(0, screen_name)
429 return ListsAction.getStatuses(self, twitter, options)
430
69851f4c 431
0ea01db7 432class FriendsAction(StatusAction):
39a6f562
MV
433 def getStatuses(self, twitter, options):
434 return reversed(twitter.statuses.friends_timeline(count=options["length"]))
efa0ba89 435
0ea01db7 436class PublicAction(StatusAction):
39a6f562
MV
437 def getStatuses(self, twitter, options):
438 return reversed(twitter.statuses.public_timeline(count=options["length"]))
0ea01db7 439
9a9f7ae7 440class RepliesAction(StatusAction):
39a6f562
MV
441 def getStatuses(self, twitter, options):
442 return reversed(twitter.statuses.replies(count=options["length"]))
9a9f7ae7 443
1c11e6d7 444class FollowAction(AdminAction):
efa0ba89 445 def getUser(self, twitter, user):
70955aae 446 return twitter.friendships.create(id=user)
efa0ba89 447
1c11e6d7 448class LeaveAction(AdminAction):
efa0ba89 449 def getUser(self, twitter, user):
70955aae 450 return twitter.friendships.destroy(id=user)
1c11e6d7 451
0ea01db7 452class SetStatusAction(Action):
453 def __call__(self, twitter, options):
a87e8a6c 454 statusTxt = (" ".join(options['extra_args'])
05b85831 455 if options['extra_args']
f7e63802 456 else str(input("message: ")))
24e8d939 457 twitter.statuses.update(status=statusTxt)
5251ea48 458
05b85831 459class TwitterShell(Action):
ec894371
MV
460
461 def render_prompt(self, prompt):
05b85831
HN
462 '''Parses the `prompt` string and returns the rendered version'''
463 prompt = prompt.strip("'").replace("\\'","'")
464 for colour in ansi.COLOURS_NAMED:
465 if '[%s]' %(colour) in prompt:
466 prompt = prompt.replace(
a8b5ad3e 467 '[%s]' %(colour), ansi.cmdColourNamed(colour))
05b85831
HN
468 prompt = prompt.replace('[R]', ansi.cmdReset())
469 return prompt
a8b5ad3e 470
05b85831
HN
471 def __call__(self, twitter, options):
472 prompt = self.render_prompt(options.get('prompt', 'twitter> '))
473 while True:
ec894371 474 options['action'] = ""
05b85831 475 try:
f7e63802 476 args = input(prompt).split()
05b85831
HN
477 parse_args(args, options)
478 if not options['action']:
479 continue
480 elif options['action'] == 'exit':
481 raise SystemExit(0)
482 elif options['action'] == 'shell':
f7e63802 483 print('Sorry Xzibit does not work here!', file=sys.stderr)
05b85831
HN
484 continue
485 elif options['action'] == 'help':
f7e63802 486 print('''\ntwitter> `action`\n
a8b5ad3e 487 The Shell Accepts all the command line actions along with:
05b85831 488
a8b5ad3e 489 exit Leave the twitter shell (^D may also be used)
05b85831 490
f7e63802 491 Full CMD Line help is appended below for your convinience.''', file=sys.stderr)
05b85831
HN
492 Action()(twitter, options)
493 options['action'] = ''
f7e63802
MV
494 except NoSuchActionError as e:
495 print(e, file=sys.stderr)
05b85831 496 except KeyboardInterrupt:
f7e63802 497 print('\n[Keyboard Interrupt]', file=sys.stderr)
05b85831 498 except EOFError:
f7e63802 499 print(file=sys.stderr)
05b85831
HN
500 leaving = self.ask(subject='Leave')
501 if not leaving:
f7e63802 502 print('Excellent!', file=sys.stderr)
05b85831
HN
503 else:
504 raise SystemExit(0)
505
a5e40197
MV
506class PythonPromptAction(Action):
507 def __call__(self, twitter, options):
508 try:
509 while True:
510 smrt_input(globals(), locals())
511 except EOFError:
512 pass
513
45688301
MV
514class HelpAction(Action):
515 def __call__(self, twitter, options):
f7e63802 516 print(__doc__)
45688301 517
086fc282
MV
518class DoNothingAction(Action):
519 def __call__(self, twitter, options):
520 pass
521
5251ea48 522actions = {
086fc282 523 'authorize' : DoNothingAction,
05b85831
HN
524 'follow' : FollowAction,
525 'friends' : FriendsAction,
69851f4c 526 'list' : ListsAction,
3c2b5e3e 527 'mylist' : MyListsAction,
05b85831
HN
528 'help' : HelpAction,
529 'leave' : LeaveAction,
530 'public' : PublicAction,
a5e40197 531 'pyprompt' : PythonPromptAction,
05b85831 532 'replies' : RepliesAction,
87be041f 533 'search' : SearchAction,
05b85831
HN
534 'set' : SetStatusAction,
535 'shell' : TwitterShell,
5251ea48 536}
537
21e3bd23 538def loadConfig(filename):
327e556b 539 options = dict(OPTIONS)
21e3bd23 540 if os.path.exists(filename):
541 cp = SafeConfigParser()
542 cp.read([filename])
086fc282 543 for option in ('format', 'prompt'):
327e556b
MV
544 if cp.has_option('twitter', option):
545 options[option] = cp.get('twitter', option)
546 return options
ae1d86aa 547
327e556b
MV
548def main(args=sys.argv[1:]):
549 arg_options = {}
44405280 550 try:
327e556b 551 parse_args(args, arg_options)
f7e63802
MV
552 except GetoptError as e:
553 print("I can't do that, %s." %(e), file=sys.stderr)
554 print(file=sys.stderr)
05b85831 555 raise SystemExit(1)
21e3bd23 556
7f22c021 557 config_path = os.path.expanduser(
327e556b 558 arg_options.get('config_filename') or OPTIONS.get('config_filename'))
7f22c021 559 config_options = loadConfig(config_path)
efa0ba89 560
327e556b
MV
561 # Apply the various options in order, the most important applied last.
562 # Defaults first, then what's read from config file, then command-line
563 # arguments.
564 options = dict(OPTIONS)
565 for d in config_options, arg_options:
f7e63802 566 for k,v in list(d.items()):
327e556b 567 if v: options[k] = v
05b85831 568
e02facc9
MV
569 if options['refresh'] and options['action'] not in (
570 'friends', 'public', 'replies'):
f7e63802
MV
571 print("You can only refresh the friends, public, or replies actions.", file=sys.stderr)
572 print("Use 'twitter -h' for help.", file=sys.stderr)
086fc282 573 return 1
05b85831 574
7f22c021
MV
575 oauth_filename = os.path.expanduser(options['oauth_filename'])
576
086fc282 577 if (options['action'] == 'authorize'
7f22c021 578 or not os.path.exists(oauth_filename)):
1b31d642
MV
579 oauth_dance(
580 "the Command-Line Tool", CONSUMER_KEY, CONSUMER_SECRET,
581 options['oauth_filename'])
6c527e72 582
7f22c021 583 oauth_token, oauth_token_secret = read_token_file(oauth_filename)
8ddd8500 584
9a148ed1 585 twitter = Twitter(
086fc282
MV
586 auth=OAuth(
587 oauth_token, oauth_token_secret, CONSUMER_KEY, CONSUMER_SECRET),
1cc9ab0b 588 secure=options['secure'],
9c07e547
MV
589 api_version='1',
590 domain='api.twitter.com')
086fc282 591
5251ea48 592 try:
05b85831 593 Action()(twitter, options)
f7e63802
MV
594 except NoSuchActionError as e:
595 print(e, file=sys.stderr)
05b85831 596 raise SystemExit(1)
f7e63802
MV
597 except TwitterError as e:
598 print(str(e), file=sys.stderr)
599 print("Use 'twitter -h' for help.", file=sys.stderr)
05b85831 600 raise SystemExit(1)