@@ -43,12 +43,14 @@ function getContributionGraphs(string $user): array
43
43
// Get the years the user has contributed
44
44
$ contributionYears = getContributionYears ($ user );
45
45
// build a list of individual requests
46
+ $ tokens = [];
46
47
$ requests = [];
47
48
foreach ($ contributionYears as $ year ) {
48
49
// create query for year
49
50
$ query = buildContributionGraphQuery ($ user , $ year );
50
51
// create curl request
51
- $ requests [$ year ] = getGraphQLCurlHandle ($ query );
52
+ $ tokens [$ year ] = getGitHubToken ();
53
+ $ requests [$ year ] = getGraphQLCurlHandle ($ query , $ tokens [$ year ]);
52
54
}
53
55
// build multi-curl handle
54
56
$ multi = curl_multi_init ();
@@ -67,19 +69,23 @@ function getContributionGraphs(string $user): array
67
69
$ decoded = is_string ($ contents ) ? json_decode ($ contents ) : null ;
68
70
// if response is empty or invalid, retry request one time
69
71
if (empty ($ decoded ) || empty ($ decoded ->data )) {
70
- // if rate limit is exceeded, don't retry
72
+ // if rate limit is exceeded, don't retry with same token
71
73
$ message = $ decoded ->errors [0 ]->message ?? ($ decoded ->message ?? "An API error occurred. " );
72
74
if (str_contains ($ message , "rate limit exceeded " )) {
73
- error_log ("Error: $ message " );
74
- continue ;
75
+ removeGitHubToken ($ tokens [$ year ]);
75
76
}
77
+ error_log ("First attempt to decode response for $ user's $ year contributions failed. $ message " );
76
78
$ query = buildContributionGraphQuery ($ user , $ year );
77
- $ request = getGraphQLCurlHandle ($ query );
79
+ $ token = getGitHubToken ();
80
+ $ request = getGraphQLCurlHandle ($ query , $ token );
78
81
$ contents = curl_exec ($ request );
79
82
$ decoded = is_string ($ contents ) ? json_decode ($ contents ) : null ;
80
83
// if the response is still empty or invalid, log an error and skip the year
81
84
if (empty ($ decoded ) || empty ($ decoded ->data )) {
82
85
$ message = $ decoded ->errors [0 ]->message ?? ($ decoded ->message ?? "An API error occurred. " );
86
+ if (str_contains ($ message , "rate limit exceeded " )) {
87
+ removeGitHubToken ($ token );
88
+ }
83
89
error_log ("Failed to decode response for $ user's $ year contributions after 2 attempts. $ message " );
84
90
continue ;
85
91
}
@@ -118,16 +124,46 @@ function getGitHubTokens()
118
124
return $ tokens ;
119
125
}
120
126
127
+ /**
128
+ * Get a token from the token pool
129
+ *
130
+ * @throws AssertionError if no tokens are available
131
+ */
132
+ function getGitHubToken ()
133
+ {
134
+ $ all_tokens = getGitHubTokens ();
135
+ return $ all_tokens [array_rand ($ all_tokens )];
136
+ }
137
+
138
+ /**
139
+ * Remove a token from the token pool
140
+ *
141
+ * @param string $token Token to remove
142
+ */
143
+ function removeGitHubToken (string $ token )
144
+ {
145
+ $ index = array_search ($ token , $ GLOBALS ["ALL_TOKENS " ]);
146
+ if ($ index !== false ) {
147
+ unset($ GLOBALS ["ALL_TOKENS " ][$ index ]);
148
+ }
149
+ // if there is no available token, throw an error
150
+ if (empty ($ GLOBALS ["ALL_TOKENS " ])) {
151
+ throw new AssertionError (
152
+ "We are being rate-limited! Check <a href='https://git.io/streak-ratelimit' font-weight='bold'>git.io/streak-ratelimit</a> for details. " ,
153
+ 429
154
+ );
155
+ }
156
+ }
157
+
121
158
/** Create a CurlHandle for a POST request to GitHub's GraphQL API
122
159
*
123
160
* @param string $query GraphQL query
161
+ * @param string $token GitHub token to use for the request
124
162
*
125
163
* @return CurlHandle The curl handle for the request
126
164
*/
127
- function getGraphQLCurlHandle (string $ query )
165
+ function getGraphQLCurlHandle (string $ query, string $ token )
128
166
{
129
- $ all_tokens = getGitHubTokens ();
130
- $ token = $ all_tokens [array_rand ($ all_tokens )];
131
167
$ headers = [
132
168
"Authorization: bearer $ token " ,
133
169
"Content-Type: application/json " ,
@@ -151,36 +187,41 @@ function getGraphQLCurlHandle(string $query)
151
187
* Create a POST request to GitHub's GraphQL API
152
188
*
153
189
* @param string $query GraphQL query
190
+ * @param string $token GitHub token to use for the request
154
191
*
155
192
* @return stdClass An object from the json response of the request
156
193
*
157
194
* @throws AssertionError If SSL verification fails
158
195
*/
159
- function fetchGraphQL (string $ query ): stdClass
196
+ function fetchGraphQL (string $ query, string $ token ): stdClass
160
197
{
161
- $ ch = getGraphQLCurlHandle ($ query );
198
+ $ ch = getGraphQLCurlHandle ($ query, $ token );
162
199
$ response = curl_exec ($ ch );
163
200
curl_close ($ ch );
164
- $ obj = is_string ($ response ) ? json_decode ($ response ) : null ;
201
+ $ decoded = is_string ($ response ) ? json_decode ($ response ) : null ;
165
202
// handle curl errors
166
- if ($ response === false || $ obj === null || curl_getinfo ($ ch , CURLINFO_HTTP_CODE ) >= 400 ) {
203
+ if ($ response === false || $ decoded === null || curl_getinfo ($ ch , CURLINFO_HTTP_CODE ) >= 400 ) {
204
+ $ message = $ decoded ->errors [0 ]->message ?? ($ decoded ->message ?? "" );
205
+ if (str_contains ($ message , "rate limit exceeded " )) {
206
+ removeGitHubToken ($ token );
207
+ }
167
208
// set response code to curl error code
168
209
http_response_code (curl_getinfo ($ ch , CURLINFO_HTTP_CODE ));
169
210
// Missing SSL certificate
170
211
if (str_contains (curl_error ($ ch ), "unable to get local issuer certificate " )) {
171
212
throw new AssertionError ("You don't have a valid SSL Certificate installed or XAMPP. " , 400 );
172
213
}
173
214
// Handle errors such as "Bad credentials"
174
- if ($ obj && $ obj -> message ) {
175
- throw new AssertionError ("Error: $ obj -> message \n<!-- $ response --> " , 401 );
215
+ if ($ message ) {
216
+ throw new AssertionError ("Error: $ message \n<!-- $ response --> " , 401 );
176
217
}
177
218
// Handle curl errors
178
219
if (curl_errno ($ ch )) {
179
220
throw new AssertionError ("cURL error: " . curl_error ($ ch ) . "\n<!-- $ response --> " , 500 );
180
221
}
181
222
throw new AssertionError ("An error occurred when getting a response from GitHub. \n<!-- $ response --> " , 502 );
182
223
}
183
- return $ obj ;
224
+ return $ decoded ;
184
225
}
185
226
186
227
/**
@@ -201,7 +242,13 @@ function getContributionYears(string $user): array
201
242
}
202
243
}
203
244
} " ;
204
- $ response = fetchGraphQL ($ query );
245
+ try {
246
+ $ response = fetchGraphQL ($ query , getGitHubToken ());
247
+ } catch (AssertionError $ e ) {
248
+ // retry once if an error occurred
249
+ error_log ("An error occurred getting contribution years for $ user: " . $ e ->getMessage ());
250
+ $ response = fetchGraphQL ($ query , getGitHubToken ());
251
+ }
205
252
// User not found
206
253
if (!empty ($ response ->errors )) {
207
254
$ type = $ response ->errors [0 ]->type ?? "" ;
0 commit comments