Skip to content

Commit 4ddd8a3

Browse files
committed
Implement public-facing functions for initializing OAuth flow.
1 parent cc0853a commit 4ddd8a3

File tree

2 files changed

+74
-43
lines changed

2 files changed

+74
-43
lines changed

common/src/main/java/dev/lavalink/youtube/http/YoutubeHttpContextFilter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,11 @@ public void onRequest(HttpClientContext context,
105105
context.removeAttribute(ATTRIBUTE_USER_AGENT_SPECIFIED);
106106
}
107107

108-
boolean isRequestFromOauthedClient = context.getAttribute(Client.OAUTH_CLIENT_ATTRIBUTE) == Boolean.TRUE;
108+
boolean isRequestFromOauthedClient = context.removeAttribute(Client.OAUTH_CLIENT_ATTRIBUTE) == Boolean.TRUE;
109109

110110
if (isRequestFromOauthedClient && Client.PLAYER_URL.equals(request.getURI().toString())) {
111111
// Look at the userdata for any provided oauth-token
112-
String oauthToken = context.getAttribute(OAUTH_INJECT_CONTEXT_ATTRIBUTE, String.class);
112+
String oauthToken = (String) context.removeAttribute(OAUTH_INJECT_CONTEXT_ATTRIBUTE);
113113
// only apply the token to /player requests.
114114
if (oauthToken != null && !oauthToken.isEmpty()) {
115115
oauth2Handler.applyToken(request, oauthToken);

common/src/main/java/dev/lavalink/youtube/http/YoutubeOauth2Handler.java

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,17 @@ private void initializeAccessToken() {
108108
log.info("!!! DO NOT AUTHORISE WITH YOUR MAIN ACCOUNT, USE A BURNER !!!");
109109
log.info("==================================================");
110110

111-
// Should this be a daemon?
112-
new Thread(() -> pollForToken(deviceCode, interval == 0 ? 5000 : interval), "youtube-source-token-poller").start();
111+
Thread pollThread = new Thread(() -> pollForToken(deviceCode, interval == 0 ? 5000 : interval), "youtube-source-token-poller");
112+
pollThread.setDaemon(true);
113+
pollThread.start();
113114
}
114115

115-
private JsonObject fetchDeviceCode() {
116+
/**
117+
* Retrieves a device code for use with the OAuth flow.
118+
* The returned payload will contain a user code and a device code, as well as a recommended poll interval,
119+
* which must be used to complete the flow.
120+
*/
121+
public JsonObject fetchDeviceCode() {
116122
// @formatter:off
117123
String requestJson = JsonWriter.string()
118124
.object()
@@ -137,7 +143,18 @@ private JsonObject fetchDeviceCode() {
137143
}
138144
}
139145

140-
private void pollForToken(String deviceCode, long interval) {
146+
/**
147+
* Retrieve a refresh token from a given device code. This might not yield a successful response
148+
* if the OAuth flow for the given device code has not yet been completed, or the device code is invalid.
149+
* @param deviceCode The device code obtained from {@link #fetchDeviceCode()}
150+
*/
151+
public JsonObject fetchRefreshToken(String deviceCode) throws IOException {
152+
try (HttpInterface httpInterface = getHttpInterface()) {
153+
return fetchRefreshToken(httpInterface, deviceCode);
154+
}
155+
}
156+
157+
private JsonObject fetchRefreshToken(HttpInterface httpInterface, String deviceCode) throws IOException {
141158
// @formatter:off
142159
String requestJson = JsonWriter.string()
143160
.object()
@@ -153,45 +170,60 @@ private void pollForToken(String deviceCode, long interval) {
153170
StringEntity body = new StringEntity(requestJson, ContentType.APPLICATION_JSON);
154171
request.setEntity(body);
155172

156-
while (true) {
157-
try (HttpInterface httpInterface = getHttpInterface();
158-
CloseableHttpResponse response = httpInterface.execute(request)) {
159-
HttpClientTools.assertSuccessWithContent(response, "oauth2 token fetch");
160-
JsonObject parsed = JsonParser.object().from(response.getEntity().getContent());
161-
162-
log.debug("oauth2 token fetch response: {}", JsonWriter.string(parsed));
163-
164-
if (parsed.has("error") && !parsed.isNull("error")) {
165-
String error = parsed.getString("error");
166-
167-
switch (error) {
168-
case "authorization_pending":
169-
case "slow_down":
170-
Thread.sleep(interval);
171-
continue;
172-
case "expired_token":
173-
log.error("OAUTH INTEGRATION: The device token has expired. OAuth integration has been canceled.");
174-
case "access_denied":
175-
log.error("OAUTH INTEGRATION: Account linking was denied. OAuth integration has been canceled.");
176-
default:
177-
log.error("Unhandled OAuth2 error: {}", error);
173+
try (CloseableHttpResponse response = httpInterface.execute(request)) {
174+
HttpClientTools.assertSuccessWithContent(response, "oauth2 token fetch");
175+
JsonObject parsed = JsonParser.object().from(response.getEntity().getContent());
176+
log.debug("oauth2 token fetch response: {}", JsonWriter.string(parsed));
177+
return parsed;
178+
} catch (IOException | JsonParserException e) {
179+
throw ExceptionTools.toRuntimeException(e);
180+
}
181+
}
182+
183+
private void pollForToken(String deviceCode, long interval) {
184+
try (HttpInterface httpInterface = getHttpInterface()) {
185+
while (true) {
186+
try {
187+
JsonObject response = fetchRefreshToken(httpInterface, deviceCode);
188+
189+
if (response.has("error") && !response.isNull("error")) {
190+
String error = response.getString("error");
191+
192+
switch (error) {
193+
case "authorization_pending":
194+
case "slow_down":
195+
Thread.sleep(interval);
196+
continue;
197+
case "expired_token":
198+
log.error("OAUTH INTEGRATION: The device token has expired. OAuth integration has been canceled.");
199+
break;
200+
case "access_denied":
201+
log.error("OAUTH INTEGRATION: Account linking was denied. OAuth integration has been canceled.");
202+
break;
203+
default:
204+
log.error("Unhandled OAuth2 error: {}", error);
205+
break;
206+
}
207+
208+
return;
178209
}
179210

211+
updateTokens(response);
212+
log.info("OAUTH INTEGRATION: Token retrieved successfully. Store your refresh token as this can be reused. ({})", refreshToken);
213+
enabled = true;
180214
return;
215+
} catch (InterruptedException | RuntimeException e) {
216+
log.error("Failed to fetch OAuth2 token response", e);
181217
}
182-
183-
updateTokens(parsed);
184-
log.info("OAUTH INTEGRATION: Token retrieved successfully. Store your refresh token as this can be reused. ({})", refreshToken);
185-
enabled = true;
186-
return;
187-
} catch (IOException | JsonParserException | InterruptedException e) {
188-
log.error("Failed to fetch OAuth2 token response", e);
189218
}
219+
} catch (IOException e) {
220+
log.error("Failed to acquire HTTP interface for token polling", e);
190221
}
191222
}
192223

193224
/**
194225
* Refreshes an access token using a supplied refresh token.
226+
*
195227
* @param force Whether to forcefully renew the access token, even if it doesn't necessarily
196228
* need to be refreshed yet.
197229
*/
@@ -236,16 +268,15 @@ public void refreshAccessToken(boolean force) {
236268
* @return The JSON response as a JsonObject.
237269
*/
238270
public JsonObject createNewAccessToken(String refreshToken) {
239-
240271
// @formatter:off
241272
String requestJson = JsonWriter.string()
242-
.object()
243-
.value("client_id", CLIENT_ID)
244-
.value("client_secret", CLIENT_SECRET)
245-
.value("refresh_token", refreshToken)
246-
.value("grant_type", "refresh_token")
247-
.end()
248-
.done();
273+
.object()
274+
.value("client_id", CLIENT_ID)
275+
.value("client_secret", CLIENT_SECRET)
276+
.value("refresh_token", refreshToken)
277+
.value("grant_type", "refresh_token")
278+
.end()
279+
.done();
249280
// @formatter:on
250281

251282
HttpPost request = new HttpPost("https://www.youtube.com/o/oauth2/token");

0 commit comments

Comments
 (0)