1 | const url = require('url');
|
2 | const miniget = require('miniget');
|
3 | const querystring = require('querystring');
|
4 | const Cache = require('./cache');
|
5 |
|
6 |
|
7 |
|
8 | exports.cache = new Cache();
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | exports.getTokens = (html5playerfile, options) => exports.cache.getOrSet(html5playerfile, async() => {
|
19 | let body = await miniget(html5playerfile, options.requestOptions).text();
|
20 | const tokens = exports.extractActions(body);
|
21 | if (!tokens || !tokens.length) {
|
22 | throw Error('Could not extract signature deciphering actions');
|
23 | }
|
24 | exports.cache.set(html5playerfile, tokens);
|
25 | return tokens;
|
26 | });
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | exports.decipher = (tokens, sig) => {
|
37 | sig = sig.split('');
|
38 | for (let i = 0, len = tokens.length; i < len; i++) {
|
39 | let token = tokens[i], pos;
|
40 | switch (token[0]) {
|
41 | case 'r':
|
42 | sig = sig.reverse();
|
43 | break;
|
44 | case 'w':
|
45 | pos = ~~token.slice(1);
|
46 | sig = swapHeadAndPosition(sig, pos);
|
47 | break;
|
48 | case 's':
|
49 | pos = ~~token.slice(1);
|
50 | sig = sig.slice(pos);
|
51 | break;
|
52 | case 'p':
|
53 | pos = ~~token.slice(1);
|
54 | sig.splice(0, pos);
|
55 | break;
|
56 | }
|
57 | }
|
58 | return sig.join('');
|
59 | };
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 | const swapHeadAndPosition = (arr, position) => {
|
70 | const first = arr[0];
|
71 | arr[0] = arr[position % arr.length];
|
72 | arr[position] = first;
|
73 | return arr;
|
74 | };
|
75 |
|
76 |
|
77 | const jsVarStr = '[a-zA-Z_\\$][a-zA-Z_0-9]*';
|
78 | const jsSingleQuoteStr = `'[^'\\\\]*(:?\\\\[\\s\\S][^'\\\\]*)*'`;
|
79 | const jsDoubleQuoteStr = `"[^"\\\\]*(:?\\\\[\\s\\S][^"\\\\]*)*"`;
|
80 | const jsQuoteStr = `(?:${jsSingleQuoteStr}|${jsDoubleQuoteStr})`;
|
81 | const jsKeyStr = `(?:${jsVarStr}|${jsQuoteStr})`;
|
82 | const jsPropStr = `(?:\\.${jsVarStr}|\\[${jsQuoteStr}\\])`;
|
83 | const jsEmptyStr = `(?:''|"")`;
|
84 | const reverseStr = ':function\\(a\\)\\{' +
|
85 | '(?:return )?a\\.reverse\\(\\)' +
|
86 | '\\}';
|
87 | const sliceStr = ':function\\(a,b\\)\\{' +
|
88 | 'return a\\.slice\\(b\\)' +
|
89 | '\\}';
|
90 | const spliceStr = ':function\\(a,b\\)\\{' +
|
91 | 'a\\.splice\\(0,b\\)' +
|
92 | '\\}';
|
93 | const swapStr = ':function\\(a,b\\)\\{' +
|
94 | 'var c=a\\[0\\];a\\[0\\]=a\\[b(?:%a\\.length)?\\];a\\[b(?:%a\\.length)?\\]=c(?:;return a)?' +
|
95 | '\\}';
|
96 | const actionsObjRegexp = new RegExp(
|
97 | `var (${jsVarStr})=\\{((?:(?:${
|
98 | jsKeyStr}${reverseStr}|${
|
99 | jsKeyStr}${sliceStr}|${
|
100 | jsKeyStr}${spliceStr}|${
|
101 | jsKeyStr}${swapStr
|
102 | }),?\\r?\\n?)+)\\};`);
|
103 | const actionsFuncRegexp = new RegExp(`${`function(?: ${jsVarStr})?\\(a\\)\\{` +
|
104 | `a=a\\.split\\(${jsEmptyStr}\\);\\s*` +
|
105 | `((?:(?:a=)?${jsVarStr}`}${
|
106 | jsPropStr
|
107 | }\\(a,\\d+\\);)+)` +
|
108 | `return a\\.join\\(${jsEmptyStr}\\)` +
|
109 | `\\}`);
|
110 | const reverseRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${reverseStr}`, 'm');
|
111 | const sliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${sliceStr}`, 'm');
|
112 | const spliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${spliceStr}`, 'm');
|
113 | const swapRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${swapStr}`, 'm');
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 | exports.extractActions = body => {
|
137 | const objResult = actionsObjRegexp.exec(body);
|
138 | const funcResult = actionsFuncRegexp.exec(body);
|
139 | if (!objResult || !funcResult) { return null; }
|
140 |
|
141 | const obj = objResult[1].replace(/\$/g, '\\$');
|
142 | const objBody = objResult[2].replace(/\$/g, '\\$');
|
143 | const funcBody = funcResult[1].replace(/\$/g, '\\$');
|
144 |
|
145 | let result = reverseRegexp.exec(objBody);
|
146 | const reverseKey = result && result[1]
|
147 | .replace(/\$/g, '\\$')
|
148 | .replace(/\$|^'|^"|'$|"$/g, '');
|
149 | result = sliceRegexp.exec(objBody);
|
150 | const sliceKey = result && result[1]
|
151 | .replace(/\$/g, '\\$')
|
152 | .replace(/\$|^'|^"|'$|"$/g, '');
|
153 | result = spliceRegexp.exec(objBody);
|
154 | const spliceKey = result && result[1]
|
155 | .replace(/\$/g, '\\$')
|
156 | .replace(/\$|^'|^"|'$|"$/g, '');
|
157 | result = swapRegexp.exec(objBody);
|
158 | const swapKey = result && result[1]
|
159 | .replace(/\$/g, '\\$')
|
160 | .replace(/\$|^'|^"|'$|"$/g, '');
|
161 |
|
162 | const keys = `(${[reverseKey, sliceKey, spliceKey, swapKey].join('|')})`;
|
163 | const myreg = `(?:a=)?${obj
|
164 | }(?:\\.${keys}|\\['${keys}'\\]|\\["${keys}"\\])` +
|
165 | `\\(a,(\\d+)\\)`;
|
166 | const tokenizeRegexp = new RegExp(myreg, 'g');
|
167 | const tokens = [];
|
168 | while ((result = tokenizeRegexp.exec(funcBody)) !== null) {
|
169 | let key = result[1] || result[2] || result[3];
|
170 | switch (key) {
|
171 | case swapKey:
|
172 | tokens.push(`w${result[4]}`);
|
173 | break;
|
174 | case reverseKey:
|
175 | tokens.push('r');
|
176 | break;
|
177 | case sliceKey:
|
178 | tokens.push(`s${result[4]}`);
|
179 | break;
|
180 | case spliceKey:
|
181 | tokens.push(`p${result[4]}`);
|
182 | break;
|
183 | }
|
184 | }
|
185 | return tokens;
|
186 | };
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 | exports.setDownloadURL = (format, sig) => {
|
194 | let decodedUrl;
|
195 | if (format.url) {
|
196 | decodedUrl = format.url;
|
197 | } else {
|
198 | return;
|
199 | }
|
200 |
|
201 | try {
|
202 | decodedUrl = decodeURIComponent(decodedUrl);
|
203 | } catch (err) {
|
204 | return;
|
205 | }
|
206 |
|
207 |
|
208 | const parsedUrl = url.parse(decodedUrl, true);
|
209 |
|
210 |
|
211 |
|
212 | delete parsedUrl.search;
|
213 |
|
214 | let query = parsedUrl.query;
|
215 |
|
216 |
|
217 |
|
218 | query.ratebypass = 'yes';
|
219 | if (sig) {
|
220 |
|
221 |
|
222 |
|
223 | query[format.sp || 'signature'] = sig;
|
224 | }
|
225 |
|
226 | format.url = url.format(parsedUrl);
|
227 | };
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 |
|
237 | exports.decipherFormats = async(formats, html5player, options) => {
|
238 | let decipheredFormats = {};
|
239 | let tokens = await exports.getTokens(html5player, options);
|
240 | formats.forEach(format => {
|
241 | let cipher = format.signatureCipher || format.cipher;
|
242 | if (cipher) {
|
243 | Object.assign(format, querystring.parse(cipher));
|
244 | delete format.signatureCipher;
|
245 | delete format.cipher;
|
246 | }
|
247 | const sig = tokens && format.s ? exports.decipher(tokens, format.s) : null;
|
248 | exports.setDownloadURL(format, sig);
|
249 | decipheredFormats[format.url] = format;
|
250 | });
|
251 | return decipheredFormats;
|
252 | };
|