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