@@ -107,6 +107,8 @@ export default {
107
107
max-height: 90vh;
108
108
overflow-y: auto;
109
109
overflow-x: hidden;
110
+ display: flex;
111
+ flex-direction: column;
110
112
}
111
113
.ufs_popup table, .ufs_popup th, .ufs_popup td {
112
114
border: 1px solid #aaa;
@@ -120,6 +122,19 @@ export default {
120
122
top: -22px;
121
123
background: #333;
122
124
}
125
+ .ufs_popup input {
126
+ padding: 5px;
127
+ }
128
+ .ufs_popup button {
129
+ padding: 5px;
130
+ background: #333;
131
+ color: #eee;
132
+ border: 1px solid #777;
133
+ cursor: pointer;
134
+ }
135
+ .ufs_popup button:hover {
136
+ background: #666;
137
+ }
123
138
</style>
124
139
125
140
<div id="${ floatingBtnId } ">📥</div>
@@ -131,6 +146,17 @@ export default {
131
146
const floatingBtn = ui . querySelector ( "#" + floatingBtnId ) ;
132
147
const container = ui . querySelector ( "#" + containerId ) ;
133
148
149
+ container . onclick = ( e ) => {
150
+ if ( e . target === container ) {
151
+ toggle ( false ) ;
152
+ }
153
+ } ;
154
+
155
+ floatingBtn . onclick = ( ) => {
156
+ let isShow = toggle ( ) ;
157
+ if ( isShow ) renderData ( ) ;
158
+ } ;
159
+
134
160
function toggle ( willShow ) {
135
161
if ( ! ( typeof willShow === "boolean" ) ) {
136
162
let isShowing = container . style . display == "flex" ;
@@ -146,15 +172,23 @@ export default {
146
172
<h1 style="text-align:center">Tiktok - Useful Scripts</h1>
147
173
<h2 style="text-align:center">Found ${ CACHED . videoById . size } videos</h2>
148
174
175
+ <div style="align-self: flex-end;">
176
+ <button id="video">🎬 Download video</button>
177
+ <button id="audio">🎧 Download audio</button>
178
+ <button id="json">📄 Download json</button>
179
+ <button id="clear">🗑️ Clear all</button>
180
+ <input type="text" id="search" placeholder="🔎 Search..." >
181
+ </div>
182
+
149
183
<table>
150
184
<thead>
151
185
<tr>
152
186
<th data-sort="index">#</th>
153
- <th>Video</th>
187
+ <th>🎬 Video</th>
154
188
<th data-sort="title">Title</th>
155
- <th data-sort="user">User</th>
156
- <th data-sort="view">View</th>
157
- <th data-sort="length">Length</th>
189
+ <th data-sort="user">👤 User</th>
190
+ <th data-sort="view">👀 View</th>
191
+ <th data-sort="length">🕒 Length</th>
158
192
<th>Download</th>
159
193
</tr>
160
194
</thead>
@@ -164,18 +198,153 @@ export default {
164
198
</div>` ;
165
199
166
200
const tbody = container . querySelector ( "tbody" ) ;
167
- renderTable ( tbody ) ;
201
+
202
+ // render initial data
203
+ let data = Array . from ( CACHED . videoById . values ( ) ) . map ( ( _ , index ) => ( {
204
+ ..._ ,
205
+ index,
206
+ } ) ) ;
207
+ renderTable ( tbody , data ) ;
208
+
209
+ // btn
210
+ const clearBtn = container . querySelector ( "button#clear" ) ;
211
+ clearBtn . addEventListener ( "click" , ( ) => {
212
+ if ( confirm ( "Are you sure want to clear all data?" ) ) {
213
+ CACHED . videoById . clear ( ) ;
214
+ renderData ( ) ;
215
+ floatingBtn . innerText = "📥" ;
216
+ }
217
+ } ) ;
218
+ const downVideoBtn = container . querySelector ( "button#video" ) ;
219
+ downVideoBtn . addEventListener ( "click" , ( ) => {
220
+ download (
221
+ data . map ( ( _ , i ) => {
222
+ const urlList =
223
+ _ . video . bitrateInfo . find ( ( b ) => b . Bitrate === _ . video . bitrate )
224
+ ?. PlayAddr ?. UrlList || [ ] ;
225
+
226
+ const bestUrl = urlList [ urlList . length - 1 ] ;
227
+
228
+ return {
229
+ url : bestUrl || _ . video . playAddr ,
230
+ filename :
231
+ i +
232
+ "_" +
233
+ UfsGlobal . Utils . sanitizeName ( _ . desc . substr ( 0 , 50 ) ) +
234
+ ".mp4" ,
235
+ } ;
236
+ } ) ,
237
+ ( i , total ) => {
238
+ downVideoBtn . textContent = `🎬 Download video (${ i } /${ total } )` ;
239
+ }
240
+ ) ;
241
+ } ) ;
242
+ const downAudioBtn = container . querySelector ( "button#audio" ) ;
243
+ downAudioBtn . addEventListener ( "click" , ( ) => {
244
+ const uniqueMusic = new Map ( ) ;
245
+ for ( const item of data ) {
246
+ if ( ! uniqueMusic . has ( item . music . id ) )
247
+ uniqueMusic . set ( item . music . id , item ) ;
248
+ }
249
+ download (
250
+ Array . from ( uniqueMusic . values ( ) ) . map ( ( _ , i ) => ( {
251
+ url : _ . music . playUrl ,
252
+ filename :
253
+ i +
254
+ "_" +
255
+ UfsGlobal . Utils . sanitizeName (
256
+ _ . music . title . substr ( 0 , 50 ) || "audio"
257
+ ) +
258
+ ".mp3" ,
259
+ } ) ) ,
260
+ ( i , total ) => {
261
+ downAudioBtn . textContent = `🎧 Download audio (${ i } /${ total } )` ;
262
+ }
263
+ ) ;
264
+ } ) ;
265
+ const downJsonBtn = container . querySelector ( "button#json" ) ;
266
+ downJsonBtn . addEventListener ( "click" , ( ) => {
267
+ UfsGlobal . Utils . downloadData (
268
+ JSON . stringify ( data , null , 4 ) ,
269
+ "tiktok.json"
270
+ ) ;
271
+ } ) ;
272
+
273
+ // search
274
+ const searchInp = container . querySelector ( "#search" ) ;
275
+ searchInp . addEventListener ( "input" , ( event ) => {
276
+ const value = event . target . value ;
277
+ let trs = tbody . querySelectorAll ( "tr" ) ;
278
+ for ( const tr of trs ) {
279
+ const tds = tr . querySelectorAll ( "td" ) ;
280
+ let found = false ;
281
+ for ( const td of tds ) {
282
+ if ( td . textContent . toLowerCase ( ) . includes ( value ) ) {
283
+ found = true ;
284
+ break ;
285
+ }
286
+ }
287
+ tr . style . display = found ? "" : "none" ;
288
+ }
289
+ } ) ;
290
+
291
+ // sorting
292
+ const sorting = { } ;
293
+ const ths = container . querySelectorAll ( "th" ) ;
294
+ for ( const th of ths ) {
295
+ const sort = th . getAttribute ( "data-sort" ) ;
296
+ // if (!sort) return;
297
+ th . style . cursor = "pointer" ;
298
+ th . title = "Sort" ;
299
+
300
+ th . addEventListener ( "click" , ( ) => {
301
+ sorting [ sort ] = sorting [ sort ] == 1 ? - 1 : 1 ;
302
+ switch ( sort ) {
303
+ case "index" :
304
+ data = data . sort ( ( a , b ) =>
305
+ sorting [ sort ] == - 1 ? a . index - b . index : b . index - a . index
306
+ ) ;
307
+ break ;
308
+ case "title" :
309
+ data = data . sort ( ( a , b ) =>
310
+ sorting [ sort ] == - 1
311
+ ? a . desc . localeCompare ( b . desc )
312
+ : b . desc . localeCompare ( a . desc )
313
+ ) ;
314
+ break ;
315
+ case "user" :
316
+ data = data . sort ( ( a , b ) =>
317
+ sorting [ sort ] == - 1
318
+ ? a . author . uniqueId . localeCompare ( b . author . uniqueId )
319
+ : b . author . uniqueId . localeCompare ( a . author . uniqueId )
320
+ ) ;
321
+ break ;
322
+ case "view" :
323
+ data = data . sort ( ( a , b ) =>
324
+ sorting [ sort ] == - 1
325
+ ? a . stats . playCount - b . stats . playCount
326
+ : b . stats . playCount - a . stats . playCount
327
+ ) ;
328
+ break ;
329
+ case "length" :
330
+ data = data . sort ( ( a , b ) =>
331
+ sorting [ sort ] == - 1
332
+ ? a . video . duration - b . video . duration
333
+ : b . video . duration - a . video . duration
334
+ ) ;
335
+ break ;
336
+ }
337
+ renderTable ( tbody , data ) ;
338
+ } ) ;
339
+ }
168
340
}
169
341
170
- function renderTable (
171
- tbody ,
172
- data = Array . from ( CACHED . videoById . values ( ) )
173
- ) {
342
+ function renderTable ( tbody , data ) {
174
343
tbody . innerHTML = data
175
344
. map (
176
345
( v , i ) => /*html*/ `
177
346
<tr>
178
- <td>${ i + 1 } </td>
347
+ <td>${ v . index } </td>
179
348
<td>
180
349
<a target="_blank" href="${ v . video . playAddr } ">
181
350
<img src="${ v . video . originCover } " style="width:150px" />
@@ -194,12 +363,13 @@ export default {
194
363
<td>${ v . video . duration } s</td>
195
364
<td>
196
365
<p style="max-width:200px">
197
- <a href="${ v . video . playAddr } " download target="_blank">Video</a><br/>
366
+ <a href="${ v . video . playAddr } " download target="_blank">🎬 Video</a><br/>
367
+ <a href="${ v . video . cover } " download target="_blank">🖼️ Cover</a><br/>
198
368
<a href="${ v . author . avatarLarger } " download target="_blank">
199
- Avatar
369
+ 👤 Avatar
200
370
</a><br/>
201
371
<a href="${ v . music . playUrl } " download target="_blank">
202
- Music: ${ v . music . title }
372
+ 🎧 Music: ${ v . music . title }
203
373
</a>
204
374
</p>
205
375
</td>
@@ -208,16 +378,20 @@ export default {
208
378
. join ( "" ) ;
209
379
}
210
380
211
- container . onclick = ( e ) => {
212
- if ( e . target === container ) {
213
- toggle ( false ) ;
214
- }
215
- } ;
381
+ async function download ( data , onProgress ) {
382
+ const dir = await UfsGlobal . Utils . chooseFolderToDownload ( "tiktok" ) ;
216
383
217
- floatingBtn . onclick = ( ) => {
218
- let isShow = toggle ( ) ;
219
- if ( isShow ) renderData ( ) ;
220
- } ;
384
+ for ( let i = 0 ; i < data . length ; ++ i ) {
385
+ try {
386
+ const { url, filename } = data [ i ] ;
387
+ const realUrl = await UfsGlobal . Utils . getRedirectedUrl ( url ) ;
388
+ await UfsGlobal . Utils . downloadToFolder ( realUrl , filename , dir ) ;
389
+ onProgress ?. ( i + 1 , data . length ) ;
390
+ } catch ( e ) {
391
+ console . error ( e ) ;
392
+ }
393
+ }
394
+ }
221
395
222
396
hookFetch ( {
223
397
onAfter : async ( url , options , response ) => {
0 commit comments