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