]> jfr.im git - z_archive/twitter.git/blob - twitter/follow.py
Update twitter/twitter_globals.py
[z_archive/twitter.git] / twitter / follow.py
1 """USAGE
2 twitter-follow [options] <user>
3
4 DESCRIPTION
5 Display all following/followers of a user, one user per line.
6
7 OPTIONS
8 -o --oauth authenticate to Twitter using OAuth (default no)
9 -r --followers display followers of the given user (default)
10 -g --following display users the given user is following
11 -a --api-rate see your current API rate limit status
12
13 AUTHENTICATION
14 Authenticate to Twitter using OAuth to see following/followers of private
15 profiles and have higher API rate limits. OAuth authentication tokens
16 are stored in the file .twitter-follow_oauth in your home directory.
17 """
18
19 from __future__ import print_function
20
21 import os, sys, time, calendar
22 from getopt import gnu_getopt as getopt, GetoptError
23
24 try:
25 import urllib.request as urllib2
26 import http.client as httplib
27 except ImportError:
28 import urllib2
29 import httplib
30
31 # T-Follow (Twitter-Follow) application registered by @stalkr_
32 CONSUMER_KEY='USRZQfvFFjB6UvZIN2Edww'
33 CONSUMER_SECRET='AwGAaSzZa5r0TDL8RKCDtffnI9H9mooZUdOa95nw8'
34
35 from .api import Twitter, TwitterError
36 from .oauth import OAuth, read_token_file
37 from .oauth_dance import oauth_dance
38 from .auth import NoAuth
39 from .util import Fail, err
40
41
42 def parse_args(args, options):
43 """Parse arguments from command-line to set options."""
44 long_opts = ['help', 'oauth', 'followers', 'following', 'api-rate']
45 short_opts = "horga"
46 opts, extra_args = getopt(args, short_opts, long_opts)
47
48 for opt, arg in opts:
49 if opt in ('-h', '--help'):
50 print(__doc__)
51 raise SystemExit(1)
52 elif opt in ('-o', '--oauth'):
53 options['oauth'] = True
54 elif opt in ('-r', '--followers'):
55 options['followers'] = True
56 elif opt in ('-g', '--following'):
57 options['followers'] = False
58 elif opt in ('-a', '--api-rate'):
59 options['api-rate' ] = True
60
61 options['extra_args'] = extra_args
62
63 def lookup_portion(twitter, user_ids):
64 """Resolve a limited list of user ids to screen names."""
65 users = {}
66 kwargs = dict(user_id=",".join(map(str, user_ids)), skip_status=1)
67 for u in twitter.users.lookup(**kwargs):
68 users[int(u['id'])] = u['screen_name']
69 return users
70
71 def lookup(twitter, user_ids):
72 """Resolve an entire list of user ids to screen names."""
73 users = {}
74 api_limit = 100
75 for i in range(0, len(user_ids), api_limit):
76 fail = Fail()
77 while True:
78 try:
79 portion = lookup_portion(twitter, user_ids[i:][:api_limit])
80 except TwitterError as e:
81 if e.e.code == 400:
82 err("Fail: %i API rate limit exceeded" % e.e.code)
83 rate = twitter.account.rate_limit_status()
84 reset = rate['reset_time_in_seconds']
85 reset = time.asctime(time.localtime(reset))
86 delay = int(rate['reset_time_in_seconds']
87 - time.time()) + 5 # avoid race
88 err("Hourly limit of %i requests reached, next reset on "
89 "%s: going to sleep for %i secs"
90 % (rate['hourly_limit'], reset, delay))
91 fail.wait(delay)
92 continue
93 elif e.e.code == 502:
94 err("Fail: %i Service currently unavailable, retrying..."
95 % e.e.code)
96 else:
97 err("Fail: %s\nRetrying..." % str(e)[:500])
98 fail.wait(3)
99 except urllib2.URLError as e:
100 err("Fail: urllib2.URLError %s - Retrying..." % str(e))
101 fail.wait(3)
102 except httplib.error as e:
103 err("Fail: httplib.error %s - Retrying..." % str(e))
104 fail.wait(3)
105 except KeyError as e:
106 err("Fail: KeyError %s - Retrying..." % str(e))
107 fail.wait(3)
108 else:
109 users.update(portion)
110 err("Resolving user ids to screen names: %i/%i"
111 % (len(users), len(user_ids)))
112 break
113 return users
114
115 def follow_portion(twitter, screen_name, cursor=-1, followers=True):
116 """Get a portion of followers/following for a user."""
117 kwargs = dict(screen_name=screen_name, cursor=cursor)
118 if followers:
119 t = twitter.followers.ids(**kwargs)
120 else: # following
121 t = twitter.friends.ids(**kwargs)
122 return t['ids'], t['next_cursor']
123
124 def follow(twitter, screen_name, followers=True):
125 """Get the entire list of followers/following for a user."""
126 user_ids = []
127 cursor = -1
128 fail = Fail()
129 while True:
130 try:
131 portion, cursor = follow_portion(twitter, screen_name, cursor,
132 followers)
133 except TwitterError as e:
134 if e.e.code == 401:
135 reason = ("follow%s of that user are protected"
136 % ("ers" if followers else "ing"))
137 err("Fail: %i Unauthorized (%s)" % (e.e.code, reason))
138 break
139 elif e.e.code == 400:
140 err("Fail: %i API rate limit exceeded" % e.e.code)
141 rate = twitter.account.rate_limit_status()
142 reset = rate['reset_time_in_seconds']
143 reset = time.asctime(time.localtime(reset))
144 delay = int(rate['reset_time_in_seconds']
145 - time.time()) + 5 # avoid race
146 err("Hourly limit of %i requests reached, next reset on %s: "
147 "going to sleep for %i secs" % (rate['hourly_limit'],
148 reset, delay))
149 fail.wait(delay)
150 continue
151 elif e.e.code == 502:
152 err("Fail: %i Service currently unavailable, retrying..."
153 % e.e.code)
154 else:
155 err("Fail: %s\nRetrying..." % str(e)[:500])
156 fail.wait(3)
157 except urllib2.URLError as e:
158 err("Fail: urllib2.URLError %s - Retrying..." % str(e))
159 fail.wait(3)
160 except httplib.error as e:
161 err("Fail: httplib.error %s - Retrying..." % str(e))
162 fail.wait(3)
163 except KeyError as e:
164 err("Fail: KeyError %s - Retrying..." % str(e))
165 fail.wait(3)
166 else:
167 new = -len(user_ids)
168 user_ids = list(set(user_ids + portion))
169 new += len(user_ids)
170 what = "follow%s" % ("ers" if followers else "ing")
171 err("Browsing %s %s, new: %i" % (screen_name, what, new))
172 if cursor == 0:
173 break
174 fail = Fail()
175 return user_ids
176
177
178 def rate_limit_status(twitter):
179 """Print current Twitter API rate limit status."""
180 r = twitter.account.rate_limit_status()
181 print("Remaining API requests: %i/%i (hourly limit)"
182 % (r['remaining_hits'], r['hourly_limit']))
183 print("Next reset in %is (%s)"
184 % (int(r['reset_time_in_seconds'] - time.time()),
185 time.asctime(time.localtime(r['reset_time_in_seconds']))))
186
187 def main(args=sys.argv[1:]):
188 options = {
189 'oauth': False,
190 'followers': True,
191 'api-rate': False
192 }
193 try:
194 parse_args(args, options)
195 except GetoptError as e:
196 err("I can't do that, %s." % e)
197 raise SystemExit(1)
198
199 # exit if no user or given, except if asking for API rate
200 if not options['extra_args'] and not options['api-rate']:
201 print(__doc__)
202 raise SystemExit(1)
203
204 # authenticate using OAuth, asking for token if necessary
205 if options['oauth']:
206 oauth_filename = (os.getenv("HOME", "") + os.sep
207 + ".twitter-follow_oauth")
208 if not os.path.exists(oauth_filename):
209 oauth_dance("Twitter-Follow", CONSUMER_KEY, CONSUMER_SECRET,
210 oauth_filename)
211 oauth_token, oauth_token_secret = read_token_file(oauth_filename)
212 auth = OAuth(oauth_token, oauth_token_secret, CONSUMER_KEY,
213 CONSUMER_SECRET)
214 else:
215 auth = NoAuth()
216
217 twitter = Twitter(auth=auth, api_version='1', domain='api.twitter.com')
218
219 if options['api-rate']:
220 rate_limit_status(twitter)
221 return
222
223 # obtain list of followers (or following) for every given user
224 for user in options['extra_args']:
225 user_ids, users = [], {}
226 try:
227 user_ids = follow(twitter, user, options['followers'])
228 users = lookup(twitter, user_ids)
229 except KeyboardInterrupt as e:
230 err()
231 err("Interrupted.")
232 raise SystemExit(1)
233
234 for uid in user_ids:
235 print(users[uid].encode("utf-8"))
236
237 # print total on stderr to separate from user list on stdout
238 if options['followers']:
239 err("Total followers for %s: %i" % (user, len(user_ids)))
240 else:
241 err("Total users %s is following: %i" % (user, len(user_ids)))