@@ -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