@@ -107,6 +107,8 @@ export default {
107107 max-height: 90vh;
108108 overflow-y: auto;
109109 overflow-x: hidden;
110+ display: flex;
111+ flex-direction: column;
110112 }
111113 .ufs_popup table, .ufs_popup th, .ufs_popup td {
112114 border: 1px solid #aaa;
@@ -120,6 +122,19 @@ export default {
120122 top: -22px;
121123 background: #333;
122124 }
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+ }
123138 </style>
124139
125140 <div id="${ floatingBtnId } ">📥</div>
@@ -131,6 +146,17 @@ export default {
131146 const floatingBtn = ui . querySelector ( "#" + floatingBtnId ) ;
132147 const container = ui . querySelector ( "#" + containerId ) ;
133148
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+
134160 function toggle ( willShow ) {
135161 if ( ! ( typeof willShow === "boolean" ) ) {
136162 let isShowing = container . style . display == "flex" ;
@@ -146,15 +172,23 @@ export default {
146172 <h1 style="text-align:center">Tiktok - Useful Scripts</h1>
147173 <h2 style="text-align:center">Found ${ CACHED . videoById . size } videos</h2>
148174
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+
149183 <table>
150184 <thead>
151185 <tr>
152186 <th data-sort="index">#</th>
153- <th>Video</th>
187+ <th>🎬 Video</th>
154188 <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>
158192 <th>Download</th>
159193 </tr>
160194 </thead>
@@ -164,18 +198,153 @@ export default {
164198</div>` ;
165199
166200 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+ }
168340 }
169341
170- function renderTable (
171- tbody ,
172- data = Array . from ( CACHED . videoById . values ( ) )
173- ) {
342+ function renderTable ( tbody , data ) {
174343 tbody . innerHTML = data
175344 . map (
176345 ( v , i ) => /*html*/ `
177346<tr>
178- <td>${ i + 1 } </td>
347+ <td>${ v . index } </td>
179348<td>
180349 <a target="_blank" href="${ v . video . playAddr } ">
181350 <img src="${ v . video . originCover } " style="width:150px" />
@@ -194,12 +363,13 @@ export default {
194363<td>${ v . video . duration } s</td>
195364<td>
196365 <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/>
198368 <a href="${ v . author . avatarLarger } " download target="_blank">
199- Avatar
369+ 👤 Avatar
200370 </a><br/>
201371 <a href="${ v . music . playUrl } " download target="_blank">
202- Music: ${ v . music . title }
372+ 🎧 Music: ${ v . music . title }
203373 </a>
204374 </p>
205375</td>
@@ -208,16 +378,20 @@ export default {
208378 . join ( "" ) ;
209379 }
210380
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" ) ;
216383
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+ }
221395
222396 hookFetch ( {
223397 onAfter : async ( url , options , response ) => {
0 commit comments