1
+ package com.github.libretube.api.poToken
2
+
3
+ import okio.ByteString.Companion.decodeBase64
4
+ import okio.ByteString.Companion.toByteString
5
+
6
+ import kotlinx.serialization.json.Json
7
+ import kotlinx.serialization.json.JsonNull
8
+ import kotlinx.serialization.json.JsonObject
9
+ import kotlinx.serialization.json.JsonPrimitive
10
+ import kotlinx.serialization.json.jsonArray
11
+ import kotlinx.serialization.json.jsonPrimitive
12
+ import kotlinx.serialization.json.long
13
+
14
+ /* *
15
+ * Parses the raw challenge data obtained from the Create endpoint and returns an object that can be
16
+ * embedded in a JavaScript snippet.
17
+ */
18
+ fun parseChallengeData (rawChallengeData : String ): String {
19
+ val scrambled = Json .parseToJsonElement(rawChallengeData).jsonArray
20
+
21
+ val challengeData = if (scrambled.size > 1 && scrambled[1 ].jsonPrimitive.isString) {
22
+ val descrambled = descramble(scrambled[1 ].jsonPrimitive.content)
23
+ Json .parseToJsonElement(descrambled).jsonArray
24
+ } else {
25
+ scrambled[1 ].jsonArray
26
+ }
27
+
28
+ val messageId = challengeData[0 ].jsonPrimitive.content
29
+ val interpreterHash = challengeData[3 ].jsonPrimitive.content
30
+ val program = challengeData[4 ].jsonPrimitive.content
31
+ val globalName = challengeData[5 ].jsonPrimitive.content
32
+ val clientExperimentsStateBlob = challengeData[7 ].jsonPrimitive.content
33
+
34
+
35
+ val privateDoNotAccessOrElseSafeScriptWrappedValue = challengeData[1 ]
36
+ .takeIf { it !is JsonNull }
37
+ ?.jsonArray
38
+ ?.find { it.jsonPrimitive.isString }
39
+
40
+ val privateDoNotAccessOrElseTrustedResourceUrlWrappedValue = challengeData[2 ]
41
+ .takeIf { it !is JsonNull }
42
+ ?.jsonArray
43
+ ?.find { it.jsonPrimitive.isString }
44
+
45
+
46
+ return Json .encodeToString(
47
+ JsonObject .serializer(), JsonObject (
48
+ mapOf (
49
+ " messageId" to JsonPrimitive (messageId),
50
+ " interpreterJavascript" to JsonObject (
51
+ mapOf (
52
+ " privateDoNotAccessOrElseSafeScriptWrappedValue" to (privateDoNotAccessOrElseSafeScriptWrappedValue
53
+ ? : JsonPrimitive (" " )),
54
+ " privateDoNotAccessOrElseTrustedResourceUrlWrappedValue" to (privateDoNotAccessOrElseTrustedResourceUrlWrappedValue
55
+ ? : JsonPrimitive (" " ))
56
+ )
57
+ ),
58
+ " interpreterHash" to JsonPrimitive (interpreterHash),
59
+ " program" to JsonPrimitive (program),
60
+ " globalName" to JsonPrimitive (globalName),
61
+ " clientExperimentsStateBlob" to JsonPrimitive (clientExperimentsStateBlob)
62
+ )
63
+ )
64
+ )
65
+ }
66
+
67
+ /* *
68
+ * Parses the raw integrity token data obtained from the GenerateIT endpoint to a JavaScript
69
+ * `Uint8Array` that can be embedded directly in JavaScript code, and an [Int] representing the
70
+ * duration of this token in seconds.
71
+ */
72
+ fun parseIntegrityTokenData (rawIntegrityTokenData : String ): Pair <String , Long > {
73
+ val integrityTokenData = Json .parseToJsonElement(rawIntegrityTokenData).jsonArray
74
+ return base64ToU8(integrityTokenData[0 ].jsonPrimitive.content) to integrityTokenData[1 ].jsonPrimitive.long
75
+ }
76
+
77
+ /* *
78
+ * Converts a string (usually the identifier used as input to `obtainPoToken`) to a JavaScript
79
+ * `Uint8Array` that can be embedded directly in JavaScript code.
80
+ */
81
+ fun stringToU8 (identifier : String ): String {
82
+ return newUint8Array(identifier.toByteArray())
83
+ }
84
+
85
+ /* *
86
+ * Takes a poToken encoded as a sequence of bytes represented as integers separated by commas
87
+ * (e.g. "97,98,99" would be "abc"), which is the output of `Uint8Array::toString()` in JavaScript,
88
+ * and converts it to the specific base64 representation for poTokens.
89
+ */
90
+ fun u8ToBase64 (poToken : String ): String {
91
+ return poToken.split(" ," )
92
+ .map { it.toUByte().toByte() }
93
+ .toByteArray()
94
+ .toByteString()
95
+ .base64()
96
+ .replace(" +" , " -" )
97
+ .replace(" /" , " _" )
98
+ }
99
+
100
+ /* *
101
+ * Takes the scrambled challenge, decodes it from base64, adds 97 to each byte.
102
+ */
103
+ private fun descramble (scrambledChallenge : String ): String {
104
+ return base64ToByteString(scrambledChallenge)
105
+ .map { (it + 97 ).toByte() }
106
+ .toByteArray()
107
+ .decodeToString()
108
+ }
109
+
110
+ /* *
111
+ * Decodes a base64 string encoded in the specific base64 representation used by YouTube, and
112
+ * returns a JavaScript `Uint8Array` that can be embedded directly in JavaScript code.
113
+ */
114
+ private fun base64ToU8 (base64 : String ): String {
115
+ return newUint8Array(base64ToByteString(base64))
116
+ }
117
+
118
+ private fun newUint8Array (contents : ByteArray ): String {
119
+ return " new Uint8Array([" + contents.joinToString(separator = " ," ) { it.toUByte().toString() } + " ])"
120
+ }
121
+
122
+ /* *
123
+ * Decodes a base64 string encoded in the specific base64 representation used by YouTube.
124
+ */
125
+ private fun base64ToByteString (base64 : String ): ByteArray {
126
+ val base64Mod = base64
127
+ .replace(' -' , ' +' )
128
+ .replace(' _' , ' /' )
129
+ .replace(' .' , ' =' )
130
+
131
+ return (base64Mod.decodeBase64() ? : throw PoTokenException (" Cannot base64 decode" ))
132
+ .toByteArray()
133
+ }
0 commit comments