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