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