@@ -5,6 +5,7 @@ import dev.typetype.server.currentRequestId
55import kotlinx.coroutines.suspendCancellableCoroutine
66import okhttp3.Call
77import okhttp3.Callback
8+ import okhttp3.HttpUrl.Companion.toHttpUrl
89import okhttp3.OkHttpClient
910import okhttp3.Request
1011import okhttp3.Response
@@ -28,51 +29,69 @@ internal class OkHttpYouTubeSubtitleContentFetcher(
2829 .header(" User-Agent" , OkHttpProxyService .BROWSER_USER_AGENT )
2930 .apply { currentRequestId()?.let { header(REQUEST_ID_HEADER , it) } }
3031 .build()
31- return execute (request, format)
32+ return client.executeSubtitleRequest (request, format)
3233 }
34+ }
3335
34- private suspend fun execute (
35- request : Request ,
36- format : YouTubeSubtitleFormat ,
37- ): YouTubeSubtitleFetchResult = suspendCancellableCoroutine { continuation ->
38- val call = client.newCall(request)
39- continuation.invokeOnCancellation { call.cancel() }
40- call.enqueue(object : Callback {
41- override fun onFailure (call : Call , e : IOException ) {
42- if (continuation.isActive) continuation.resume(YouTubeSubtitleFetchResult .Unavailable )
43- }
36+ internal class TokenYouTubeSubtitleContentFetcher (
37+ private val client : OkHttpClient ,
38+ baseUrl : String ,
39+ private val directFetcher : YouTubeSubtitleContentFetcher ,
40+ ) : YouTubeSubtitleContentFetcher {
41+ private val endpoint = baseUrl.toHttpUrl().newBuilder()
42+ .addPathSegments(" subtitles/content" )
43+ .build()
4444
45- override fun onResponse (call : Call , response : Response ) {
46- val result = runCatching { response.use { readResponse(it, format) } }
47- .getOrDefault(YouTubeSubtitleFetchResult .Unavailable )
48- if (continuation.isActive) continuation.resume(result)
49- }
50- })
45+ override suspend fun fetch (url : String , format : YouTubeSubtitleFormat ): YouTubeSubtitleFetchResult {
46+ if (format == YouTubeSubtitleFormat .Ttml ) return directFetcher.fetch(url, format)
47+ if (! isYouTubeTimedTextUrl(url)) return YouTubeSubtitleFetchResult .Unavailable
48+ val request = Request .Builder ()
49+ .url(endpoint.newBuilder().addQueryParameter(" url" , url).build())
50+ .header(" Accept" , format.contentType)
51+ .apply { currentRequestId()?.let { header(REQUEST_ID_HEADER , it) } }
52+ .build()
53+ return client.executeSubtitleRequest(request, format)
5154 }
55+ }
5256
53- private fun readResponse (response : Response , format : YouTubeSubtitleFormat ): YouTubeSubtitleFetchResult {
54- if (response.code == 429 ) return YouTubeSubtitleFetchResult .Throttled
55- if (response.code == 403 || response.code == 404 || response.code == 410 ) {
56- return YouTubeSubtitleFetchResult .Expired
57+ private suspend fun OkHttpClient.executeSubtitleRequest (
58+ request : Request ,
59+ format : YouTubeSubtitleFormat ,
60+ ): YouTubeSubtitleFetchResult = suspendCancellableCoroutine { continuation ->
61+ val call = newCall(request)
62+ continuation.invokeOnCancellation { call.cancel() }
63+ call.enqueue(object : Callback {
64+ override fun onFailure (call : Call , e : IOException ) {
65+ if (continuation.isActive) continuation.resume(YouTubeSubtitleFetchResult .Unavailable )
5766 }
58- if (! response.isSuccessful) return YouTubeSubtitleFetchResult .Unavailable
59- val body = response.body
60- if (body.contentLength() > MAX_SUBTITLE_BYTES ) return YouTubeSubtitleFetchResult .InvalidPayload
61- return runCatching { body.byteStream().readNBytes(MAX_SUBTITLE_BYTES + 1 ) }
62- .fold(
63- onSuccess = { bytes ->
64- if (isValidSubtitlePayload(bytes, format)) YouTubeSubtitleFetchResult .Ready (bytes)
65- else YouTubeSubtitleFetchResult .InvalidPayload
66- },
67- onFailure = { YouTubeSubtitleFetchResult .Unavailable },
68- )
69- }
7067
71- private companion object {
72- const val YOUTUBE_ORIGIN = " https://m.youtube.com"
68+ override fun onResponse (call : Call , response : Response ) {
69+ val result = runCatching { response.use { readSubtitleResponse(it, format) } }
70+ .getOrDefault(YouTubeSubtitleFetchResult .Unavailable )
71+ if (continuation.isActive) continuation.resume(result)
72+ }
73+ })
74+ }
75+
76+ private fun readSubtitleResponse (response : Response , format : YouTubeSubtitleFormat ): YouTubeSubtitleFetchResult {
77+ if (response.code == 429 ) return YouTubeSubtitleFetchResult .Throttled
78+ if (response.code == 403 || response.code == 404 || response.code == 410 ) {
79+ return YouTubeSubtitleFetchResult .Expired
7380 }
81+ if (! response.isSuccessful) return YouTubeSubtitleFetchResult .Unavailable
82+ val body = response.body
83+ if (body.contentLength() > MAX_SUBTITLE_BYTES ) return YouTubeSubtitleFetchResult .InvalidPayload
84+ return runCatching { body.byteStream().readNBytes(MAX_SUBTITLE_BYTES + 1 ) }
85+ .fold(
86+ onSuccess = { bytes ->
87+ if (isValidSubtitlePayload(bytes, format)) YouTubeSubtitleFetchResult .Ready (bytes)
88+ else YouTubeSubtitleFetchResult .InvalidPayload
89+ },
90+ onFailure = { YouTubeSubtitleFetchResult .Unavailable },
91+ )
7492}
7593
94+ private const val YOUTUBE_ORIGIN = " https://m.youtube.com"
7695internal const val MAX_SUBTITLE_BYTES = 5 * 1024 * 1024
7796
7897internal fun isValidSubtitlePayload (content : ByteArray , format : YouTubeSubtitleFormat ): Boolean {
0 commit comments