]> jfr.im git - z_archive/twitter.git/blob - twitter/cmdline.py
Add the leave and follow commands to the cmdline tool
[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 leave remove the specified user from your following list
10 public get latest public tweets
11 replies get latest replies
12 set set your twitter status
13
14 OPTIONS:
15
16 -e --email <email> your email to login to twitter
17 -p --password <password> your twitter password
18 -r --refresh run this command forever, polling every once
19 in a while (default: every 5 minutes)
20 -R --refresh-rate <rate> set the refresh rate (in seconds)
21 -f --format <format> specify the output format for status updates
22 -c --config <filename> read username and password from given config
23 file (default ~/.twitter)
24
25 FORMATS for the --format option
26
27 default one line per status
28 verbose multiple lines per status, more verbose status info
29 urls nothing but URLs. Dare you click them?
30
31 CONFIG FILES
32
33 The config file should contain your email and password like so:
34
35 [twitter]
36 email: <username>
37 password: <password>
38 """
39
40 import sys
41 import time
42 from getopt import getopt
43 from getpass import getpass
44 import re
45 import os.path
46 from ConfigParser import SafeConfigParser
47
48 from api import Twitter, TwitterError
49
50 options = {
51 'email': None,
52 'password': None,
53 'action': 'friends',
54 'refresh': False,
55 'refresh_rate': 600,
56 'format': 'default',
57 'config_filename': os.environ.get('HOME', '') + os.sep + '.twitter',
58 'extra_args': []
59 }
60
61 def parse_args(args, options):
62 long_opts = ['email', 'password', 'help', 'format', 'refresh',
63 'refresh-rate', 'config']
64 short_opts = "e:p:f:h?rR:c:"
65 opts, extra_args = getopt(args, short_opts, long_opts)
66
67 for opt, arg in opts:
68 if opt in ('-e', '--email'):
69 options['email'] = arg
70 elif opt in ('-p', '--password'):
71 options['password'] = arg
72 elif opt in ('-f', '--format'):
73 options['format'] = arg
74 elif opt in ('-r', '--refresh'):
75 options['refresh'] = True
76 elif opt in ('-R', '--refresh-rate'):
77 options['refresh_rate'] = int(arg)
78 elif opt in ('-?', '-h', '--help'):
79 print __doc__
80 sys.exit(0)
81 elif opt in ('-c', '--config'):
82 options['config_filename'] = arg
83
84 if extra_args:
85 options['action'] = extra_args[0]
86 options['extra_args'] = extra_args[1:]
87
88 class StatusFormatter(object):
89 def __call__(self, status):
90 return (u"%s %s" %(
91 status['user']['screen_name'], status['text']))
92
93 class VerboseStatusFormatter(object):
94 def __call__(self, status):
95 return (u"-- %s (%s) on %s\n%s\n" %(
96 status['user']['screen_name'],
97 status['user']['location'],
98 status['created_at'],
99 status['text']))
100
101 class URLStatusFormatter(object):
102 urlmatch = re.compile(r'https?://\S+')
103 def __call__(self, status):
104 urls = self.urlmatch.findall(status['text'])
105 return u'\n'.join(urls) if urls else ""
106
107 class AdminFormatter(object):
108 def __call__(self, action, user):
109 return(u"%s: %s" %(
110 "Following" if action == "follow" else "Leaving", user['name']))
111
112 class VerboseAdminFormatter(object):
113 def __call__(self, action, user):
114 return(u"-- %s: %s (%s): %s" % (
115 "Following" if action == "follow" else "Leaving",
116 user['screen_name'],
117 user['name'],
118 user['url']))
119
120 class URLAdminFormatter(object):
121 def __call__(self, action, user):
122 return("Admin actions do not support the URL formatter")
123
124 status_formatters = {
125 'default': StatusFormatter,
126 'verbose': VerboseStatusFormatter,
127 'urls': URLStatusFormatter
128 }
129
130 admin_formatters = {
131 'default': AdminFormatter,
132 'verbose': VerboseAdminFormatter,
133 'urls': URLAdminFormatter
134 }
135
136 def get_status_formatter(options):
137 sf = status_formatters.get(options['format'])
138 if (not sf):
139 raise TwitterError(
140 "Unknown formatter '%s'" %(options['format']))
141 return sf()
142
143 def get_admin_formatter(options):
144 sf = admin_formatters.get(options['format'])
145 if (not sf):
146 raise TwitterError(
147 "Unknown formatter '%s'" %(options['format']))
148 return sf()
149
150 class Action(object):
151 pass
152
153 class NoSuchAction(Action):
154 def __call__(self, twitter, options):
155 print >> sys.stderr, "No such action: ", options['action']
156 sys.exit(1)
157
158 class StatusAction(Action):
159 def __call__(self, twitter, options):
160 statuses = self.getStatuses(twitter)
161 sf = get_status_formatter(options)
162 for status in statuses:
163 statusStr = sf(status)
164 if statusStr.strip():
165 print statusStr.encode(sys.stdout.encoding, 'replace')
166
167 class AdminAction(Action):
168 def __call__(self, twitter, options):
169 if (not options['extra_args'][0]):
170 raise TwitterError("You need to specify a User (Screen Name)")
171 af = get_admin_formatter(options)
172 user = self.getUser(twitter, options['extra_args'][0])
173 if(user):
174 print af(options['action'], user).encode(sys.stdout.encoding, 'replace')
175
176 class FriendsAction(StatusAction):
177 def getStatuses(self, twitter):
178 return reversed(twitter.statuses.friends_timeline())
179
180 class PublicAction(StatusAction):
181 def getStatuses(self, twitter):
182 return reversed(twitter.statuses.public_timeline())
183
184 class RepliesAction(StatusAction):
185 def getStatuses(self, twitter):
186 return reversed(twitter.statuses.replies())
187
188 class FollowAction(AdminAction):
189 def getUser(self, twitter, user):
190 # Twitter wants /notifications/follow/user.json?id=user
191 return twitter.notifications.follow.__getattr__(user)(id=user)
192
193 class LeaveAction(AdminAction):
194 def getUser(self, twitter, user):
195 return twitter.notifications.leave.__getattr__(user)(id=user)
196
197 class SetStatusAction(Action):
198 def __call__(self, twitter, options):
199 statusTxt = (u" ".join(options['extra_args'])
200 if options['extra_args']
201 else unicode(raw_input("message: ")))
202 status = (statusTxt.encode('utf8', 'replace'))
203 twitter.statuses.update(status=status)
204
205 actions = {
206 'follow': FollowAction,
207 'friends': FriendsAction,
208 'leave': LeaveAction,
209 'public': PublicAction,
210 'replies': RepliesAction,
211 'set': SetStatusAction,
212 }
213
214 def loadConfig(filename):
215 email = None
216 password = None
217 if os.path.exists(filename):
218 cp = SafeConfigParser()
219 cp.read([filename])
220 email = cp.get('twitter', 'email', None)
221 password = cp.get('twitter', 'password', None)
222 return email, password
223
224 def main():
225 return main_with_args(sys.argv[1:])
226
227 def main_with_args(args):
228 parse_args(args, options)
229
230 email, password = loadConfig(options['config_filename'])
231 if not options['email']: options['email'] = email
232 if not options['password']: options['password'] = password
233
234 #Maybe check for AdminAction here, but whatever you do, don't write TODO
235 if options['refresh'] and options['action'] == 'set':
236 print >> sys.stderr, "You can't repeatedly set your status, silly"
237 print >> sys.stderr, "Use 'twitter -h' for help."
238 sys.exit(1)
239 if options['email'] and not options['password']:
240 options['password'] = getpass("Twitter password: ")
241 twitter = Twitter(options['email'], options['password'])
242 action = actions.get(options['action'], NoSuchAction)()
243 try:
244 doAction = lambda : action(twitter, options)
245
246 if (options['refresh'] and isinstance(action, StatusAction)):
247 while True:
248 doAction()
249 time.sleep(options['refresh_rate'])
250 else:
251 doAction()
252
253 except TwitterError, e:
254 print >> sys.stderr, e.args[0]
255 print >> sys.stderr, "Use 'twitter -h' for help."
256 sys.exit(1)
257 except KeyboardInterrupt:
258 pass
259