]> jfr.im git - z_archive/twitter.git/blame - twitter/cmdline.py
Bump version to 1.1.1
[z_archive/twitter.git] / twitter / cmdline.py
CommitLineData
7364ea65 1"""
5251ea48 2USAGE:
7364ea65 3
5251ea48 4 twitter [action] [options]
5
6ACTIONS:
1c11e6d7 7 follow add the specified user to your follow list
5251ea48 8 friends get latest tweets from your friends (default action)
45688301 9 help print this help text that you are currently reading
efa0ba89 10 leave remove the specified user from your following list
5251ea48 11 public get latest public tweets
9a9f7ae7 12 replies get latest replies
5251ea48 13 set set your twitter status
14
15OPTIONS:
16
17 -e --email <email> your email to login to twitter
18 -p --password <password> your twitter password
0ea01db7 19 -r --refresh run this command forever, polling every once
20 in a while (default: every 5 minutes)
21 -R --refresh-rate <rate> set the refresh rate (in seconds)
22 -f --format <format> specify the output format for status updates
21e3bd23 23 -c --config <filename> read username and password from given config
24 file (default ~/.twitter)
0ea01db7 25
26FORMATS for the --format option
27
28 default one line per status
29 verbose multiple lines per status, more verbose status info
327e556b
MV
30 urls nothing but URLs
31 ansi ansi colour (rainbow mode)
32
21e3bd23 33CONFIG FILES
34
327e556b
MV
35 The config file should contain a [twitter] header, and all the desired options
36 you wish to set, like so:
21e3bd23 37
38[twitter]
39email: <username>
40password: <password>
327e556b 41format: <desired_default_format_for_output>
7364ea65 42"""
43
5251ea48 44import sys
0ea01db7 45import time
44405280 46from getopt import getopt, GetoptError
f068ff42 47from getpass import getpass
0ea01db7 48import re
21e3bd23 49import os.path
50from ConfigParser import SafeConfigParser
5251ea48 51
52from api import Twitter, TwitterError
0b9960a3 53import ansi
5251ea48 54
3d17fdfc
MV
55# Please don't change this, it was provided by the fine folks at Twitter.
56# If you change it, it will not work.
57AGENT_STR = "twittercommandlinetoolpy"
45688301 58
327e556b 59OPTIONS = {
5251ea48 60 'email': None,
61 'password': None,
62 'action': 'friends',
0ea01db7 63 'refresh': False,
64 'refresh_rate': 600,
65 'format': 'default',
21e3bd23 66 'config_filename': os.environ.get('HOME', '') + os.sep + '.twitter',
5251ea48 67 'extra_args': []
68}
69
70def parse_args(args, options):
0ea01db7 71 long_opts = ['email', 'password', 'help', 'format', 'refresh',
21e3bd23 72 'refresh-rate', 'config']
73 short_opts = "e:p:f:h?rR:c:"
44405280 74 opts, extra_args = getopt(args, short_opts, long_opts)
efa0ba89 75
5251ea48 76 for opt, arg in opts:
77 if opt in ('-e', '--email'):
78 options['email'] = arg
79 elif opt in ('-p', '--password'):
80 options['password'] = arg
0ea01db7 81 elif opt in ('-f', '--format'):
82 options['format'] = arg
83 elif opt in ('-r', '--refresh'):
84 options['refresh'] = True
85 elif opt in ('-R', '--refresh-rate'):
86 options['refresh_rate'] = int(arg)
5251ea48 87 elif opt in ('-?', '-h', '--help'):
88 print __doc__
89 sys.exit(0)
21e3bd23 90 elif opt in ('-c', '--config'):
91 options['config_filename'] = arg
efa0ba89 92
ae1d86aa 93 if extra_args:
94 options['action'] = extra_args[0]
95 options['extra_args'] = extra_args[1:]
5251ea48 96
97class StatusFormatter(object):
98 def __call__(self, status):
f068ff42 99 return (u"%s %s" %(
0ea01db7 100 status['user']['screen_name'], status['text']))
5251ea48 101
0b9960a3
MV
102class AnsiStatusFormatter(object):
103 def __init__(self):
104 self._colourMap = ansi.ColourMap()
105
106 def __call__(self, status):
107 colour = self._colourMap.colourFor(status['user']['screen_name'])
108 return (u"%s%s%s %s" %(
109 ansi.cmdColour(colour), status['user']['screen_name'],
110 ansi.cmdReset(), status['text']))
111
f068ff42 112class VerboseStatusFormatter(object):
113 def __call__(self, status):
114 return (u"-- %s (%s) on %s\n%s\n" %(
115 status['user']['screen_name'],
116 status['user']['location'],
117 status['created_at'],
0ea01db7 118 status['text']))
f068ff42 119
0ea01db7 120class URLStatusFormatter(object):
121 urlmatch = re.compile(r'https?://\S+')
122 def __call__(self, status):
123 urls = self.urlmatch.findall(status['text'])
124 return u'\n'.join(urls) if urls else ""
125
1c11e6d7 126class AdminFormatter(object):
efa0ba89 127 def __call__(self, action, user):
da45d039
MV
128 user_str = u"%s (%s)" %(user['screen_name'], user['name'])
129 if action == "follow":
e02facc9 130 return u"You are now following %s.\n" %(user_str)
da45d039 131 else:
e02facc9 132 return u"You are no longer following %s.\n" %(user_str)
efa0ba89 133
1c11e6d7 134class VerboseAdminFormatter(object):
efa0ba89
MV
135 def __call__(self, action, user):
136 return(u"-- %s: %s (%s): %s" % (
137 "Following" if action == "follow" else "Leaving",
138 user['screen_name'],
139 user['name'],
140 user['url']))
141
1c11e6d7 142status_formatters = {
0ea01db7 143 'default': StatusFormatter,
144 'verbose': VerboseStatusFormatter,
0b9960a3
MV
145 'urls': URLStatusFormatter,
146 'ansi': AnsiStatusFormatter
0ea01db7 147}
1c11e6d7
WD
148
149admin_formatters = {
efa0ba89
MV
150 'default': AdminFormatter,
151 'verbose': VerboseAdminFormatter,
327e556b
MV
152 'urls': AdminFormatter,
153 'ansi': AdminFormatter
1c11e6d7 154}
efa0ba89 155
0ea01db7 156def get_status_formatter(options):
1c11e6d7 157 sf = status_formatters.get(options['format'])
0ea01db7 158 if (not sf):
159 raise TwitterError(
160 "Unknown formatter '%s'" %(options['format']))
161 return sf()
162
1c11e6d7 163def get_admin_formatter(options):
efa0ba89
MV
164 sf = admin_formatters.get(options['format'])
165 if (not sf):
166 raise TwitterError(
167 "Unknown formatter '%s'" %(options['format']))
168 return sf()
169
0ea01db7 170class Action(object):
171 pass
172
173class NoSuchAction(Action):
174 def __call__(self, twitter, options):
175 print >> sys.stderr, "No such action: ", options['action']
176 sys.exit(1)
177
178class StatusAction(Action):
179 def __call__(self, twitter, options):
180 statuses = self.getStatuses(twitter)
181 sf = get_status_formatter(options)
182 for status in statuses:
183 statusStr = sf(status)
184 if statusStr.strip():
185 print statusStr.encode(sys.stdout.encoding, 'replace')
1c11e6d7
WD
186
187class AdminAction(Action):
efa0ba89 188 def __call__(self, twitter, options):
da45d039 189 if not options['extra_args'][0]:
e02facc9 190 raise TwitterError("You need to specify a user (screen name)")
efa0ba89 191 af = get_admin_formatter(options)
e02facc9
MV
192 try:
193 user = self.getUser(twitter, options['extra_args'][0])
194 except TwitterError, e:
195 print "There was a problem following or leaving the specified user."
196 print " You may be trying to follow a user you are already following;"
197 print " Leaving a user you are not currently following;"
198 print " Or the user may not exist."
199 print " Sorry."
200 print
45688301 201 print e
e02facc9 202 else:
efa0ba89
MV
203 print af(options['action'], user).encode(sys.stdout.encoding, 'replace')
204
0ea01db7 205class FriendsAction(StatusAction):
206 def getStatuses(self, twitter):
207 return reversed(twitter.statuses.friends_timeline())
efa0ba89 208
0ea01db7 209class PublicAction(StatusAction):
210 def getStatuses(self, twitter):
211 return reversed(twitter.statuses.public_timeline())
212
9a9f7ae7
MV
213class RepliesAction(StatusAction):
214 def getStatuses(self, twitter):
215 return reversed(twitter.statuses.replies())
216
1c11e6d7 217class FollowAction(AdminAction):
efa0ba89 218 def getUser(self, twitter, user):
da45d039 219 return twitter.notifications.follow(id=user)
efa0ba89 220
1c11e6d7 221class LeaveAction(AdminAction):
efa0ba89 222 def getUser(self, twitter, user):
da45d039 223 return twitter.notifications.leave(id=user)
1c11e6d7 224
0ea01db7 225class SetStatusAction(Action):
226 def __call__(self, twitter, options):
772fbdd1 227 statusTxt = (u" ".join(options['extra_args'])
228 if options['extra_args']
229 else unicode(raw_input("message: ")))
230 status = (statusTxt.encode('utf8', 'replace'))
0ea01db7 231 twitter.statuses.update(status=status)
5251ea48 232
45688301
MV
233class HelpAction(Action):
234 def __call__(self, twitter, options):
235 print __doc__
236
5251ea48 237actions = {
efa0ba89 238 'follow': FollowAction,
0ea01db7 239 'friends': FriendsAction,
45688301 240 'help': HelpAction,
1c11e6d7 241 'leave': LeaveAction,
0ea01db7 242 'public': PublicAction,
9a9f7ae7 243 'replies': RepliesAction,
0ea01db7 244 'set': SetStatusAction,
5251ea48 245}
246
21e3bd23 247def loadConfig(filename):
327e556b 248 options = dict(OPTIONS)
21e3bd23 249 if os.path.exists(filename):
250 cp = SafeConfigParser()
251 cp.read([filename])
327e556b
MV
252 for option in ('email', 'password', 'format'):
253 if cp.has_option('twitter', option):
254 options[option] = cp.get('twitter', option)
255 return options
ae1d86aa 256
327e556b
MV
257def main(args=sys.argv[1:]):
258 arg_options = {}
44405280 259 try:
327e556b 260 parse_args(args, arg_options)
44405280
MV
261 except GetoptError, e:
262 print >> sys.stderr, "I can't do that, %s." %(e)
263 print >> sys.stderr
264 sys.exit(1)
21e3bd23 265
327e556b
MV
266 config_options = loadConfig(
267 arg_options.get('config_filename') or OPTIONS.get('config_filename'))
efa0ba89 268
327e556b
MV
269 # Apply the various options in order, the most important applied last.
270 # Defaults first, then what's read from config file, then command-line
271 # arguments.
272 options = dict(OPTIONS)
273 for d in config_options, arg_options:
274 for k,v in d.items():
275 if v: options[k] = v
276
e02facc9
MV
277 if options['refresh'] and options['action'] not in (
278 'friends', 'public', 'replies'):
279 print >> sys.stderr, "You can only refresh the friends, public, or replies actions."
0ea01db7 280 print >> sys.stderr, "Use 'twitter -h' for help."
281 sys.exit(1)
e02facc9 282
f068ff42 283 if options['email'] and not options['password']:
284 options['password'] = getpass("Twitter password: ")
45688301
MV
285
286 twitter = Twitter(options['email'], options['password'], agent=AGENT_STR)
0ea01db7 287 action = actions.get(options['action'], NoSuchAction)()
45688301 288
5251ea48 289 try:
0ea01db7 290 doAction = lambda : action(twitter, options)
efa0ba89 291
1c11e6d7 292 if (options['refresh'] and isinstance(action, StatusAction)):
efa0ba89
MV
293 while True:
294 doAction()
295 time.sleep(options['refresh_rate'])
0ea01db7 296 else:
efa0ba89
MV
297 doAction()
298
5251ea48 299 except TwitterError, e:
f1a8ed67 300 print >> sys.stderr, e.args[0]
5251ea48 301 print >> sys.stderr, "Use 'twitter -h' for help."
302 sys.exit(1)
0ea01db7 303 except KeyboardInterrupt:
304 pass
efa0ba89 305