// ==UserScript== // @name PikPak 增强大师 // @name:zh-CN PikPak 增强大师 // @name:zh-TW PikPak 增強大師 // @name:en PikPak Enhancement Master // @name:ko PikPak Enhancement Master // @name:ja PikPak Enhancement Master // @name:id PikPak Enhancement Master // @name:ms PikPak Enhancement Master // @namespace https://github.com/digbug82/ // @version 3.0.2 // @author digbug82 // @license AGPL-3.0-or-later // @description PikPak 网盘增强:集成 Aria2 下载、下载直链加速、下载过滤、分享链接解析增强、文件/文件夹查重、批量重命名、资源清理、批量解压、M3U 导出、PotPlayer 直达、污染磁链识别、TXT 磁链提取、排序与搜索增强、数据迁移、目录树导出、以图搜图、视音频播放增强等。 // @description:zh-CN PikPak 网盘增强:集成 Aria2 下载、下载直链加速、下载过滤、分享链接解析增强、文件/文件夹查重、批量重命名、资源清理、批量解压、M3U 导出、PotPlayer 直达、污染磁链识别、TXT 磁链提取、排序与搜索增强、数据迁移、目录树导出、以图搜图、视音频播放增强等。 // @description:zh-TW PikPak 網盤增強:整合 Aria2 下載、下載直鏈加速、下載過濾、分享連結解析增強、檔案/資料夾查重、批次重新命名、資源清理、批次解壓縮、M3U 匯出、PotPlayer 直達、污染磁鏈識別、TXT 磁鏈提取、排序與搜尋增強、資料遷移、目錄樹匯出、以圖搜圖、視音訊播放增強等。 // @description:en PikPak cloud drive enhancement: integrates Aria2 downloads, direct-link acceleration, download filtering, enhanced share-link parsing, file/folder deduplication, batch rename, resource cleanup, batch extraction, M3U export, PotPlayer direct open, polluted magnet detection, TXT magnet extraction, sorting and search enhancements, data migration, directory tree export, reverse image search, and enhanced audio/video playback. // @description:ko PikPak 클라우드 드라이브 강화: Aria2 다운로드, 다운로드 직접 링크 가속, 다운로드 필터링, 공유 링크解析 강화, 파일/폴더 중복 검사, 일괄 이름 변경, 리소스 정리, 일괄 압축 해제, M3U 내보내기, PotPlayer 바로 열기, 오염된 마그넷 감지, TXT 마그넷 추출, 정렬 및 검색 강화, 데이터 마이그레이션, 디렉터리 트리 내보내기, 이미지 검색, 오디오/비디오 재생 강화를 통합합니다. // @description:ja PikPak クラウドドライブ強化:Aria2 ダウンロード、ダウンロード直リンク高速化、ダウンロードフィルター、共有リンク解析強化、ファイル/フォルダ重複チェック、一括リネーム、リソース整理、一括解凍、M3U エクスポート、PotPlayer 直接起動、汚染マグネット検出、TXT マグネット抽出、並び替えと検索の強化、データ移行、ディレクトリツリー出力、画像検索、音声/動画再生強化などを統合します。 // @description:id Peningkatan cloud drive PikPak: mengintegrasikan unduhan Aria2, akselerasi tautan langsung unduhan, filter unduhan, parsing tautan berbagi yang ditingkatkan, deduplikasi file/folder, ganti nama massal, pembersihan sumber daya, ekstraksi massal, ekspor M3U, buka langsung dengan PotPlayer, deteksi magnet tercemar, ekstraksi magnet dari TXT, peningkatan penyortiran dan pencarian, migrasi data, ekspor pohon direktori, pencarian gambar, serta peningkatan pemutaran audio/video. // @description:ms Penambahbaikan pemacu awan PikPak: mengintegrasikan muat turun Aria2, pecutan pautan terus muat turun, penapisan muat turun, penghuraian pautan perkongsian dipertingkat, deduplikasi fail/folder, penamaan semula pukal, pembersihan sumber, nyahmampat pukal, eksport M3U, buka terus dengan PotPlayer, pengesanan magnet tercemar, pengekstrakan magnet TXT, peningkatan susunan dan carian, migrasi data, eksport pepohon direktori, carian imej serta peningkatan main balik audio/video. // @match https://mypikpak.com/drive/* // @match https://app.mypikpak.com/* // @match https://drive.mypikpak.com/* // @icon https://raw.githubusercontent.com/digbug82/PikPak_Enhancement_Master/main/img/logo.svg // @homepage https://github.com/digbug82/PikPak_Enhancement_Master // @supportURL https://github.com/digbug82/PikPak_Enhancement_Master/issues // @compatible chrome // @compatible edge // @grant GM_setClipboard // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @grant GM_info // @grant unsafeWindow // @grant GM_xmlhttpRequest // @connect catbox.moe // @connect litterbox.catbox.moe // @connect uguu.se // @connect mypikpak.com // @connect upload.pikpak.site // @connect *.upload.pikpak.site // @connect web-vod-xdrive.mypikpak.com // @connect whatslink.info // @connect localhost // @run-at document-start // @require https://cdn.jsdelivr.net/npm/hls.js@1.5.8/dist/hls.min.js // @require https://cdn.jsdelivr.net/npm/localforage@1.10.0/dist/localforage.min.js // ==/UserScript== /* * ============================================================================ * COPYRIGHT & LICENSE NOTICE * ============================================================================ * PikPak Enhancement Master * * Copyright (C) 2025-2026 digbug82. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License * for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * Source code: https://github.com/digbug82/PikPak_Enhancement_Master * * Acknowledgements: * This project is inspired in part by the UI design language, file-management * structure, and some web-side API interaction ideas of PikPak File Manager * v1.2.0 by 브랜뉴: * https://github.com/poihoii/PikPak_FileManager * * PikPak File Manager is licensed under the MIT License. Its original * copyright and license notice are preserved in THIRD_PARTY_NOTICES.md: * https://github.com/digbug82/PikPak_Enhancement_Master/blob/main/THIRD_PARTY_NOTICES.md * * Special thanks and respect to the original project and its author. * ============================================================================ */ (() => { "use strict"; if (window.self !== window.top) return; const parseCloudLinks = (rawString, isSmartFix) => { const formattedVal = String(rawString || '').replace(/(https?:\/\/|ftp:\/\/|sftp:\/\/|magnet:\?|ed2k:\/\/|thunder:\/\/)/gi, '\n$1'); const lines = formattedVal.split('\n').map(l => l.trim()).filter(l => l); const uniqueTasks = new Map(); const linkRegex = /^(https?:\/\/|ftp:\/\/|sftp:\/\/|magnet:\?|ed2k:\/\/|thunder:\/\/)/i; const base32ToHex = (b32) => { const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; let bits = ""; const input = String(b32 || '').toUpperCase(); for (let i = 0; i < input.length; i++) { const val = alphabet.indexOf(input[i]); if (val === -1) return null; bits += val.toString(2).padStart(5, '0'); } let hex = ""; for (let i = 0; i + 4 <= bits.length; i += 4) { const chunk = bits.substring(i, i + 4); const num = parseInt(chunk, 2); if (!isNaN(num)) hex += num.toString(16); } return hex.toUpperCase(); }; const extractHexHash = (url) => { const match = String(url || '').match(/urn:btih:([^&]+)/i); if (!match) return null; const hash = match[1].toUpperCase(); if (hash.length === 40) return hash; if (hash.length === 32) return base32ToHex(hash); return null; }; const addResult = (url) => { const hash = extractHexHash(url); if (hash) { if (!uniqueTasks.has(hash)) uniqueTasks.set(hash, url); } else { uniqueTasks.set(url, url); } }; for (let i = 0; i < lines.length; i++) { let line = lines[i]; if (linkRegex.test(line)) { addResult(line); continue; } if (isSmartFix) { let cleanedStr = line.replace(/[^a-zA-Z0-9]/g, ''); const hexMatches = cleanedStr.matchAll(/([a-fA-F0-9]{40})/g); for (const match of hexMatches) { addResult(`magnet:?xt=urn:btih:${match[1].toUpperCase()}`); cleanedStr = cleanedStr.replace(match[1], ' '.repeat(40)); } const b32Matches = cleanedStr.matchAll(/([a-zA-Z2-7]{32})/g); for (const match of b32Matches) { const hex = base32ToHex(match[1].toUpperCase()); if (hex) addResult(`magnet:?xt=urn:btih:${hex}`); } } } return Array.from(uniqueTasks.values()); }; (() => { const inject = () => { const s = document.createElement('script'); s.textContent = `(function(){ const _f = window.fetch; window.fetch = async function(...args) { const raw = args[0]; const url = raw && raw.url ? raw.url : (raw ? raw.toString() : ''); if (url.includes('area_accessible')) { return new Promise((_, reject) => { reject(new Error("Bypassed area_accessible")); }); } const isTurbo = localStorage.getItem('pk_turbo_mode') === 'true'; if (isTurbo) { if (location.href.includes('/login') || location.pathname.includes('login')) { return _f.apply(this, args); } if (url.includes(':incremental_sync') || url.includes(':sync')) { return new Response(JSON.stringify({ error_code: 0, data: [], files: [], tasks: [], next_page_token: '' }), {status: 200, headers: {'Content-Type': 'application/json'}}); } try { const opts = args[1] || {}; let cap = null; let auth = null; if (opts.headers) { if (opts.headers instanceof Headers) { cap = opts.headers.get('x-captcha-token') || opts.headers.get('X-Captcha-Token'); auth = opts.headers.get('authorization') || opts.headers.get('Authorization'); } else if (typeof opts.headers === 'object') { const capKey = Object.keys(opts.headers).find(k => k.toLowerCase() === 'x-captcha-token'); if (capKey) cap = opts.headers[capKey]; const authKey = Object.keys(opts.headers).find(k => k.toLowerCase() === 'authorization'); if (authKey) auth = opts.headers[authKey]; } } if (cap && cap.length > 20) localStorage.setItem('pk_captured_captcha', cap); if (auth && auth.length > 20) document.dispatchEvent(new CustomEvent('pk-token-captured', { detail: auth })); } catch (e) {} } const res = await _f.apply(this, args); try { const u = url.split('?')[0]; const authReadyUrl = u.includes('/drive/v1/about') || u.includes('/v1/user/me'); const authErrorUrl = u.includes('/drive/v1/about') || u.includes('/drive/v1/files') || u.includes('/v1/user/me') || u.includes('/v1/auth/token'); if (res && res.ok && authReadyUrl) { document.dispatchEvent(new CustomEvent('pk-official-auth-ready', { detail: { url: u, status: res.status } })); } else if (res && authErrorUrl && (res.status === 401 || res.status === 403 || res.status === 400)) { document.dispatchEvent(new CustomEvent('pk-official-auth-error', { detail: { url: u, status: res.status } })); } } catch (e) {} return res; }; const _W = window.Worker; window.Worker = function(url, opts) { const isTurbo = localStorage.getItem('pk_turbo_mode') === 'true'; if (!isTurbo) return new _W(url, opts); if (location.href.includes('/login') || location.pathname.includes('login')) { return new _W(url, opts); } const u = url ? url.toString() : ''; const blockList = ['query_db', 'sync', 'database', 'index', 'calc_sha1', 'query_docs']; if (blockList.some(k => u.includes(k))) { return new _W('data:application/javascript,self.onmessage=()=>{}'); } return new _W(url, opts); }; })()`; (document.head || document.documentElement).appendChild(s).remove(); }; inject(); })(); window.addEventListener('beforeunload', (e) => { const activeStatus = ['UPLOADING', 'HASHING', 'WAITING', 'RUNNING']; const tasks = pkState?.uploadTasks || []; if (tasks.some(t => activeStatus.includes(t.status))) { e.preventDefault(); return e.returnValue; } }); window.pkAddGhostFile = function(id) { try { const ghosts = JSON.parse(localStorage.getItem('pk_ghost_files') || '[]'); if (!ghosts.includes(id)) { ghosts.push(id); localStorage.setItem('pk_ghost_files', JSON.stringify(ghosts)); } } catch(e){} }; window.pkRemoveGhostFile = function(id) { try { const ghosts = JSON.parse(localStorage.getItem('pk_ghost_files') || '[]'); const updated = ghosts.filter(x => x !== id); localStorage.setItem('pk_ghost_files', JSON.stringify(updated)); } catch(e){} }; window.pkCleanupGhostFiles = function() { try { const ghosts = JSON.parse(localStorage.getItem('pk_ghost_files') || '[]'); if (ghosts.length > 0) { const BATCH_SIZE = 100; for (let i = 0; i < ghosts.length; i += BATCH_SIZE) { const chunk = ghosts.slice(i, i + BATCH_SIZE); fetch('https://api-drive.mypikpak.com/drive/v1/files:batchDelete', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: chunk }) }).catch(()=>{}); } localStorage.setItem('pk_ghost_files', '[]'); } } catch(e){} try { const phases = "PHASE_TYPE_UNKNOW,PHASE_TYPE_PENDING,PHASE_TYPE_RUNNING,PHASE_TYPE_PAUSED,PHASE_TYPE_ERROR"; const filters = encodeURIComponent(JSON.stringify({ "phase": { "in": phases } })); const url = `https://api-drive.mypikpak.com/drive/v1/tasks?type=upload&limit=100&filters=${filters}&_t=${Date.now()}`; fetch(url, { headers: getHeaders() }) .then(res => res.json()) .then(cloudData => { const cloudTasks = cloudData.tasks || []; const ghostIds = []; cloudTasks.forEach(ct => { if (ct.file_id) { const ref = ct.reference_resource || {}; const taskName = ref.name || ct.name || ct.file_name || 'Ghost Task'; if (taskName === 'upload' || taskName === 'Ghost Task') { ghostIds.push(ct.file_id); } } }); if (ghostIds.length > 0) { fetch('https://api-drive.mypikpak.com/drive/v1/files:batchDelete', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: ghostIds }) }).catch(()=>{}); } }).catch(()=>{}); } catch (e) {} }; window.addEventListener('beforeunload', () => { const tasks = pkState?.uploadTasks || []; const filesToDelete = tasks.filter(t => t.status !== 'DONE' && t.file_id && !t._deleted).map(t => t.file_id); if (filesToDelete.length > 0) { const BATCH_SIZE = 100; for (let i = 0; i < filesToDelete.length; i += BATCH_SIZE) { const chunk = filesToDelete.slice(i, i + BATCH_SIZE); try { fetch('https://api-drive.mypikpak.com/drive/v1/files:batchDelete', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: chunk }), keepalive: true }).catch(()=>{}); } catch(e) {} } if (typeof window.pkRemoveGhostFile === 'function') { filesToDelete.forEach(id => window.pkRemoveGhostFile(id)); } } }); ; const CONF = { rowHeight: 40, buffer: 20, dupGridHeaderHeight: { normal: 40, max: 60 }, dupGridSectionGap: { normal: 14, max: 18 }, dupGridBodyGapY: { normal: 16, max: 20 }, SYSTEM_FOLDER_NAME: 'My Pack', browserDownloadConfirmFileCount: 10, browserDownloadConfirmTotalBytes: 10 * 1024 * 1024 * 1024, downloadAccelEnable: false, downloadAccelDomain: '', downloadAccelMode: 'prefix', downloadAccelQueryParam: 'url', aria2KeepFolderStructure: true, uploadPartSizeOfficial: 1 * 1024 * 1024, uploadPartMaxCount: 10000, uploadPartConcurrencyOfficial: 5, mouseSideNavHistoryMax: 50, shareParseInsightConcurrency: 128, clipboardMagnetPaste: true, clipboardMagnetFocusCooldown: 2500, clipboardMagnetDenyCooldown: 60000, clipboardMagnetPromptGap: 10 * 60 * 1000, clipboardMagnetIgnoreTTL: 10 * 60 * 1000, clipboardMagnetMaxChars: 200000, magnetPreviewApi: 'https://whatslink.info/api/v1/link', magnetPreviewTimeout: 9000, magnetPreviewCacheTTL: 60 * 60 * 1000, magnetPreviewErrorCacheTTL: 10 * 60 * 1000, magnetPreviewCircuitTTL: 5 * 60 * 1000, magnetPreviewMaxShots: 5, textPreviewMaxBytes: 30 * 1024 * 1024, txtPreviewHotkeys: { parse: 'Ctrl+Enter', fullscreen: 'F', close: 'Esc' }, audioHotkeys: { mini: 'P' }, playerVolumeStep: 0.05, audioCoverRangeBytes: 192 * 1024, audioCoverCacheMax: 80, audioMiniPosKey: 'pk_audio_mini_pos', potplayerLaunchStateKey: 'pk_potplayer_launch_state', potplayerLaunchStateSchemaVersion: 1, potplayerProtocolStateKey: 'pk_potplayer_protocol_state', potplayerProtocolStateSchemaVersion: 1, potplayerProtocolRepairVersion: '20260501', potplayerRegDeleteFileName: 'pikpak-potplayer-delete.reg', potplayerRegCustomInstallFileName: 'pikpak-potplayer-install-custom.reg', potplayerBrowserPolicyRegFileName: 'pikpak-potplayer-browser-policy.reg', potplayerAutoRepairFailThreshold: 2, potplayerPromptCooldown: 10 * 60 * 1000, potplayerLikelyOpenTrustTTL: 24 * 60 * 60 * 1000, potplayerSuppressTodayTTL: 24 * 60 * 60 * 1000, potplayerPostRepairConfirmDelay: 6000, scriptUpdateManifestUrl: 'https://raw.githubusercontent.com/digbug82/PikPak_Enhancement_Master/main/version.json', scriptUpdateCheckTTL: 24 * 60 * 60 * 1000, scriptUpdateCacheKey: 'pk_script_update_cache', scriptUpdateDismissPrefix: 'pk_script_update_dismiss_', scriptUpdateProjectUrl: 'https://github.com/digbug82/PikPak_Enhancement_Master', logoSVG: ``, emptySVG: ``, dupHashSVG: ``, dupSimSVG: ``, dupNameSVG: ``, dupContainSVG: ``, crumbIcons: { right: ``, down: ``, sortAZ: ``, sortZA: ``, sortNew: ``, sortOld: ``, sortProgressAsc:``, sortProgressDesc:``, sortLarge: ``, sortSmall: ``, sortStarNew: ``, sortStarOld: ``, sortTypeDurAsc: ``, sortTypeDurDesc: ``, sortPathAsc: ``, sortPathDesc: `` }, icons: { offline: ``, navShare: ``, shareParse: ``, unshare: ``, refresh: ``, retry: ``, settings: ``, home: ``, recent: ``, history: ``, trash: ``, emptyTrash: ``, restore: ``, delForever: ``, newfolder: ``, del: ``, deselect: ``, copy: ``, cut: ``, paste: ``, rename: ``, bulkrename: ``, unzip: ``, migrate: ``, exportM3U: ``, prune: ``, blacklist: ``, invert: ``, folderFirst: ``, viewList: ``, viewGrid: ``, analyze: ``, scanDup: ``, export: ``, stop: ``, ext: ``, download: ``, aria2:``, moon: ``, sun: ``, cloudDownload: ``, share: ``, maximize: ``, minimize: ``, close: ``, help: ``, warning: ``, eyeOff: ``, eye: ``, lock: ``, uploadBtn: ``, upFile: ``, upFolder: ``, navUpload: ``, taskStart: ``, taskPause: ``, cleanAll: ``, logout: ``, blMarker: ``, vault: `` }, audioIcons: {play:``,pause:``,prev:``,next:``,vol:``,mute:``,modeOrder:``,modeList:``,modeSingle:``,modeShuffle:``}, typeIcons: { folder: ``, systemFolder: ``, video: ``, image: ``, audio: ``, archive: ``, archiveUnzipped: ``, text: ``, pdf: ``, subtitle: ``, executable: ``, web: ``, apk: ``, file: `` } }; ; CONF.audioIcons.playlist = ``; CONF.audioIcons.miniFold = ``; CONF.audioIcons.mini = ``; CONF.audioIcons.restore = ``; CONF.audioIcons.close = CONF.icons.close; CONF.icons.txtPreviewFullscreenSVG = CONF.icons.maximize; CONF.icons.txtPreviewExitFullscreenSVG = CONF.icons.minimize; CONF.icons.txtPreviewParseLinksSVG = CONF.icons.cloudDownload; const CSS = ` :root { --pk-bg: #ffffff; --pk-bg-rgb: 255, 255, 255; --pk-fg: #1a1a1a; --pk-bd: #e5e5e5; --pk-hl: #f0f0f0; --pk-sel-bg: #e6f3ff; --pk-sel-bd: #cce8ff; --pk-pri: #0067c0; --pk-btn-hov: #e0e0e0; --pk-gh: #f5f5f5; --pk-gh-fg: #333; --pk-sb-bg: transparent; --pk-sb-th: #ccc; --pk-sb-hov: #aaa; --pk-icon-c: #888; --pk-tip-bg: rgba(255, 255, 255, 0.95); --pk-tip-fg: #1a1a1a; --pk-tip-bd: rgba(0, 0, 0, 0.06); --pk-tip-sd: rgba(0, 0, 0, 0.12); --pk-toast-bg: rgba(255, 255, 255, 0.95); --pk-toast-fg: #1a1a1a; --pk-toast-bd: rgba(0, 0, 0, 0.08); --pk-match-bg: #fff2cc; --pk-match-fg: #d93025; --pk-v-line: #d1d1d1; } .pk-no-transition, .pk-no-transition * { transition: none !important; } .pk-magnet-preview-wrap { width:420px; max-width:86vw; display:flex; flex-direction:column; color:var(--pk-fg); overflow:hidden; } .pk-magnet-hero { width:100%; height:210px; background:var(--pk-hl); display:flex; align-items:center; justify-content:center; overflow:hidden; border-radius:14px 14px 0 0; } .pk-magnet-hero img { width:100%; height:100%; object-fit:cover; display:block; } .pk-magnet-empty { width:100%; height:100%; display:flex; align-items:center; justify-content:center; font-size:14px; opacity:0.62; } .pk-magnet-body { padding:18px 20px 20px 20px; display:flex; flex-direction:column; gap:12px; } .pk-magnet-title { font-size:16px; font-weight:800; line-height:1.45; overflow:hidden; word-break:break-all; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; text-overflow:ellipsis; } .pk-magnet-desc { font-size:12px; line-height:1.5; opacity:0.68; } .pk-magnet-meta { display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px; } .pk-magnet-meta-item { border:1px solid var(--pk-bd); border-radius:10px; padding:8px 9px; background:var(--pk-bg); min-width:0; } .pk-magnet-meta-label { font-size:11px; opacity:0.58; margin-bottom:4px; } .pk-magnet-meta-value { font-size:13px; font-weight:700; overflow:hidden; white-space:nowrap; text-overflow:ellipsis; } .pk-magnet-hash { font-size:11px; line-height:1.45; opacity:0.62; word-break:break-all; background:var(--pk-hl); border-radius:8px; padding:8px 10px; } .pk-magnet-save-row { display:flex; align-items:center; gap:5px; min-width:0; height:26px; margin-top:-2px; font-size:12px; color:var(--pk-fg); } .pk-magnet-save-label { opacity:.72; flex-shrink:0; } .pk-magnet-save-icon { width:16px; height:16px; display:flex; align-items:center; justify-content:center; flex-shrink:0; overflow:hidden; } .pk-magnet-save-icon svg { width:16px !important; height:16px !important; display:block; } .pk-magnet-save-name { font-weight:700; max-width:150px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .pk-magnet-save-help { color:#aaa; display:flex; align-items:center; flex-shrink:0; } .pk-magnet-save-change { color:var(--pk-pri); cursor:pointer; font-size:12px; font-weight:700; flex-shrink:0; } .pk-magnet-save-change:hover { text-decoration:underline; } .pk-magnet-source { font-size:11px; line-height:1.35; opacity:.58; display:flex; align-items:center; gap:4px; } .pk-magnet-source a { color:var(--pk-pri); text-decoration:none; font-weight:700; } .pk-magnet-source a:hover { text-decoration:underline; } .pk-magnet-warn { font-size:12px; line-height:1.45; color:#d93025; background:rgba(217,48,37,0.08); border:1px solid rgba(217,48,37,0.16); border-radius:8px; padding:8px 10px; } .pk-magnet-shots { display:flex; gap:7px; overflow-x:auto; padding-bottom:2px; } .pk-magnet-thumb-wrap { width:68px; height:42px; border-radius:7px; overflow:hidden; flex:0 0 auto; position:relative; border:1px solid var(--pk-bd); box-sizing:border-box; cursor:pointer; user-select:none; transition:border-color .16s ease; } .pk-magnet-thumb-wrap:hover { } .pk-magnet-thumb-wrap.active { border-color:transparent; } .pk-magnet-thumb-wrap.active::after { content:""; position:absolute; inset:0; border:2px solid var(--pk-pri); border-radius:7px; box-sizing:border-box; pointer-events:none; z-index:2; } .pk-magnet-thumb-wrap .pk-magnet-thumb { width:100%; height:100%; border:none; border-radius:0; display:block; object-fit:cover; } .pk-magnet-shot-time { position:absolute; right:3px; bottom:3px; padding:1px 4px; border-radius:4px; background:rgba(0,0,0,.62); color:#fff; font-size:10px; line-height:1.25; pointer-events:none; z-index:3; } .pk-magnet-thumb { width:68px; height:42px; border-radius:7px; object-fit:cover; flex:0 0 auto; border:1px solid var(--pk-bd); box-sizing:border-box; cursor:pointer; user-select:none; -webkit-user-drag:none; transition:border-color .16s ease, border-width .16s ease; } .pk-magnet-thumb:hover { } .pk-magnet-thumb.active { border:2px solid var(--pk-pri); } .pk-magnet-hero img { user-select:none; -webkit-user-drag:none; } .pk-magnet-actions { display:flex; justify-content:flex-end; gap:10px; margin-top:2px; } html.pk-txt-preview-fullscreen-lock, body.pk-txt-preview-fullscreen-lock { overflow:hidden !important; } .pk-modal-ov.pk-txt-preview-ov:not(.pk-modal-over-player-webfullscreen) { position:fixed !important; inset:0 !important; display:block !important; z-index:2147483641 !important; } .pk-modal-ov.pk-txt-preview-fullscreen-ov { padding:0 !important; align-items:stretch !important; justify-content:stretch !important; } .pk-modal-ov .pk-modal.pk-txt-preview-modal { position:absolute !important; top:10% !important; left:50% !important; transform:translateX(-50%) !important; width:auto !important; padding:18px; height:80% !important; min-height:0 !important; max-height:none !important; overflow:hidden; box-sizing:border-box; min-width:0; display:flex !important; flex-direction:column; } .pk-modal-ov .pk-modal.pk-txt-preview-modal.pk-txt-preview-fullscreen { position:fixed !important; inset:0 !important; top:0 !important; left:0 !important; transform:none !important; width:100vw !important; height:100vh !important; min-height:0 !important; max-height:none !important; margin:0 !important; padding:14px !important; border-radius:0 !important; } .pk-modal-ov .pk-txt-preview-title { border:none; margin:0; padding:0 112px 0 0; font-size:18px; font-weight:700; color:var(--pk-fg); line-height:1.45; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:100%; box-sizing:border-box; flex:0 0 auto; min-height:28px; position:relative; z-index:2; } .pk-modal-ov .pk-txt-preview-head-actions { position:absolute; top:15px; right:50px; display:flex; align-items:center; gap:6px; z-index:11; } .pk-modal-ov .pk-modal.pk-txt-preview-modal.pk-txt-preview-fullscreen .pk-txt-preview-head-actions { top:14px; right:50px; } .pk-modal-ov .pk-txt-preview-icon-btn { width:28px; height:28px; display:inline-flex; align-items:center; justify-content:center; border:0; border-radius:6px; background:transparent; color:var(--pk-icon-c); cursor:pointer; padding:0; transition:background .1s,color .1s,opacity .1s; box-sizing:border-box; } .pk-modal-ov .pk-txt-preview-icon-btn:hover:not(:disabled) { background:var(--pk-hl); color:var(--pk-fg); } .pk-modal-ov .pk-txt-preview-icon-btn:disabled { opacity:.38; cursor:not-allowed; pointer-events:none; } .pk-modal-ov .pk-txt-preview-icon-btn svg { width:16px !important; height:16px !important; display:block; pointer-events:none; } .pk-modal-ov .pk-txt-preview-link-btn svg { width:18px !important; height:18px !important; transform:scale(1.08); } .pk-modal-ov .pk-txt-preview { width:min(760px,86vw); max-width:86vw; height:auto; flex:1 1 0; min-height:0; display:flex; flex-direction:column; gap:12px; color:var(--pk-fg); overflow:hidden; min-width:0; } .pk-modal-ov .pk-modal.pk-txt-preview-modal.pk-txt-preview-fullscreen .pk-txt-preview { width:100%; max-width:none; height:auto; flex:1 1 0; min-height:0; } .pk-modal-ov .pk-txt-preview-body { flex:1 1 0; min-height:0; max-height:none; border:1px solid var(--pk-bd); border-radius:8px; background:rgba(var(--pk-bg-rgb),0.92); overflow-y:auto; overflow-x:hidden; display:block; position:relative; scrollbar-color:var(--pk-sb-th) var(--pk-sb-bg); overscroll-behavior:contain; } .pk-modal-ov .pk-txt-preview-body::-webkit-scrollbar { width:6px; height:6px; } .pk-modal-ov .pk-txt-preview-body::-webkit-scrollbar-track { background:var(--pk-sb-bg); } .pk-modal-ov .pk-txt-preview-body::-webkit-scrollbar-thumb { background:var(--pk-sb-th); border-radius:3px; } .pk-modal-ov .pk-txt-preview-body::-webkit-scrollbar-thumb:hover { background:var(--pk-sb-hov); } .pk-modal-ov .pk-txt-preview-pre { display:block; width:100%; min-width:0; min-height:100%; margin:0; padding:16px; overflow:visible; white-space:pre-wrap; word-break:break-word; overflow-wrap:anywhere; color:var(--pk-fg); background:transparent; font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace; font-size:13px; line-height:1.7; box-sizing:border-box; } .pk-modal-ov .pk-txt-preview-status { min-height:100%; display:flex; align-items:center; justify-content:center; padding:24px; text-align:center; color:var(--pk-fg); opacity:.72; font-size:14px; line-height:1.7; white-space:pre-line; box-sizing:border-box; } .pk-modal-ov .pk-txt-preview-error { color:#d93025; opacity:1; } .pk-script-update-toast { position:relative;top:auto;left:auto;transform:translateY(-15px) scale(0.95);opacity:0;transition:all .3s cubic-bezier(.23,1,.32,1);max-width:80vw;pointer-events:auto;cursor:pointer;background:rgba(250,173,20,.96);color:#fff;border:1px solid rgba(250,173,20,.22);box-shadow:0 10px 26px rgba(0,0,0,.18);border-radius:999px;padding:9px 10px 9px 14px;display:flex;align-items:center;gap:10px;font-size:13px;font-weight:700;line-height:1.4; } .pk-script-update-toast.pk-dark { box-shadow:0 10px 28px rgba(0,0,0,.42); } .pk-script-update-msg { min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; } .pk-script-update-open { border:1px solid rgba(255,255,255,.5);background:rgba(255,255,255,.16);color:#fff;border-radius:999px;height:24px;padding:0 10px;font-size:12px;font-weight:800;cursor:pointer;white-space:nowrap; } .pk-script-update-close { width:22px;height:22px;border:0;border-radius:50%;background:rgba(255,255,255,.18);color:#fff;cursor:pointer;font-size:16px;line-height:22px;padding:0;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0; } .pk-script-version-box { position:relative;padding:18px 14px 14px 14px;border:2px solid var(--pk-bd);border-radius:8px;background:var(--pk-bg);display:flex;flex-direction:column;gap:9px;transition:border-color .2s; } .pk-script-version-grid { display:grid;grid-template-columns:auto 1fr;gap:8px 14px;align-items:center;font-size:13px; } .pk-script-version-k { color:var(--pk-fg);opacity:.72;font-weight:600;white-space:nowrap; } .pk-script-version-v { color:var(--pk-fg);font-weight:800;text-align:right;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; } .pk-script-version-v.new { color:var(--pk-pri);cursor:pointer; } .pk-dark { --pk-bg: #202020; --pk-bg-rgb: 32, 32, 32; --pk-fg: #f5f5f5; --pk-bd: #333333; --pk-hl: #2d2d2d; --pk-sel-bg: #2b3a4a; --pk-sel-bd: #0067c0; --pk-pri: #4cc2ff; --pk-btn-hov: #3a3a3a; --pk-gh: #2a2a2a; --pk-gh-fg: #eee; --pk-sb-th: #555; --pk-sb-hov: #777; --pk-icon-c: #aaa; --pk-tip-bg: rgba(20, 20, 20, 0.95); --pk-tip-fg: #ffffff; --pk-tip-bd: rgba(255, 255, 255, 0.1); --pk-tip-sd: rgba(0, 0, 0, 0.4); --pk-toast-bg: rgba(45, 45, 45, 0.95); --pk-toast-fg: #ffffff; --pk-toast-bd: rgba(255, 255, 255, 0.15); --pk-v-line: rgba(255, 255, 255, 0.25); } .pk-dark .pk-loading-ov { background: rgba(0,0,0,0.8); } .pk-ov { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 10000; background: rgba(0,0,0,0.4); backdrop-filter: blur(5px); display: flex; align-items: center; justify-content: center; font-family: inherit; outline: none; overscroll-behavior: none; -webkit-user-select: none; user-select: none; } .pk-ov input, .pk-ov textarea { -webkit-user-select: text !important; user-select: text !important; cursor: text; } .pk-ov,.pk-modal-ov,.pk-img-ov,#pk-player-ov,#pk-audio-ov,.pk-cal-pop,.pk-crumb-pop,.pk-hist-pop,.pk-tooltip,.pk-msg-toast,.pk-float-bar-item,.pk-selection-box,.pk-drag-ghost{line-height:1.5;} .pk-ov :where(div,span,button,input,textarea,select,label,a,p,h1,h2,h3,h4,h5,h6,li,td,th,mark),.pk-modal-ov :where(div,span,button,input,textarea,select,label,a,p,h1,h2,h3,h4,h5,h6,li,td,th,mark),.pk-img-ov :where(div,span,button,input,textarea,select,label,a,p,h1,h2,h3,h4,h5,h6,li,td,th,mark),#pk-player-ov :where(div,span,button,input,textarea,select,label,a,p,h1,h2,h3,h4,h5,h6,li,td,th,mark),#pk-audio-ov :where(div,span,button,input,textarea,select,label,a,p,h1,h2,h3,h4,h5,h6,li,td,th,mark),.pk-cal-pop :where(div,span,button,input,textarea,select,label,a,p,h1,h2,h3,h4,h5,h6,li,td,th,mark),.pk-crumb-pop :where(div,span,button,input,textarea,select,label,a,p,h1,h2,h3,h4,h5,h6,li,td,th,mark),.pk-hist-pop :where(div,span,button,input,textarea,select,label,a,p,h1,h2,h3,h4,h5,h6,li,td,th,mark){line-height:1.5;} .pk-ov :where(.pk-select-item,.pk-hist-item,.pk-ctx-item,.pk-crumb-item,.pk-menu-item,.pk-dropdown-item),.pk-modal-ov :where(.pk-select-item,.pk-hist-item,.pk-ctx-item,.pk-crumb-item,.pk-menu-item,.pk-dropdown-item),.pk-hist-pop :where(.pk-select-item,.pk-hist-item),.pk-crumb-pop :where(.pk-crumb-item,.pk-select-item){min-height:32px;line-height:1.5;display:flex;align-items:center;box-sizing:border-box;} .pk-ov :where(.pk-select-item span,.pk-hist-item span,.pk-ctx-item span,.pk-crumb-item span,.pk-menu span,#pk-rn-vp span,#pk-rn-vp mark),.pk-modal-ov :where(.pk-select-item span,.pk-hist-item span,.pk-ctx-item span,.pk-crumb-item span,.pk-menu span,#pk-rn-vp span,#pk-rn-vp mark),.pk-hist-pop :where(.pk-select-item span,.pk-hist-item span),.pk-crumb-pop :where(.pk-crumb-item span,.pk-select-item span){line-height:1.5;min-height:1.5em;box-sizing:border-box;} .pk-ov :where(mark,#pk-rn-vp span[style*="background"],.pk-row mark,.pk-modal mark),.pk-modal-ov :where(mark,#pk-rn-vp span[style*="background"]){display:inline-block;line-height:1.35;padding-top:1px;padding-bottom:1px;vertical-align:baseline;} .pk-win { width: 90%; max-width: 1600px; min-width: 720px; min-height: 340px; height: 80%; background: var(--pk-bg); color: var(--pk-fg); line-height:1.5; border-radius: 8px; box-shadow: 0 25px 50px rgba(0,0,0,0.25); display: flex; flex-direction: row; overflow: hidden; border: 1px solid var(--pk-bd); position: relative; } .pk-sidebar { width: 68px; background: var(--pk-bg); border-right: 1px solid var(--pk-bd); display: flex; flex-direction: column; align-items: center; padding: 16px 0; flex-shrink: 0; z-index: 10; gap: 0; overflow-x: hidden; box-sizing: border-box; } .pk-nav-btn { width: 44px !important; max-width: 100% !important; height: 44px !important; border-radius: 10px; color: var(--pk-icon-c); padding: 0 !important; border: none; display: flex; align-items: center; justify-content: center; cursor: pointer; margin-bottom: 0; transition: background-color 0.1s ease, color 0.1s ease; position: relative !important; box-sizing: border-box !important; } .pk-nav-btn:hover { background: var(--pk-hl); color: var(--pk-fg); } .pk-nav-btn.act { background: var(--pk-sel-bg); color: var(--pk-pri); } #pk-btn-cloud { background: var(--pk-pri) !important; color: #fff !important; border-radius: 50% !important; margin-bottom: 12px !important; padding: 0 !important; overflow: visible !important; display: flex !important; align-items: center !important; justify-content: center !important; } #pk-btn-cloud:hover { filter: brightness(1.1); } #pk-btn-cloud svg { width: 24px !important; height: 24px !important; transform: scale(1.3); transform-origin: center center; margin: 0 !important; transition: transform 0.2s; } .pk-nav-btn svg { width: 24px !important; height: 24px !important; } .pk-maximized #pk-btn-cloud { background: var(--pk-pri) !important; border-radius: 8px !important; width: calc(100% - 20px) !important; max-width: calc(100% - 20px) !important; height: 48px !important; padding: 0 15px !important; margin-bottom: 20px !important; box-sizing: border-box !important; } .pk-maximized #pk-btn-cloud svg { width: 24px !important; height: 24px !important; transform: scale(1.4); margin-right: 6px; } .pk-maximized #pk-btn-cloud:hover { filter: brightness(1.1); } .pk-sidebar #pk-settings { margin-top: auto; } .pk-btn-danger { background: #d93025 !important; color: #fff !important; border: none !important; } .pk-btn-danger:hover, #pk-empty-trash:hover { background: #d93025 !important; color: #fff !important; filter: brightness(1.15); opacity: 1 !important; } .pk-sidebar #pk-settings:hover { background: var(--pk-hl); color: var(--pk-fg); } .pk-sidebar #pk-settings svg { width: 26px !important; height: 26px !important; } .pk-main-col { flex: 1; display: flex; flex-direction: column; overflow: hidden; position: relative; min-width: 0; } .pk-hd { height: 48px; border-bottom: 1px solid var(--pk-bd); display: flex; align-items: center; justify-content: space-between; padding: 0 16px; background: var(--pk-bg); } .pk-tt { font-weight: 700; font-size: 20px; display: flex; align-items: center; gap: 10px; } .pk-tt svg { color: #333; margin-right: 10px; transition: color 0.2s ease; } .pk-ov.pk-dark .pk-tt svg { color: #fff; } .pk-tb { height: 50px !important; padding: 0 8px !important; border-bottom: 1px solid var(--pk-bd); display: flex; gap: clamp(4px, 0.6vw, 8px) !important; align-items: center !important; background: var(--pk-bg); overflow: visible !important; flex-wrap: nowrap !important; position: relative; } #pk-top-bar { z-index: 30; } #pk-actionbar, #pk-trash-bar { z-index: 20; } .pk-btn { height: 32px; padding: 0 12px; border-radius: 4px; border: 1px solid transparent; background: transparent; color: var(--pk-fg); cursor: pointer; font-size: 13px; display: flex; align-items: center; justify-content: center; gap: 6px; transition: background-color 0.1s; position: relative; font-weight: 500; white-space: nowrap; flex-shrink: 0; backface-visibility: hidden; } #pk-theme svg { transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); } #pk-theme:active svg { transform: rotate(90deg) scale(0.8); } .pk-btn:hover:not(:disabled) { background: var(--pk-btn-hov) !important; } .pk-btn.pri:hover:not(:disabled) { background: var(--pk-pri) !important; filter: brightness(1.15); color: #fff !important; } .pk-copy-success-freeze, .pk-copy-success-freeze:hover, .pk-copy-success-freeze:disabled, .pk-copy-success-freeze:disabled:hover, .pk-btn.pk-copy-success-freeze, .pk-btn.pk-copy-success-freeze:hover, .pk-btn.pk-copy-success-freeze:disabled, .pk-btn.pk-copy-success-freeze:disabled:hover, #pk_err_launch_btn.pk-copy-success-freeze, #pk_err_launch_btn.pk-copy-success-freeze:hover, #pk_err_launch_btn.pk-copy-success-freeze:disabled, #pk_err_launch_btn.pk-copy-success-freeze:disabled:hover { background:#52c41a !important; color:#fff !important; filter:none !important; opacity:1 !important; cursor:default !important; pointer-events:none !important; } .pk-potfix-entry { align-self:flex-start; height:30px; padding:0 2px; border:none; background:transparent; color:var(--pk-pri); font-size:12px; font-weight:700; cursor:pointer; display:inline-flex; align-items:center; justify-content:flex-start; } .pk-potfix-entry:hover { text-decoration:underline; background:transparent !important; } .pk-potfix-desc { white-space: pre-line;line-height:1.62; font-size:12px; color:var(--pk-fg); opacity:0.72; } .pk-potfix-linkbox { width:100%; min-height:72px; max-height:110px; resize:none; border:1px solid var(--pk-bd); border-radius:6px; background:var(--pk-hl); color:var(--pk-fg); padding:10px 12px; box-sizing:border-box; font-size:12px; line-height:1.45; font-family:"SF Mono","Consolas",monospace; word-break:break-all; outline:none; } .pk-potfix-actions { display:grid; grid-template-columns:1fr 1fr; gap:8px; margin-top:0; } .pk-potfix-actions .pk-btn, .pk-potfix-repair-actions .pk-btn { height:40px; justify-content:center; border-radius:6px; border:1px solid var(--pk-bd); background:var(--pk-bg); color:var(--pk-fg); box-shadow:0 1px 4px rgba(0,0,0,.06); } .pk-potfix-repair-panel { border:1px solid var(--pk-bd); border-radius:6px; background:rgba(var(--pk-bg-rgb),0.76); padding:12px; display:flex; flex-direction:column; gap:10px; flex:0 0 auto; } .pk-potfix-repair-actions { display:grid; grid-template-columns:1fr 1fr; gap:8px; } .pk-potfix-secondary { grid-column:1 / -1; position:static; display:flex; justify-content:flex-start; gap:8px; flex-wrap:wrap; margin:0; min-width:0; background:transparent; padding-right:0; } .pk-potfix-secondary .pk-btn { height:30px; padding:0 12px; border:1px solid var(--pk-bd); border-radius:6px; background:var(--pk-bg); color:var(--pk-fg); opacity:.72; font-size:12px; box-shadow:0 2px 8px rgba(0,0,0,.08); } .pk-potfix-secondary .pk-btn:hover:not(:disabled) { opacity:1; color:var(--pk-pri); border-color:var(--pk-pri); background:var(--pk-hl) !important; } .pk-potfix-footer { position:relative; display:grid; grid-template-columns:1fr minmax(220px,236px) auto; align-items:center; gap:10px 12px; margin-top:8px; padding:12px 0 2px; border-top:1px solid var(--pk-bd); } .pk-potfix-retry { grid-column:2; width:100%; height:42px !important; border:none !important; border-radius:6px !important; background:var(--pk-pri) !important; color:#fff !important; font-weight:700 !important; justify-content:center !important; box-shadow:0 2px 10px rgba(0,103,192,.28); } .pk-potfix-foot { grid-column:3; display:flex; justify-content:flex-end; min-width:56px; } .pk-potfix-version-note { font-size:12px; line-height:1.45; color:#b26a00; background:rgba(250,173,20,0.10); border:1px solid rgba(250,173,20,0.22); border-radius:8px; padding:8px 10px; } .pk-modal.pk-potfix-modal { overflow-x:hidden !important; overflow-y:auto !important; max-height:92vh !important; align-items:stretch; gap:12px !important; clip-path:inset(0 round 12px); } .pk-modal.pk-potfix-modal > * { flex-shrink:0; } .pk-potfix-adv-desc { font-size:12px; line-height:1.55; color:var(--pk-fg); opacity:.66; } .pk-potfix-path-label { font-size:12px; font-weight:700; color:var(--pk-fg); opacity:.82; } .pk-potfix-path-row { display:grid; grid-template-columns:1fr auto; gap:8px; align-items:center; } .pk-potfix-path-input { height:36px; min-width:0; border:1px solid var(--pk-bd); border-radius:6px; background:var(--pk-bg); color:var(--pk-fg); padding:0 10px; font-size:12px; outline:none; box-sizing:border-box; } .pk-potfix-path-input:focus { border-color:var(--pk-pri); box-shadow:0 0 0 2px rgba(0,103,192,.12); } .pk-potfix-path-row .pk-btn { height:34px; border:1px solid var(--pk-bd); border-radius:6px; background:transparent; color:var(--pk-fg); font-size:12px; justify-content:center; opacity:.86; } .pk-potfix-repair-actions .pk-btn:hover:not(:disabled), .pk-potfix-path-row .pk-btn:hover:not(:disabled) { opacity:1; color:var(--pk-pri); border-color:var(--pk-pri); background:var(--pk-hl) !important; } .pk-potfix-status { font-size:12px; line-height:1.45; border-radius:6px; padding:8px 10px; background:var(--pk-hl); color:var(--pk-fg); opacity:.86; } .pk-potfix-status.warn { color:#b26a00; background:rgba(250,173,20,0.10); border:1px solid rgba(250,173,20,0.18); opacity:1; } @media (max-width:520px) { .pk-modal.pk-potfix-modal { width:calc(100vw - 32px) !important; padding:22px !important; } .pk-potfix-actions, .pk-potfix-repair-actions, .pk-potfix-path-row, .pk-potfix-footer { grid-template-columns:1fr; } .pk-potfix-secondary { grid-column:auto; padding-right:0; margin-bottom:2px; } .pk-potfix-retry, .pk-potfix-foot { grid-column:auto; } .pk-potfix-foot { justify-content:center; } } .pk-btn:disabled { opacity: 0.4; cursor: not-allowed; } .pk-btn.pri { color: var(--pk-pri); font-weight: 600; } .pk-btn svg { width: 18px; height: 18px; flex-shrink: 0; display: inline-block; vertical-align: -4px; } .pk-btn span { white-space: nowrap; pointer-events: none; } .pk-blacklist-area { display: flex; align-items: center; gap: 8px; margin-left: auto; max-width: 300px; min-width: 150px; flex-shrink: 1; } .pk-blacklist-area input { height: 32px; padding: 0 8px; border: 1px solid var(--pk-bd); border-radius: 4px; background: var(--pk-bg); color: var(--pk-fg); font-size: 13px; width: 100%; transition: border-color 0.2s; } .pk-blacklist-area input:focus { border-color: var(--pk-pri); outline: none; } .pk-global-chk { display: flex; align-items: center; cursor: pointer; margin-right: 8px; font-size: 13px; color: var(--pk-fg); user-select: none; white-space: nowrap; } .pk-global-chk input { margin: 0 4px 0 0; width: 16px; height: 16px; accent-color: var(--pk-pri); } .pk-search { position: relative; display: flex !important; align-items: center !important; margin: 0 !important; flex: 1 1 auto; min-width: 100px; max-width: 300px; transition: all 0.2s; z-index: 100; } .pk-search input { height: 32px; padding: 0 56px 0 10px; border: 1px solid var(--pk-bd); border-radius: 4px; background: var(--pk-bg); color: var(--pk-fg); font-size: 13px; width: 100% !important; margin: 0 !important; transition: border-color 0.2s; box-sizing: border-box; } .pk-search input:focus { border-color: var(--pk-pri); outline: none; } #pk-search-btn { position: absolute; right: 10px; left: auto; width: 14px; height: 14px; color: #888; cursor: pointer; transition: color 0.2s; } .pk-search input:focus + svg { color: var(--pk-pri); } .pk-f-ext { cursor: pointer; color: var(--pk-fg); padding: 4px 8px; border-radius: 4px; transition: background 0.2s, color 0.2s; font-weight: 500; font-size: 13px; } .pk-f-ext:hover { background: var(--pk-hl); } .pk-f-ext.act { color: var(--pk-pri); font-weight: bold; } .pk-fc-btn { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 10px 12px; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; background: var(--pk-hl); color: var(--pk-fg); transition: all 0.2s; border: 1px solid transparent; white-space: nowrap; } .pk-fc-btn:hover { background: rgba(0, 103, 192, 0.05); color: var(--pk-pri); border-color: var(--pk-pri); } .pk-fc-btn.act { background: var(--pk-pri); color: #fff; border: none; font-weight: bold; } .pk-fc-btn.act:hover { background: var(--pk-pri); color: #fff; } .pk-search-clear { position: absolute; right: 32px; top: 50%; transform: translateY(-50%); width: 20px; height: 20px; display: none; align-items: center; justify-content: center; cursor: pointer; color: #999; border-radius: 50%; transition: background 0.2s; } .pk-search-clear:hover { background: var(--pk-hl); color: #666; } .pk-search-clear svg { position: static !important; width: 14px !important; height: 14px !important; color: inherit !important; } .pk-hist-pop { position: absolute; top: 100%; left: 0; right: 0; background: var(--pk-bg); border: 1px solid var(--pk-bd); border-radius: 8px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); z-index: 10010 !important; display: none; flex-direction: column; overflow: hidden; padding: 4px; margin-top: 4px; } .pk-hist-hd { display: flex; justify-content: space-between; align-items: center; padding: 10px 12px; font-size: 11px; color: var(--pk-pri); font-weight: bold; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--pk-bd); margin-bottom: 4px; } .pk-hist-clear-btn { cursor: pointer; } .pk-hist-clear-btn:hover { color: var(--pk-pri); } .pk-hist-item { padding: 8px 12px; font-size: 13px; cursor: pointer; color: var(--pk-fg); display: flex; align-items: center; gap: 8px; border-bottom: 1px solid transparent; } .pk-hist-item:hover { background: var(--pk-sel-bg); } .pk-hist-item svg { position: static !important; width: 14px !important; height: 14px !important; color: #999; margin: 0 !important; } .pk-dup-toolbar { display: none; align-items: center; gap: 4px; padding: 0 8px; height: 100%; margin-left: 8px; background: transparent; border: none; flex-shrink: 0; } .pk-dup-lbl, .pk-dup-chk { white-space: nowrap !important; font-weight: 500; color: var(--pk-fg); font-size: 13px; margin-right: 6px; opacity: 0.8; flex-shrink: 0; cursor: pointer; display: flex; align-items: center; } .pk-dup-chk input { margin: 0 4px 0 0; vertical-align: middle; accent-color: var(--pk-pri); width: 16px; height: 16px; } #pk-upload-tools { min-width: 0; flex-shrink: 0 !important; } .pk-upload-tip { display:none;align-items:center;height:100%;margin-left:0;margin-right:auto;max-width:min(42vw,560px);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px !important;line-height:normal !important;color:#888 !important;font-weight:normal !important;opacity:.8 !important;cursor:default;flex-shrink:1; } .pk-txt-short { display: none; } .pk-txt-long { display: inline; } #pk-search-path-con .pk-txt-short { font-weight: 500; color: var(--pk-fg); } #pk-search-path-con.pk-share-path-compact .pk-txt-long { display: none !important; } #pk-search-path-con.pk-share-path-compact .pk-txt-short { display: inline !important; } #pk-dup-folder-sel-wrap { position: relative; display: inline-flex; align-items: center; width: 220px; min-width: 0; flex-shrink: 0; } #pk-dup-folder-sel { position: absolute !important; inset: 0 !important; width: 0 !important; height: 0 !important; opacity: 0 !important; pointer-events: none !important; } #pk-dup-folder-btn { width: 100%; height: 30px; border: 1px solid var(--pk-bd); border-radius: 4px; background: var(--pk-bg); color: var(--pk-fg); padding: 0 28px 0 10px; font-size: 12px; text-align: left; cursor: pointer; position: relative; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #pk-dup-folder-btn:hover { background: var(--pk-btn-hov); border-color: var(--pk-pri); } #pk-dup-folder-btn::after { content: ""; position: absolute; right: 10px; top: 50%; width: 8px; height: 8px; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: translateY(-65%) rotate(45deg); opacity: 0.7; pointer-events: none; } .pk-dup-folder-pop { position:fixed; min-width:220px; max-width:520px; max-height:320px; background:#ffffff; color:#1f1f1f; border:1px solid rgba(0,0,0,0.12); border-radius:8px; box-shadow:0 8px 24px rgba(0,0,0,0.18); z-index:2147483647; overflow:hidden; display:flex; flex-direction:column; } .pk-dup-folder-pop.pk-dark { background:rgba(28,28,28,0.98); color:#f5f5f5; border-color:rgba(255,255,255,0.12); box-shadow:0 10px 28px rgba(0,0,0,0.45); } .pk-dup-folder-head { flex:0 0 auto; padding:4px; border-bottom:1px solid rgba(0,0,0,0.08); background:#ffffff; } .pk-dup-folder-pop.pk-dark .pk-dup-folder-head { background:rgba(28,28,28,0.98); border-bottom:1px solid rgba(255,255,255,0.10); } .pk-dup-folder-list { flex:1 1 auto; min-height:0; max-height:272px; overflow-y:auto; overflow-x:hidden; padding:4px; } .pk-dup-folder-item { width:100%; min-height:30px; border:0; background:transparent; color:inherit; border-radius:6px; padding:0 10px; font-size:12px; text-align:left; cursor:pointer; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } .pk-dup-folder-item:hover { background:rgba(0,0,0,0.06); } .pk-dup-folder-pop.pk-dark .pk-dup-folder-item:hover { background:rgba(255,255,255,0.08); } .pk-dup-folder-item.act { background:rgba(26,115,232,0.12); color:#1a73e8; font-weight:600; } .pk-dup-folder-pop.pk-dark .pk-dup-folder-item.act { background:rgba(138,180,248,0.18); color:#8ab4f8; } .pk-dup-folder-item.pk-reset { position:relative; top:auto; z-index:auto; color:#1a73e8; font-weight:700; background:transparent; border-bottom:0; border-radius:6px; } .pk-dup-folder-item.pk-reset:hover { background:#f7f9fc; } .pk-dup-folder-pop.pk-dark .pk-dup-folder-item.pk-reset { color:#8ab4f8; background:transparent; border-bottom:0; } .pk-dup-folder-pop.pk-dark .pk-dup-folder-item.pk-reset:hover { background:rgba(255,255,255,0.06); } .pk-ov.pk-hide-btn-text .pk-txt-long, .pk-ov.pk-auto-hide-btn-text .pk-txt-long { display: none !important; } .pk-ov.pk-hide-btn-text .pk-txt-short, .pk-ov.pk-auto-hide-btn-text .pk-txt-short { display: inline !important; } .pk-ov.pk-hide-btn-text #pk-dup-folder-sel-wrap, .pk-ov.pk-auto-hide-btn-text #pk-dup-folder-sel-wrap { max-width: 100px !important; } .pk-ov.pk-hide-btn-text .pk-btn:not(#pk-filter-btn):not(#pk-filter-exit-btn):not(#pk-share-parse-back-list) > span, .pk-ov.pk-auto-hide-btn-text .pk-btn:not(#pk-filter-btn):not(#pk-filter-exit-btn):not(#pk-share-parse-back-list) > span, .pk-ov.pk-hide-btn-text .pk-nav-btn > span, .pk-ov.pk-auto-hide-btn-text .pk-nav-btn > span { display: none !important; } .pk-ov.pk-hide-btn-text .pk-tb .pk-btn:not(#pk-filter-btn):not(#pk-filter-exit-btn):not(#pk-share-parse-back-list), .pk-ov.pk-auto-hide-btn-text .pk-tb .pk-btn:not(#pk-filter-btn):not(#pk-filter-exit-btn):not(#pk-share-parse-back-list) { padding: 0 8px !important; min-width: 32px; justify-content: center; gap: 0 !important; } .pk-ov.pk-hide-btn-text #pk-share-parse-back-list > span, .pk-ov.pk-auto-hide-btn-text #pk-share-parse-back-list > span { display: none !important; } .pk-ov.pk-hide-btn-text #pk-share-parse-back-list, .pk-ov.pk-auto-hide-btn-text #pk-share-parse-back-list { padding: 0 8px !important; min-width: 32px; justify-content: center; gap: 0 !important; } .pk-ov.pk-hide-btn-text #pk-btn-upload, .pk-ov.pk-auto-hide-btn-text #pk-btn-upload { padding: 0 8px !important; gap: 4px !important; } .pk-ov.pk-hide-btn-text .pk-btn-arrow, .pk-ov.pk-auto-hide-btn-text .pk-btn-arrow { margin-left: 0 !important; } .pk-ov.pk-hide-btn-text .pk-maximized .pk-sidebar, .pk-ov.pk-auto-hide-btn-text .pk-maximized .pk-sidebar { width: 68px !important; align-items: center !important; padding: 16px 0 !important; } .pk-ov.pk-hide-btn-text .pk-maximized .pk-nav-btn, .pk-ov.pk-auto-hide-btn-text .pk-maximized .pk-nav-btn { width: 44px !important; justify-content: center !important; padding: 0 !important; gap: 0 !important; height: 44px !important; border-radius: 10px !important; } .pk-ov.pk-hide-btn-text .pk-maximized #pk-btn-cloud, .pk-ov.pk-auto-hide-btn-text .pk-maximized #pk-btn-cloud { border-radius: 50% !important; width: 44px !important; height: 44px !important; padding: 0 !important; margin-bottom: 12px !important; } .pk-ov.pk-hide-btn-text .pk-maximized #pk-btn-cloud svg, .pk-ov.pk-auto-hide-btn-text .pk-maximized #pk-btn-cloud svg { margin-right: 0 !important; } .pk-ov.pk-hide-btn-text .pk-maximized #pk-quota-wrap, .pk-ov.pk-auto-hide-btn-text .pk-maximized #pk-quota-wrap { align-items: center !important; padding: 0 !important; } .pk-ov.pk-hide-btn-text .pk-maximized #pk-quota-panel, .pk-ov.pk-auto-hide-btn-text .pk-maximized #pk-quota-panel { align-items: center !important; margin-bottom: 2px !important; opacity: 0.6 !important; } .pk-ov.pk-hide-btn-text .pk-maximized #pk-quota-head, .pk-ov.pk-auto-hide-btn-text .pk-maximized #pk-quota-head { flex-direction: column !important; align-items: center !important; justify-content: flex-end !important; gap: 2px !important; } .pk-ov.pk-hide-btn-text .pk-maximized #pk-transfer-detail-btn, .pk-ov.pk-auto-hide-btn-text .pk-maximized #pk-transfer-detail-btn { order: 1 !important; align-items: center !important; margin: 0 0 2px 0 !important; } .pk-ov.pk-hide-btn-text .pk-maximized #pk-quota-txt, .pk-ov.pk-auto-hide-btn-text .pk-maximized #pk-quota-txt { order: 2 !important; font-size: 10px !important; font-weight: 700 !important; transform: scale(0.8) !important; line-height: 1 !important; } .pk-ov.pk-hide-btn-text .pk-maximized #pk-quota-bar-box, .pk-ov.pk-auto-hide-btn-text .pk-maximized #pk-quota-bar-box { width: 44px !important; height: 4px !important; border-radius: 2px !important; } .pk-btn-toggle { border: 1px solid var(--pk-bd); background: var(--pk-bg); color: var(--pk-fg); height: 30px; border-radius: 4px; padding: 0 10px; font-size: 12px; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; flex-shrink: 0; } .pk-btn-toggle:hover { background: var(--pk-btn-hov); border-color: var(--pk-pri); } .pk-btn-toggle span { font-weight: 700; color: var(--pk-pri); } #pk-dup-exit { flex-shrink: 0; } .pk-nav { display: flex !important; align-items: center !important; gap: 4px; overflow: hidden; white-space: nowrap; font-size: 13px; color: #666; margin: 0 8px; height: 100%; max-width: 60%; } .pk-nav span { cursor: pointer; padding: 2px 6px; border-radius: 4px; } .pk-nav span:hover { background: var(--pk-hl); color: var(--pk-fg); } .pk-nav span.act { font-weight: 600; color: var(--pk-fg); cursor: pointer; } .pk-grid-hd, .pk-row { display: grid; column-gap: 10px; align-items: center; font-size: 14px; color: var(--pk-fg); box-sizing: border-box; width: 100%; } .pk-grid-hd > div, .pk-row > div { display: flex; align-items: center; justify-content: flex-start !important; overflow: hidden; white-space: nowrap; text-align: left; } .pk-grid-hd > div:first-child, .pk-row > div:first-child { justify-content: center !important; overflow: visible !important; } .pk-grid-hd { height: 36px; border-bottom: 1px solid var(--pk-bd); font-size: 13px; color: #666; user-select: none; padding: 0 22px 0 16px; } .pk-row { padding: 0 16px; } .pk-col { cursor: pointer; font-weight: 600; display: flex; align-items: center; justify-content: flex-start; } .pk-col:hover { color: var(--pk-fg); } .pk-view-switch { display: inline-flex; align-items: center; gap: 4px; margin-left: 12px; padding: 3px; border: 1px solid var(--pk-bd); border-radius: 999px; background: var(--pk-bg); flex-shrink: 0; } .pk-view-switch[style*="display: none"] { margin-left: 0; } .pk-view-btn { width: 34px; height: 28px; border: none; border-radius: 999px; background: transparent; color: #777; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; transition: background 0.2s,color 0.2s,transform 0.2s; padding: 0; } .pk-view-btn:hover { color: var(--pk-fg); background: var(--pk-hl); } .pk-view-btn.active { background: var(--pk-pri); color: #fff; box-shadow: 0 3px 10px rgba(0,103,192,0.22); } .pk-mode-view-syncing .pk-view-btn { transition: none !important; } .pk-grid-hd.pk-grid-view-hd { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0 18px 0 16px; overflow: visible; position: relative; z-index: 30; } .pk-grid-hd.pk-grid-view-hd > div { overflow: visible; min-width: 0; } .pk-grid-hd.pk-grid-view-hd > div:first-child { width: auto; flex: 0 1 auto; } .pk-grid-check-tools { display:flex; align-items:center; gap:10px; min-width:0; flex:0 1 auto; overflow:visible !important; padding-left:9px; box-sizing:border-box; } .pk-grid-check-tools > input[type="checkbox"] { flex:0 0 auto; margin:0; } .pk-grid-check-tools > #pk-grid-folder-first { flex:0 0 auto; } .pk-grid-check-tools { display:flex; align-items:center; gap:8px; min-width:0; overflow:visible !important; padding-left:9px; box-sizing:border-box; } .pk-grid-sort-wrap { position: relative; display: inline-flex; align-items: center; flex: 0 0 auto; flex-shrink: 0; overflow: visible !important; z-index: 2; } .pk-grid-sort-trigger { height: 30px; border: 1px solid var(--pk-bd); border-radius: 999px; background: var(--pk-bg); color: var(--pk-fg); padding: 0 12px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; cursor: pointer; font-size: 12px; font-weight: 600; transition: border-color 0.2s,background 0.2s,color 0.2s; max-width: 100%; } .pk-grid-sort-trigger:hover, .pk-grid-sort-wrap.open .pk-grid-sort-trigger { border-color: var(--pk-pri); color: var(--pk-pri); background: var(--pk-hl); } #pk-btn-invert:hover,#pk-grid-invert:hover{color:var(--pk-pri)!important;} .pk-grid-sort-trigger svg { flex-shrink: 0; } .pk-grid-sort-trigger span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } .pk-grid-sort-menu { position: absolute; top: calc(100% + 8px); right: 0; min-width: 156px; background: var(--pk-bg); border: 1px solid var(--pk-bd); border-radius: 12px; box-shadow: 0 12px 36px rgba(0,0,0,0.16); padding: 6px; display: none; z-index: 10020; } .pk-grid-sort-wrap.open .pk-grid-sort-menu { display: block; } .pk-grid-sort-opt { border-radius: 10px; padding: 9px 12px; display: flex; align-items: center; gap: 10px; color: var(--pk-fg); cursor: pointer; transition: background 0.15s,color 0.15s; font-size: 13px; font-weight: 500; white-space: nowrap; } .pk-grid-sort-opt:hover, .pk-grid-sort-opt.active { background: var(--pk-hl); color: var(--pk-pri); } .pk-grid-view .pk-vp { padding: 14px 0 18px; } .pk-grid-view .pk-in { left: 0; right: 0; } .pk-view-switching .pk-row, .pk-view-switching .pk-row::before, .pk-view-switching .pk-row::after, .pk-view-switching .pk-row *, .pk-grid-resizing .pk-row, .pk-grid-resizing .pk-row::before, .pk-grid-resizing .pk-row::after, .pk-grid-resizing .pk-row *, .pk-grid-scrolling .pk-row, .pk-grid-scrolling .pk-row::before, .pk-grid-scrolling .pk-row::after, .pk-grid-scrolling .pk-row * { transition: none !important; animation: none !important; } .pk-view-switching.pk-grid-view .pk-row, .pk-view-switching.pk-grid-view .pk-row:hover, .pk-view-switching.pk-grid-view .pk-row.sel, .pk-view-switching.pk-grid-view .pk-row.sel.pk-focused, .pk-dark .pk-view-switching.pk-grid-view .pk-row, .pk-dark .pk-view-switching.pk-grid-view .pk-row:hover, .pk-dark .pk-view-switching.pk-grid-view .pk-row.sel, .pk-dark .pk-view-switching.pk-grid-view .pk-row.sel.pk-focused, .pk-grid-resizing.pk-grid-view .pk-row, .pk-grid-resizing.pk-grid-view .pk-row:hover, .pk-grid-resizing.pk-grid-view .pk-row.sel, .pk-grid-resizing.pk-grid-view .pk-row.sel.pk-focused, .pk-dark .pk-grid-resizing.pk-grid-view .pk-row, .pk-dark .pk-grid-resizing.pk-grid-view .pk-row:hover, .pk-dark .pk-grid-resizing.pk-grid-view .pk-row.sel, .pk-dark .pk-grid-resizing.pk-grid-view .pk-row.sel.pk-focused { border-color: transparent !important; box-shadow: none !important; outline: none !important; } .pk-grid-view .pk-row { box-sizing: border-box; border-radius: 16px; border: 1px solid transparent; background: #e3e9f2; box-shadow: none; overflow: hidden; transition: background 0.2s; } .pk-grid-view .pk-row:hover { background: #d9e1ec; transform: none; box-shadow: none; } .pk-grid-view .pk-row.sel { background: #d4deeb; border-color: rgba(0,103,192,0.15); box-shadow: 0 0 0 1px rgba(0,103,192,0.15); } .pk-grid-view .pk-row.sel.pk-focused { border-color: var(--pk-pri); box-shadow: 0 0 0 1px var(--pk-pri); } .pk-grid-scrolling.pk-grid-view .pk-row.pk-focused:not(.sel), .pk-dark .pk-grid-scrolling.pk-grid-view .pk-row.pk-focused:not(.sel) { background: var(--pk-sel-bg) !important; border-color: var(--pk-pri) !important; box-shadow: none !important; outline: none !important; } .pk-grid-card-body { position: relative; display: flex; flex-direction: column; width: 100%; height: 100%; box-sizing: border-box; padding: 10px; gap: 10px; } .pk-gv-check { position: absolute; top: 12px; left: 12px; width: 24px; height: 24px; background: #fff; border-radius: 6px; z-index: 4; display: flex; align-items: center; justify-content: center; cursor: pointer; box-sizing: border-box; opacity: 0; visibility: hidden; pointer-events: none; transform: translateY(-2px); transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s ease; } .pk-gv-check::before { content: ''; position: absolute; left: 50%; top: 50%; width: 18px; height: 18px; margin-left: -9px; margin-top: -9px; border-radius: 50%; border: 1.5px solid #c7cdd6; background: transparent; box-sizing: border-box; transition: all 0.15s; } .pk-gv-check::after { content: ''; position: absolute; left: 50%; top: 50%; width: 4px; height: 8px; margin-left: -2px; margin-top: -5px; border: solid #fff; border-width: 0 1.5px 1.5px 0; opacity: 0; transform: rotate(45deg) scale(0.8); transform-origin: center; transition: opacity 0.15s, transform 0.15s; box-sizing: border-box; } .pk-grid-view .pk-row:hover .pk-gv-check, .pk-grid-view .pk-row.sel .pk-gv-check, .pk-grid-view .pk-row:focus-within .pk-gv-check { opacity: 1; visibility: visible; pointer-events: auto; transform: translateY(0); } .pk-grid-view .pk-row:hover .pk-gv-check::before { border-color: #818c9b; } .pk-grid-view .pk-row.sel .pk-gv-check::before { background: var(--pk-pri); border-color: var(--pk-pri); } .pk-grid-view .pk-row.sel .pk-gv-check::after { opacity: 1; transform: rotate(45deg) scale(1); } .pk-grid-view .pk-row input[type="checkbox"] { position: absolute; inset: 0; width: 100% !important; height: 100% !important; margin: 0 !important; opacity: 0; cursor: pointer; z-index: 2; } .pk-gv-more { position: absolute; top: 12px; right: 12px; width: 24px; height: 24px; border: none; border-radius: 6px; background: #fff; color: #8a94a4; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; z-index: 5; padding: 0; opacity: 0; visibility: hidden; pointer-events: none; transform: translateY(-2px); transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s ease, color 0.2s; } .pk-grid-view .pk-row:hover .pk-gv-more, .pk-grid-view .pk-row:focus-within .pk-gv-more, .pk-grid-view .pk-row.sel.pk-focused .pk-gv-more, .pk-grid-view .pk-row.pk-sel-single .pk-gv-more { opacity: 1; visibility: visible; pointer-events: auto; transform: translateY(0); } .pk-gv-more:hover { color: var(--pk-pri); } .pk-gv-more svg { width: 16px; height: 16px; flex-shrink: 0; } .pk-gv-cover { position: relative; height: calc(100% - 56px); min-height: 140px; background: transparent; display: flex; align-items: center; justify-content: center; overflow: hidden; border-radius: 12px; } .pk-gv-cover:not(.pk-gv-file) { align-items: flex-end; padding-bottom: 8px; box-sizing: border-box; } .pk-gv-cover.pk-gv-file { width: auto; max-width: 100%; aspect-ratio: 1 / 1; align-self: center; border-radius: 18px; flex: 0 0 auto; isolation: isolate; transform: translateZ(0); backface-visibility: hidden; -webkit-backface-visibility: hidden; will-change: transform; } .pk-gv-cover.pk-gv-file .pk-gv-media-mount { width: 100%; height: 100%; display: block; position: relative; isolation: isolate; transform: translateZ(0); backface-visibility: hidden; -webkit-backface-visibility: hidden; will-change: transform; } .pk-gv-cover.pk-gv-file .pk-gv-media-mount > div { width: 100%; height: 100%; position: relative; transform: translateZ(0); backface-visibility: hidden; -webkit-backface-visibility: hidden; will-change: transform; } .pk-gv-cover.pk-gv-file .pk-gv-media-mount .pk-max-thumb { width: 100% !important; height: 100% !important; max-width: none !important; max-height: none !important; object-fit: cover !important; border-radius: 0 !important; transform: translateZ(0); transform-origin: center center; backface-visibility: hidden; -webkit-backface-visibility: hidden; will-change: transform, opacity; } .pk-gv-cover.pk-gv-has-thumb { background: transparent; } .pk-gv-cover img { max-width: 100%; max-height: 100%; object-fit: cover; display: block; border-radius: 12px; } .pk-gv-cover.pk-gv-has-thumb > img { width: 100%; height: 100%; } .pk-gv-cover.pk-gv-blur > img, .pk-gv-cover.pk-gv-blur .pk-gv-media-mount [data-pk-thumb-ready="1"] > .pk-max-thumb, .pk-gv-cover.pk-gv-blur .pk-gv-media-mount .pk-max-thumb[data-pk-frozen-cover="1"], .pk-gv-cover.pk-gv-blur .pk-gv-folder-preview img[data-pk-id][data-pk-src], .pk-gv-cover.pk-gv-blur .pk-gv-folder-preview img[data-pk-frozen-cover="1"] { filter: blur(8px); transform: scale(1.04); } .pk-gv-cover.pk-gv-file .pk-gv-media-mount { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; } .pk-gv-cover.pk-gv-file .pk-gv-media-mount > div { width: 100%; height: 100%; } .pk-gv-cover .pk-gv-icon { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; } .pk-gv-cover .pk-gv-icon svg, .pk-gv-cover .pk-gv-icon img { width: 108px !important; height: 108px !important; max-width: 108px !important; max-height: 108px !important; object-fit: contain !important; } .pk-gv-cover .pk-gv-icon img { border-radius: 10px; } .pk-gv-folder-preview.pk-gv-folder-has-thumb { background: transparent; isolation: isolate; transform: translateZ(0); } .pk-gv-folder-preview.pk-gv-folder-has-thumb img { width: 100%; height: 100%; display: block; object-fit: cover; border-radius: 0; transform: translateZ(0) scale(1.02); transform-origin: center center; backface-visibility: hidden; will-change: transform; } .pk-gv-folder-shell { position: relative; width: 176px; height: 138px; margin: 12px auto 0; display: flex; align-items: flex-end; justify-content: center; transform: translateY(12px); } .pk-gv-folder-back { position: absolute; top: 19px; left: 0; right: 0; bottom: 0; background: #fbc645; border-radius: 12px; } .pk-gv-folder-tab { position: absolute; top: 6px; left: 15px; width: 62px; height: 23px; background: #fbc645; border-radius: 9px 9px 0 0; } .pk-gv-folder-preview { position: absolute; top: 0; left: 15px; right: 15px; bottom: 23px; border-radius: 10px; overflow: hidden; clip-path: inset(0 round 10px); background: #fff7e6; z-index: 1; display: flex; align-items: center; justify-content: center; isolation: isolate; transform: translateZ(0); } .pk-gv-folder-preview { position: absolute; top: 0; left: 15px; right: 15px; bottom: 23px; border-radius: 10px; overflow: hidden; clip-path: inset(0 round 10px); background: #fff7e6; z-index: 1; display: flex; align-items: center; justify-content: center; isolation: isolate; transform: translateZ(0); backface-visibility: hidden; -webkit-backface-visibility: hidden; will-change: transform; } .pk-gv-folder-fallback { width: 102px !important; height: 102px !important; max-width: 102px !important; max-height: 102px !important; object-fit: contain !important; border-radius: 10px; display: inline-flex; align-items: center; justify-content: center; } .pk-gv-folder-front { position: absolute; left: -1px; right: -1px; bottom: -1px; height: 86px; background: #f9b126; border-radius: 12px; z-index: 2; box-shadow: 0 -2px 12px rgba(249, 177, 38, 0.2); transform: translateZ(0); backface-visibility: hidden; -webkit-backface-visibility: hidden; will-change: transform; } .pk-gv-folder-shell { isolation: isolate; transform: translateZ(0); backface-visibility: hidden; -webkit-backface-visibility: hidden; } .pk-gv-folder-back, .pk-gv-folder-tab { transform: translateZ(0); backface-visibility: hidden; -webkit-backface-visibility: hidden; } .pk-gv-play { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); width: 48px; height: 48px; border-radius: 50%; background: rgba(0,0,0,0.4); display: inline-flex; align-items: center; justify-content: center; pointer-events: none; z-index: 3; } .pk-gv-play svg { width: 28px !important; height: 28px !important; color: #fff; margin-left: 3px; } .pk-gv-fav { position: absolute; right: 14px; bottom: 14px; width: 24px; height: 24px; border-radius: 50%; background: rgba(255,255,255,0.95); color: #f4b400; display: grid; place-items: center; box-shadow: 0 4px 10px rgba(0,0,0,0.1); pointer-events: none; z-index: 3; line-height: 0; } .pk-dark .pk-gv-fav { background: rgba(39,45,54,0.96); box-shadow: 0 6px 14px rgba(0,0,0,0.24); } .pk-gv-fav .pk-star-icon { width: 14px !important; height: 14px !important; display: block !important; margin: 0 !important; padding: 0 !important; transform: none !important; transform-origin: center center !important; } .pk-gv-fav .pk-star-icon svg { width: 14px !important; height: 14px !important; display: block !important; margin: 0 !important; padding: 0 !important; transform: none !important; } .pk-gv-bl { position: absolute; left: 14px; bottom: 14px; width: 24px; height: 24px; border-radius: 50%; background: rgba(255,255,255,0.95); display: grid; place-items: center; box-shadow: 0 4px 10px rgba(0,0,0,0.1); pointer-events: none; z-index: 3; line-height: 0; } .pk-gv-bl svg { width: 14px !important; height: 14px !important; display: block !important; margin: 0 !important; padding: 0 !important; transform: none !important; transform-origin: center center !important; } .pk-dark .pk-gv-bl { background: rgba(39,45,54,0.96); box-shadow: 0 6px 14px rgba(0,0,0,0.24); } .pk-gv-info { display:grid; grid-template-rows:20px 16px; row-gap:2px; align-content:end; align-self:stretch; width:100%; min-width:0; padding:0 2px; height:38px; min-height:38px; box-sizing:border-box; overflow:hidden; text-align:left; } .pk-grid-view .pk-name { display:flex; align-items:center; justify-content:flex-start; align-self:stretch; width:100%; min-width:0; height:20px; overflow:hidden; text-align:left; } .pk-grid-view .pk-name .pk-name-main { display:inline-flex; align-items:center; min-width:0; max-width:100%; overflow:hidden; } .pk-grid-view .pk-name .pk-name-txt { display:block; flex:0 1 auto; width:auto; min-width:0; max-width:100%; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; line-height:20px; font-size:14px; font-weight:500; color:#1a1a1a; word-break:normal; text-align:left; } .pk-grid-view .pk-name .pk-tag-default { flex:0 0 auto; align-self:center; margin-left:6px; margin-top:0 !important; margin-bottom:0 !important; padding:0 6px; min-width:auto; height:16px; border-radius:10px; font-size:11px; line-height:1 !important; white-space:nowrap; display:inline-flex !important; align-items:center !important; justify-content:center !important; text-align:center; box-sizing:border-box; vertical-align:middle; transform:none !important; position:relative; top:-2px; } .pk-gv-meta { display:flex; align-items:center; justify-content:flex-start; align-self:stretch; width:100%; min-width:0; height:16px; gap:0; color:#1a1a1a; font-size:12px; line-height:16px; overflow:hidden; text-align:left; } .pk-gv-meta-item { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:0 1 auto; text-align:left; } .pk-gv-date-wrap { display:flex; align-items:center; min-width:0; flex:0 1 auto; overflow:hidden; gap:0; } .pk-gv-date { flex:0 1 auto; min-width:0; text-align:left; } .pk-gv-size { flex:0 0 auto; max-width:40%; text-align:left; } .pk-gv-dur { flex:0 0 auto; max-width:34%; text-align:left; } .pk-gv-fcount { flex:0 0 auto; max-width:34%; text-align:left; } .pk-gv-meta-sep { display:block; width:3px; height:3px; margin:0 6px; border-radius:50%; background:currentColor; opacity:0.5; flex:0 0 auto; align-self:center; } .pk-gv-date, .pk-gv-size, .pk-gv-dur, .pk-gv-fcount { color: inherit; opacity: 1; } .pk-history-grid-meta .pk-history-grid-pct { flex:0 0 auto; max-width:28%; } .pk-history-grid-progress { position:absolute; left:0; right:0; bottom:0; height:4px; background:rgba(0,0,0,0.16); overflow:hidden; pointer-events:none; z-index:4; } .pk-history-grid-progress-bar { height:100%; width:0; background:var(--pk-pri); } .pk-dark .pk-history-grid-progress { background:rgba(255,255,255,0.22); } .pk-dark .pk-grid-view .pk-row { background: rgba(39,45,54,0.96); border-color: transparent; box-shadow: 0 12px 26px rgba(0,0,0,0.22); outline: none; background-clip: padding-box; } .pk-dark .pk-grid-view .pk-row:hover { background: rgba(45,52,62,0.98); border-color: transparent; outline: none; } .pk-dark .pk-grid-view .pk-row.sel { background: rgba(34,60,96,0.9); border-color: rgba(92,169,255,0.42); box-shadow: 0 12px 28px rgba(18,44,82,0.32); outline: none; background-clip: padding-box; } .pk-dark .pk-grid-view .pk-row.sel:not(.pk-sel-single), .pk-dark .pk-grid-view .pk-row.sel:not(.pk-sel-single):hover, .pk-dark .pk-grid-view .pk-row.sel:not(.pk-sel-single).pk-focused { transition: none !important; animation: none !important; } .pk-dark .pk-grid-view .pk-row.pk-sel-single, .pk-dark .pk-grid-view .pk-row.pk-sel-single:hover, .pk-dark .pk-grid-view .pk-row.pk-sel-single.pk-focused { transition: none !important; animation: none !important; } .pk-dark .pk-gv-check::before { background: rgba(37,42,50,0.92); border-color: rgba(124,137,156,0.9); } .pk-dark .pk-gv-more { background: rgba(34,40,48,0.94); color: #c0c7d2; box-shadow: 0 6px 14px rgba(0,0,0,0.24); } .pk-dark .pk-gv-more:hover { background: rgba(46,54,64,0.98); color: #fff; } .pk-dark .pk-gv-cover.pk-gv-file { background: transparent; } .pk-dark .pk-gv-cover.pk-gv-file.pk-gv-has-thumb:has(.pk-gv-media-mount > [data-pk-thumb-ready="1"]) { background: linear-gradient(180deg,#313844 0%,#2a313b 100%); } .pk-dark .pk-gv-cover.pk-gv-folder { background: transparent; } .pk-dark .pk-gv-folder-preview { background: #fff7e6; } .pk-dark .pk-grid-view .pk-name .pk-name-txt, .pk-dark .pk-gv-meta, .pk-dark .pk-gv-meta-item, .pk-dark .pk-gv-date, .pk-dark .pk-gv-size { color:#fff; } .pk-modal::-webkit-scrollbar, .pk-vp::-webkit-scrollbar, .pk-prev-list::-webkit-scrollbar, .pk-scroll::-webkit-scrollbar, #pk-rn-vp::-webkit-scrollbar, .pk-bl-area::-webkit-scrollbar, textarea::-webkit-scrollbar, .pk-sub-pane::-webkit-scrollbar, #pk_sub_search_list::-webkit-scrollbar, .pk-p-pop::-webkit-scrollbar, .pk-share-modal::-webkit-scrollbar { width: 6px; height: 6px; } .pk-no-scrollbar::-webkit-scrollbar { display: none !important; } .pk-no-scrollbar { -ms-overflow-style: none !important; scrollbar-width: none !important; } .pk-vp::-webkit-scrollbar-track, .pk-modal::-webkit-scrollbar-track, .pk-prev-list::-webkit-scrollbar-track, .pk-scroll::-webkit-scrollbar-track, #pk-rn-vp::-webkit-scrollbar-track, .pk-bl-area::-webkit-scrollbar-track, textarea::-webkit-scrollbar-track, .pk-sub-pane::-webkit-scrollbar-track, #pk_sub_search_list::-webkit-scrollbar-track, .pk-p-pop::-webkit-scrollbar-track { background: var(--pk-sb-bg); } .pk-vp::-webkit-scrollbar-thumb, .pk-modal::-webkit-scrollbar-thumb, .pk-prev-list::-webkit-scrollbar-thumb, .pk-scroll::-webkit-scrollbar-thumb, #pk-rn-vp::-webkit-scrollbar-thumb, .pk-bl-area::-webkit-scrollbar-thumb, textarea::-webkit-scrollbar-thumb, .pk-sub-pane::-webkit-scrollbar-thumb, #pk_sub_search_list::-webkit-scrollbar-thumb, .pk-p-pop::-webkit-scrollbar-thumb { background: var(--pk-sb-th); border-radius: 3px; } .pk-vp::-webkit-scrollbar-thumb:hover, .pk-modal::-webkit-scrollbar-thumb:hover, .pk-prev-list::-webkit-scrollbar-thumb:hover, .pk-scroll::-webkit-scrollbar-thumb:hover { background: var(--pk-sb-hov); } ::-webkit-scrollbar { cursor: default; } .pk-vp { flex: 1; overflow-y: auto; position: relative; background: var(--pk-bg); scrollbar-gutter: stable; } .pk-in { position: absolute; width: 100%; top: 0; } .pk-row { height: 40px; border: 1px solid transparent; cursor: default; padding: 0 16px; border-radius: 4px; } .pk-row:hover { background: var(--pk-hl); } .pk-row.sel { background: var(--pk-sel-bg); border: 1px solid transparent; } .pk-row.sel.pk-focused { border: 1px solid var(--pk-pri); border-radius: 4px; } .pk-grid-view .pk-row.sel.pk-focused { border-color: var(--pk-pri); box-shadow: 0 0 0 1px var(--pk-pri); border-radius: 16px; } .pk-name { display: flex; align-items: center; overflow: visible; min-width: 0; cursor: default; } .pk-name .pk-name-txt { transition: color 0.1s; border-bottom: 1px solid transparent; } .pk-name svg { flex-shrink: 0; margin-right: 8px; cursor: default; } .pk-win:not(.pk-mode-trash) .pk-name .pk-name-txt { cursor: pointer; } .pk-win:not(.pk-mode-trash) .pk-name .pk-name-txt:hover { color: var(--pk-pri); } .pk-tag-default { cursor: default !important; color: #999 !important; border: 1px solid #ccc !important; font-weight: normal !important; } .pk-tag-default:hover { color: #999 !important; } .pk-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 1 auto; line-height: 1.5; padding: 2px 0; margin-top: -2px; } .pk-group-hd { display: flex; background: var(--pk-gh); color: var(--pk-gh-fg); font-weight: bold; align-items: center; padding: 0 16px; border-top: 4px solid var(--pk-bg) !important; border-bottom: 4px solid var(--pk-bg) !important; background-clip: padding-box; height: 40px !important; box-sizing: border-box; margin-top: 0 !important; } .pk-group-hd .pk-tag { margin-left: auto; background: #666; color: #fff; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; border: 1px solid #555; } .pk-group-hd .pk-cnt { margin-left: 10px; color: var(--pk-fg); font-size: 12px; opacity: 0.9; } .pk-group-hd .pk-name { height:100%; align-items:center !important; } .pk-group-hd .pk-name .pk-name-txt, .pk-group-hd .pk-name span { display:flex !important; align-items:center !important; height:100% !important; line-height:1.2 !important; padding:0 !important; margin-top:0 !important; } .pk-loading-ov { position: absolute; inset: 0; background: rgba(var(--pk-bg-rgb), 0.75); z-index: 999; display: none; flex-direction: column; align-items: center; justify-content: center; color: var(--pk-fg); gap: 28px; backdrop-filter: blur(10px) saturate(180%); -webkit-backdrop-filter: blur(10px) saturate(180%); transition: all 0.3s ease; } .pk-spin-lg { width: 56px; height: 56px; border: 4px solid rgba(136, 136, 136, 0.25); border-top-color: var(--pk-pri); border-radius: 50%; position: relative; animation: pk-ultra-spin 1s linear infinite; transform: translateZ(0); } .pk-loading-txt { font-size: 15px; font-weight: 600; text-align: center; white-space: pre-line; line-height: 1.6; letter-spacing: 1px; } @keyframes pk-ultra-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes pk-text-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.6; transform: scale(0.98); } } .pk-stop-btn { display: flex !important; align-items: center !important; justify-content: center !important; gap: 6px; padding: 8px 24px; background: #d93025; color: white; border: none; border-radius: 20px; font-size: 14px; cursor: pointer; font-weight: bold; box-shadow: 0 4px 10px rgba(217, 48, 37, 0.3); transition: transform 0.1s; } .pk-stop-btn svg { display: block; } .pk-stop-btn:hover { background: #b02a20; transform: scale(1.05); } .pk-stop-btn:active { transform: scale(0.95); } .pk-ft { height: 48px; border-top: 1px solid var(--pk-bd); background: var(--pk-bg); display: flex; align-items: center; padding: 0 16px; justify-content: space-between; font-size: 13px; } .pk-stat { color: var(--pk-fg); font-size: 13px; } .pk-grp { display: flex; gap: 8px; } .pk-pop { position: fixed; pointer-events: none; z-index: 2147483647 !important; background: #000; border: 1px solid #333; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); border-radius: 6px; display: none; overflow: hidden; } .pk-pop img { display: block; max-width: 320px; max-height: 240px; object-fit: contain; } .pk-ctx { position: fixed; z-index: 2147483647 !important; background: var(--pk-bg); border: 1px solid var(--pk-bd); border-radius: 6px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); min-width: 150px; padding: 4px 0; display: none; } .pk-ctx-item { padding: 8px 16px; font-size: 13px; cursor: pointer; display: flex; align-items: center; gap: 8px; color: var(--pk-fg); } .pk-ctx-item:hover { background: var(--pk-hl); } .pk-ctx-sep { height: 1px; background: var(--pk-bd); margin: 4px 0; } .pk-modal-ov { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.5); z-index: 10001; display: flex; align-items: center; justify-content: center; overscroll-behavior: none; overflow: hidden; padding: 20px; box-sizing: border-box; } .pk-modal { position: relative; background: var(--pk-bg); color: var(--pk-fg); padding: 25px; border-radius: 12px; width: 500px; max-height: 100%; overflow: hidden !important; display: flex; flex-direction: column; gap: 15px; border: 1px solid var(--pk-bd); box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4); overscroll-behavior: none; margin: auto; flex-shrink: 1; } .pk-modal h3 { margin: 0 0 5px 0; font-size: 16px; padding-bottom: 10px; padding-right: 40px; color: var(--pk-fg); } .pk-modal-close { position: absolute; top: 15px; right: 15px; cursor: pointer; color: var(--pk-icon-c); width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; border-radius: 6px; transition: background 0.1s, color: 0.1s; } .pk-modal-close:hover { background: var(--pk-hl); color: var(--pk-fg); } .pk-field { display: flex; flex-direction: column; gap: 5px; font-size: 13px; } .pk-field input, .pk-field select { padding: 6px; border: 1px solid var(--pk-bd); border-radius: 4px; background: var(--pk-bg); color: var(--pk-fg); } .pk-field select { appearance: none; -webkit-appearance: none; -moz-appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; background-size: 16px; padding-right: 32px !important; cursor: pointer; } .pk-modal-act { display: flex; justify-content: flex-end; gap: 10px; margin-top: 10px; } .pk-credit { font-size: 11px; color: #888; text-align: center; margin-top: 20px; border-top: 1px solid var(--pk-bd); padding-top: 10px; } .pk-credit a { color: #888; text-decoration: none; } .pk-credit a:hover { text-decoration: underline; } .pk-prev-list { flex: 1; overflow-y: auto; border: 1px solid var(--pk-bd); max-height: 300px; } .pk-prev-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; padding: 5px 10px; border-bottom: 1px solid var(--pk-bd); font-size: 12px; } .pk-prev-row:nth-child(odd) { background: var(--pk-hl); } .pk-sep { width: 1px; height: 16px; background: var(--pk-bd); margin: 0 4px; display: none; flex-shrink: 0; } .pk-sep-sm { width: 1px; height: 16px; background: var(--pk-bd); margin: 0 8px; flex-shrink: 0; } #pk-refresh, #pk-trash-refresh { width: auto !important; justify-content: center !important; flex-shrink: 0; } #pk-refresh svg, #pk-trash-refresh svg { width: 16px !important; height: 16px !important; } .pk-grid-hd input[type="checkbox"], .pk-row input[type="checkbox"] { width: 18px; height: 18px; cursor: pointer; margin: 0 auto !important; flex-shrink: 0; accent-color: var(--pk-pri); box-sizing: content-box; transform: translateZ(0); display: block; position: relative; } .pk-player-box { position: relative; width: 100%; height: 100%; background: #000; display: flex; flex-direction: column; user-select: none; overflow: hidden; } .pk-player-video { width: 100%; height: 100%; object-fit: contain; outline: none; transform: translateZ(0); backface-visibility: hidden; image-rendering: -webkit-optimize-contrast; -webkit-font-smoothing: antialiased; } .pk-player-top { position: absolute; top: 0; left: 0; right: 0; height: 64px; background: linear-gradient(to bottom, rgba(0, 0, 0, 0.7) 0%, rgba(0, 0, 0, 0.3) 60%, transparent 100%); display: flex; align-items: center; justify-content: space-between; padding: 0 24px; z-index: 100 !important; opacity: 1; transition: opacity 0.3s; pointer-events: auto; } .pk-player-box.ui-hidden .pk-player-top { opacity: 0 !important; pointer-events: none !important; } .pk-player-title { color: #fff; font-size: 16px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); letter-spacing: 0.5px; } .pk-player-controls { position: absolute; bottom: 0; left: 0; right: 0; height: 64px; background: linear-gradient(to top, rgba(0, 0, 0, 0.75) 0%, rgba(0, 0, 0, 0.35) 60%, transparent 100%); display: flex; align-items: center; padding: 0 16px; z-index: 80 !important; gap: 4px; opacity: 0; transition: opacity 0.3s; } .pk-player-box:hover .pk-player-controls, .pk-player-box.paused .pk-player-controls { opacity: 1; } .pk-player-progress-container { position: absolute; bottom: 64px; left: 12px; right: 12px; height: 14px; cursor: pointer; z-index: 11; display: flex; align-items: center; } .pk-player-progress-bg { width: 100%; height: 4px; background: rgba(255, 255, 255, 0.25); position: relative; border-radius: 2px; transition: height 0.1s, transform 0.1s; backdrop-filter: blur(2px); } .pk-player-progress-container:hover .pk-player-progress-bg { height: 6px; transform: scaleY(1.1); } .pk-player-progress-filled { height: 100%; background: var(--pk-pri); width: 0; position: relative; border-radius: 2px; transition: none !important; will-change: width; } .pk-player-progress-thumb { position: absolute; right: -7px; top: 50%; transform: translateY(-50%) scale(0); width: 14px; height: 14px; border-radius: 50%; background: #fff; box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); transition: transform 0.15s cubic-bezier(0.175, 0.885, 0.32, 1.275); } .pk-player-progress-container:hover .pk-player-progress-thumb { transform: translateY(-50%) scale(1); } .pk-share-preview-limit-marker { position: absolute; top: 50%; width: 2px; height: 14px; transform: translate(-50%, -50%); background: rgba(255,255,255,0.95); border-radius: 2px; box-shadow: 0 0 6px rgba(0,0,0,0.65); z-index: 4; pointer-events: auto; } .pk-share-preview-limit-label { position: absolute; left: 50%; bottom: 15px; transform: translateX(-50%); white-space: nowrap; background: rgba(0,0,0,0.65); color: #fff; font-size: 12px; line-height: 1; padding: 5px 8px; border-radius: 12px; opacity: 0; pointer-events: none; transition: opacity .15s ease; text-shadow: none; } #pk_p_prog_area:hover .pk-share-preview-limit-label, .pk-share-preview-limit-marker:hover .pk-share-preview-limit-label { opacity: 1; } .pk-p-btn { color: #eee; cursor: pointer; display: flex; align-items: center; justify-content: center; width: 50px; height: 40px; border-radius: 4px; transition: all 0.2s ease; position: relative; flex-shrink: 0; } .pk-p-btn:hover { color: var(--pk-pri); background: transparent; transform: scale(1.05); } .pk-p-btn:active { color: var(--pk-pri); background: transparent; transform: scale(0.95); } #pk_p_play, #pk_p_vol, #pk_p_close { display: inline-flex !important; width: 40px !important; height: 40px !important; border-radius: 50% !important; margin: 0 4px !important; padding: 0 !important; box-sizing: border-box !important; justify-content: center !important; align-items: center !important; } #pk_p_play svg, #pk_p_vol svg, #pk_p_close svg { margin: 0 !important; padding: 0 !important; } #pk_p_play:hover, #pk_p_vol:hover, #pk_p_close:hover { color: #fff !important; filter: brightness(1.5); background: rgba(255, 255, 255, 0.15) !important; transform: none !important; } .pk-p-btn svg { width: 24px; height: 24px; fill: currentColor; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3)); } #pk_sub_trigger .pk-p-btn svg { width: 21px; height: 21px; } #pk_p_full svg { width: 28px; height: 28px; } .pk-p-time { color: #ddd; font-size: 13px; font-family: "Segoe UI", Roboto, monospace; min-width: 90px; text-align: center; font-variant-numeric: tabular-nums; margin: 0 5px; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); } .pk-p-menu-con { position: relative; display: flex; align-items: center; justify-content: center; height: 40px; cursor: pointer; font-size: 15px; color: #ddd; font-weight: 600; padding: 0; min-width: 50px; transition: color 0.2s; border-radius: 4px; } #pk_sub_trigger .pk-p-btn { width: 50px; height: 40px; } .pk-p-menu-con:hover { color: var(--pk-pri); background: transparent; } .pk-p-pop { position: absolute; bottom: 45px; left: 50%; transform: translateX(-50%); background: rgba(20, 20, 20, 0.9); border-radius: 8px; padding: 6px 0; display: none; flex-direction: column-reverse; min-width: 100px; text-align: center; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5); border: 1px solid rgba(255, 255, 255, 0.1); z-index: 20; backdrop-filter: blur(10px); } .pk-p-pop::after { content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 45px; background: transparent; } .pk-p-menu-con:hover .pk-p-pop { display: flex; animation: pkFadeIn 0.2s ease; } @keyframes pkFadeIn { from { opacity: 0; transform: translate(-50%, 10px); } to { opacity: 1; transform: translate(-50%, 0); } } .pk-p-item { padding: 8px 16px; color: #ccc; cursor: pointer; font-size: 13px; position: relative; z-index: 2; transition: background 0.1s; text-align: center; display: flex; align-items: center; justify-content: center; } .pk-p-item:hover { background: rgba(255, 255, 255, 0.1); color: #fff; } .pk-p-item.active { color: var(--pk-pri); font-weight: bold; } .pk-p-vol-wrap { display: flex; align-items: center; height: 100%; gap: 0px; margin-right: 5px; } .pk-p-vol-slider { -webkit-appearance: none; width: 70px; height: 4px; background: rgba(255, 255, 255, 0.3); border-radius: 2px; outline: none; cursor: pointer; transition: height 0.1s; } .pk-p-vol-slider:hover { height: 6px; } .pk-p-vol-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 12px; height: 12px; border-radius: 50%; background: #fff; cursor: pointer; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); transition: transform 0.1s; } .pk-p-vol-slider::-webkit-slider-thumb:hover { transform: scale(1.2); } .pk-p-loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); pointer-events: none; display: none; } .pk-player-box.buffering:not(.pk-is-seeking) .pk-p-loading { display: block; } .pk-player-box.pk-is-seeking .pk-p-loading { display: none !important; } .pk-p-center-play { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) translateZ(0); width: 72px; height: 72px; background: rgba(0, 0, 0, 0.35); border-radius: 50%; display: none; align-items: center; justify-content: center; z-index: 36; pointer-events: none; border: 1px solid rgba(255, 255, 255, 0.25); box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); backface-visibility: hidden; will-change: transform, opacity; } .pk-p-center-play svg { width: 36px; height: 36px; fill: #fff; margin-left: 4px; filter: drop-shadow(0 0 8px rgba(0, 0, 0, 0.3)); } .pk-player-box.paused.pk-v-started:not(.buffering):not(.pk-is-seeking) .pk-p-center-play { display: flex !important; animation: pkPlayPop 0.35s cubic-bezier(0.2, 0, 0.2, 1) forwards; } .pk-player-box.buffering .pk-p-center-play { display: none !important; opacity: 0 !important; } .pk-player-box.pk-is-seeking .pk-p-center-play { display: none !important; opacity: 0 !important; } @keyframes pkPlayPop { from { opacity: 0; transform: translate(-50%, -50%) scale(0.8) translateZ(0); } to { opacity: 1; transform: translate(-50%, -50%) scale(1) translateZ(0); } } .pk-p-seek-indicator { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) translateZ(0); height: 54px; min-width: 190px; background: rgba(0, 0, 0, 0.65); color: #fff; padding: 0 22px; border-radius: 12px; font-size: 22px; font-weight: 300; font-family: "Inter", "Segoe UI", "Roboto", "Helvetica Neue", "Arial", sans-serif; z-index: 50; display: none; align-items: center; justify-content: center; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.15); box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4); font-variant-numeric: tabular-nums; pointer-events: none; white-space: nowrap; letter-spacing: 0px; } body.pk-dragging { cursor: pointer !important; user-select: none !important; -webkit-user-select: none !important; } .pk-player-box.pk-is-seeking .pk-player-progress-thumb { transform: translateY(-50%) scale(1) !important; opacity: 1 !important; transition: none !important; } .pk-player-box.pk-is-seeking .pk-player-progress-bg { height: 6px !important; transform: scaleY(1.1) !important; transition: none !important; } .pk-player-box.pk-is-seeking .pk-player-progress-filled { transition: none !important; will-change: width; } .pk-p-resume-toast { position: absolute; bottom: 85px; left: 24px; background: rgba(28, 28, 28, 0.95); color: #fff; padding: 8px 16px; border-radius: 99px; font-size: 13px; display: flex; align-items: center; gap: 8px; z-index: 100; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); border: 1px solid rgba(255, 255, 255, 0.1); animation: pkFadeInUp 0.2s ease; transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.3s ease; } .pk-p-plist-ov { position: absolute; top: 100%; left: 0; right: 0; z-index: 25; display: flex; flex-direction: column; pointer-events: none; } .pk-p-plist-strip { height: 84px; background: rgba(20, 20, 20, 0.9); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); display: flex; align-items: center; position: relative; border-top: none; pointer-events: auto; } .pk-p-plist-tab { position: absolute !important; bottom: 100%; left: 50%; transform: translateX(-50%); height: 25px; pointer-events: auto !important; z-index: 70; margin-bottom: -1px; } .pk-p-plist-tab { align-self: center; position: relative; z-index: 26; color: rgba(255, 255, 255, 0.8); padding: 0 20px; height: 30px; font-size: 12px; font-weight: 600; cursor: pointer; border: none; display: flex; align-items: center; justify-content: center; gap: 4px; background: transparent; margin-bottom: -1px; letter-spacing: 0.5px; transition: opacity 0.3s ease; opacity: 0; } .pk-p-plist-tab:hover, .pk-player-box.plist-active .pk-p-plist-tab { opacity: 1; } .pk-p-plist-tab:hover, .pk-player-box.plist-active .pk-p-plist-tab, .pk-img-box.plist-active .pk-p-plist-tab { opacity: 1; } .pk-p-plist-tab::before { content: ''; position: absolute; inset: 0; z-index: -1; left: -50px; right: -50px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='20' viewBox='0 0 100 20' preserveAspectRatio='none'%3E%3Cpath d='M0 20 C 25 20, 25 0, 40 0 H 60 C 75 0, 75 20, 100 20 Z' fill='rgba(20, 20, 20, 0.9)'/%3E%3Cpath d='M0 20 C 25 20, 25 0, 40 0 H 60 C 75 0, 75 20, 100 20' fill='none' stroke='rgba(255,255,255,0.1)' stroke-width='1' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E"); background-size: 100% 100%; background-repeat: no-repeat; backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); transform: translateZ(0); backface-visibility: hidden; -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='20' viewBox='0 0 100 20' preserveAspectRatio='none'%3E%3Cpath d='M0 20 C 25 20, 25 0, 40 0 H 60 C 75 0, 75 20, 100 20 Z' fill='black'/%3E%3C/svg%3E"); mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='20' viewBox='0 0 100 20' preserveAspectRatio='none'%3E%3Cpath d='M0 20 C 25 20, 25 0, 40 0 H 60 C 75 0, 75 20, 100 20 Z' fill='black'/%3E%3C/svg%3E"); -webkit-mask-size: 100% 100%; mask-size: 100% 100%; } #pk_p_box:fullscreen, #pk_p_box:-webkit-full-screen, #pk_p_box:-moz-full-screen { width: 100vw !important; height: 100vh !important; top: 0 !important; left: 0 !important; transform: none !important; margin: 0 !important; border-radius: 0 !important; overflow: hidden !important; } #pk_p_box:fullscreen #pk_video, #pk_p_box:-webkit-full-screen #pk_video, #pk_p_box.full #pk_video, #pk_p_box:fullscreen #pk_p_poster, #pk_p_box:-webkit-full-screen #pk_p_poster, #pk_p_box.full #pk_p_poster { height: 100% !important; bottom: auto !important; top: 0 !important; transform: translateZ(0); } #pk_p_box:fullscreen.plist-active #pk_video, #pk_p_box:-webkit-full-screen.plist-active #pk_video, #pk_p_box.full.plist-active #pk_video, #pk_p_box:fullscreen.plist-active #pk_p_poster, #pk_p_box:-webkit-full-screen.plist-active #pk_p_poster, #pk_p_box.full.plist-active #pk_p_poster { height: calc(100% - 84px) !important; } #pk_p_box:fullscreen.plist-active .pk-p-side-nav, #pk_p_box:fullscreen.plist-active .pk-p-center-play, #pk_p_box:fullscreen.plist-active .pk-p-seek-indicator, #pk_p_box:fullscreen.plist-active .pk-p-loading, #pk_p_box:-webkit-full-screen.plist-active .pk-p-side-nav, #pk_p_box:-webkit-full-screen.plist-active .pk-p-center-play, #pk_p_box:-webkit-full-screen.plist-active .pk-p-seek-indicator, #pk_p_box:-webkit-full-screen.plist-active .pk-p-loading { top: calc(50% - 42px) !important; } #pk_p_box:fullscreen #pk_p_plist, #pk_p_box:-webkit-full-screen #pk_p_plist { top: auto !important; bottom: 0 !important; transform: translateY(100%) translateZ(0); backface-visibility: hidden; perspective: 1000px; transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1); } #pk_p_box:fullscreen .pk-p-plist-tab, #pk_p_box:-webkit-full-screen .pk-p-plist-tab { bottom: auto !important; top: 0 !important; transform: translateY(-100%) translateZ(0) !important; margin-top: 1px !important; margin-bottom: 0 !important; backface-visibility: hidden; z-index: 100 !important; } #pk_p_box:fullscreen.plist-active #pk_p_plist, #pk_p_box:-webkit-full-screen.plist-active #pk_p_plist { transform: translateY(0); } #pk_p_box:fullscreen.plist-active .pk-player-controls, #pk_p_box:-webkit-full-screen.plist-active .pk-player-controls { bottom: 84px !important; } #pk_p_box:fullscreen.plist-active .pk-p-prog-wrap, #pk_p_box:-webkit-full-screen.plist-active .pk-p-prog-wrap { bottom: 148px !important; } #pk_p_box:fullscreen.plist-active .pk-p-resume-toast, #pk_p_box:-webkit-full-screen.plist-active .pk-p-resume-toast { bottom: 169px !important; } #pk_p_box:fullscreen #pk_video, #pk_p_box:-webkit-full-screen #pk_video, #pk_p_box.full #pk_video, #pk_p_box:fullscreen #pk_p_poster, #pk_p_box:-webkit-full-screen #pk_p_poster, #pk_p_box.full #pk_p_poster { transition: height 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease !important; } #pk_p_box:fullscreen .pk-player-controls, #pk_p_box:-webkit-full-screen .pk-player-controls, #pk_p_box:fullscreen .pk-p-prog-wrap, #pk_p_box:-webkit-full-screen .pk-p-prog-wrap, #pk_p_box:fullscreen .pk-p-resume-toast, #pk_p_box:-webkit-full-screen .pk-p-resume-toast { transition: bottom 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease !important; } #pk_p_box:fullscreen .pk-p-side-nav, #pk_p_box:fullscreen .pk-p-center-play, #pk_p_box:fullscreen .pk-p-seek-indicator, #pk_p_box:fullscreen .pk-p-loading, #pk_p_box:-webkit-full-screen .pk-p-side-nav, #pk_p_box:-webkit-full-screen .pk-p-center-play, #pk_p_box:-webkit-full-screen .pk-p-seek-indicator, #pk_p_box:-webkit-full-screen .pk-p-loading { transition: top 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease, transform 0.2s cubic-bezier(0.4, 0, 0.2, 1) !important; } .pk-p-plist-tab:hover { color: rgba(255, 255, 255, 0.8); } .pk-p-plist-tab:hover::before { opacity: 1; filter: none; } .pk-p-plist-tab svg { transition: transform 0.3s; } .pk-p-plist-ov.open .pk-p-plist-tab svg { transform: rotate(180deg); } .pk-p-plist-strip { height: 110px; background: rgba(20, 20, 20, 0.9); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); display: flex; align-items: center; position: relative; border-top: 1px solid rgba(255, 255, 255, 0.1); } .pk-p-plist-scroll { flex: 1; display: flex; overflow-x: auto; gap: 2px; padding: 0 50px; height: 100%; align-items: center; } .pk-p-plist-scroll::-webkit-scrollbar { display: none; } .pk-p-plist-item { flex-shrink: 0; width: 140px; height: 80px; background: #333; cursor: pointer; position: relative; overflow: hidden; border: 2px solid transparent; border-radius: 4px; will-change: opacity, transform; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .pk-p-plist-item.active { border-color: var(--pk-pri); box-shadow: 0 0 10px var(--pk-pri); } .pk-p-plist-ph { position:absolute; inset:0; z-index:1; display:flex; align-items:center; justify-content:center; overflow:hidden; background:#3a3a3a; } .pk-p-plist-ph img, .pk-p-plist-ph svg { width:100% !important; height:100% !important; max-width:none !important; max-height:none !important; display:block; border-radius:0 !important; } .pk-p-plist-ph img { object-fit:cover !important; } .pk-p-plist-ph svg { object-fit:fill !important; } .pk-p-plist-item > img { position:relative; z-index:2; width:100%; height:100%; object-fit:cover; opacity:1; transition:opacity 0.3s ease-in-out; display:block; } .pk-p-plist-item img:error { opacity:0 !important; } .pk-p-plist-nav { position: absolute; top: 0; bottom: 0; width: 50px; background: transparent !important; color: rgba(255, 255, 255, 0.7); cursor: pointer; display: flex; align-items: center; justify-content: center; z-index: 35; transition: all 0.2s; border: none; } .pk-p-plist-nav:hover { background: rgba(255, 255, 255, 0.15) !important; color: #fff; } .pk-p-plist-nav.L { left: 0; } .pk-p-plist-nav.R { right: 0; } .pk-p-plist-nav svg { width: 28px; height: 28px; stroke-width: 3; } .pk-p-plist-tip { position: fixed; background: rgba(0, 0, 0, 0.9); color: #fff; padding: 8px 12px; border-radius: 6px; font-size: 12px; pointer-events: none; z-index: 2147483647; max-width: 340px; line-height: 1.4; display: none; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); border: 1px solid rgba(255, 255, 255, 0.1); text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .pk-p-resume-btn { color: #4aa1ff; cursor: pointer; font-weight: bold; text-decoration: none; } .pk-p-resume-btn:hover { text-decoration: underline; } .pk-p-resume-close { cursor: pointer; color: #888; margin-left: 4px; display: flex; align-items: center; } #pk-audio-ov { position:fixed; inset:0; z-index:2147483641; background:rgba(0,0,0,.78); backdrop-filter:blur(8px); display:flex; align-items:center; justify-content:center; outline:none; padding:18px; box-sizing:border-box; font-family:inherit; } #pk-audio-ov .pk-audio-box { width:min(680px,100%); height:80vh; max-height:calc(100vh - 36px); background:rgba(var(--pk-bg-rgb),.94); color:var(--pk-fg); border:1px solid var(--pk-bd); border-radius:12px; box-shadow:0 25px 60px rgba(0,0,0,.45); overflow:hidden; display:flex; flex-direction:row; min-width:0; position:relative; } #pk-audio-ov .pk-audio-box.pk-audio-playlist-open { width:min(1000px,100%); } #pk-audio-ov .pk-audio-player-main { flex:1 1 680px; min-width:0; min-height:0; height:100%; display:flex; flex-direction:column; } #pk-audio-ov .pk-audio-top { height:56px; display:flex; align-items:center; justify-content:space-between; gap:12px; padding:0 16px 0 20px; border-bottom:1px solid var(--pk-bd); background:rgba(var(--pk-bg-rgb),.88); } #pk-audio-ov .pk-audio-title { font-size:15px; font-weight:800; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } #pk-audio-ov .pk-audio-actions { display:flex; align-items:center; justify-content:flex-end; gap:6px; flex-shrink:0; } #pk-audio-ov .pk-audio-main { flex:1 1 auto; justify-content:center; padding:28px 28px 24px; display:flex; flex-direction:column; align-items:center; text-align:center; gap:12px; min-width:0; min-height:0; } #pk-audio-ov .pk-audio-icon { width:188px; height:188px; border-radius:30px; display:flex; align-items:center; justify-content:center; background:var(--pk-hl); color:var(--pk-pri); box-shadow:inset 0 0 0 1px var(--pk-bd); overflow:hidden; flex-shrink:0; } #pk-audio-ov .pk-audio-icon svg { width:138px !important; height:138px !important; } #pk-audio-ov .pk-audio-icon img { width:100%; height:100%; object-fit:cover; display:block; } #pk-audio-ov .pk-audio-name { width:100%; margin-top:14px; font-size:18px; font-weight:800; line-height:1.45; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } #pk-audio-ov .pk-audio-meta { display:flex; flex-wrap:wrap; justify-content:center; gap:8px; font-size:12px; opacity:.76; } #pk-audio-ov .pk-audio-chip { border:1px solid var(--pk-bd); background:var(--pk-hl); border-radius:999px; padding:4px 10px; max-width:220px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } #pk-audio-ov .pk-audio-status { min-height:20px; font-size:13px; color:var(--pk-pri); font-weight:700; } #pk-audio-ov .pk-audio-status.err { color:#ff6b6b; } #pk-audio-ov .pk-audio-progress-row { display:grid; grid-template-columns:56px 1fr 56px; gap:12px; align-items:center; padding:0 24px 18px; } #pk-audio-ov .pk-audio-time { font-size:12px; font-variant-numeric:tabular-nums; opacity:.78; text-align:center; } #pk-audio-ov .pk-audio-progress-wrap { position:relative; display:flex; align-items:center; width:100%; min-width:0; } #pk-audio-ov .pk-audio-progress-tip { position:absolute; left:0; bottom:calc(100% + 10px); transform:translate(-50%,4px); padding:4px 8px; border-radius:6px; background:var(--pk-bg); color:var(--pk-fg); border:1px solid var(--pk-bd); box-shadow:0 6px 16px rgba(0,0,0,.28); font-size:12px; line-height:1.35; white-space:nowrap; opacity:0; pointer-events:none; transition:opacity .14s ease,transform .14s ease; z-index:20; font-variant-numeric:tabular-nums; } #pk-audio-ov .pk-audio-progress-tip::after { content:""; position:absolute; left:50%; top:100%; transform:translateX(-50%); border:5px solid transparent; border-top-color:var(--pk-bd); } #pk-audio-ov .pk-audio-progress-tip.show { opacity:1; transform:translate(-50%,0); } #pk-audio-ov .pk-audio-range { -webkit-appearance:none; appearance:none; width:100%; height:5px; border-radius:999px; background:linear-gradient(to right,var(--pk-pri) 0%,var(--pk-pri) var(--pk-audio-pct,0%),rgba(127,127,127,.32) var(--pk-audio-pct,0%),rgba(127,127,127,.32) 100%); outline:none; cursor:pointer; } #pk-audio-ov .pk-audio-range::-webkit-slider-thumb { -webkit-appearance:none; width:15px; height:15px; border-radius:50%; background:#fff; border:2px solid var(--pk-pri); box-shadow:0 2px 8px rgba(0,0,0,.25); } #pk-audio-ov .pk-audio-range::-moz-range-thumb { width:15px; height:15px; border-radius:50%; background:#fff; border:2px solid var(--pk-pri); box-shadow:0 2px 8px rgba(0,0,0,.25); } #pk-audio-ov .pk-audio-controls { min-height:66px; display:flex; align-items:center; justify-content:center; gap:12px; padding:12px 18px 18px; border-top:1px solid var(--pk-bd); background:rgba(var(--pk-bg-rgb),.88); } #pk-audio-ov .pk-audio-btn { width:40px; height:40px; border:0; border-radius:50%; background:transparent; color:var(--pk-fg); display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0; transition:background .15s,color .15s,transform .15s; flex-shrink:0; } #pk-audio-ov .pk-audio-btn:hover { background:var(--pk-hl); color:var(--pk-pri); } #pk-audio-ov .pk-audio-btn:active { transform:scale(.96); } #pk-audio-ov .pk-audio-btn.pk-audio-btn-active { background:var(--pk-hl); color:var(--pk-pri); box-shadow:inset 0 0 0 1px var(--pk-bd); } #pk-audio-ov .pk-audio-btn[data-tip] { position:relative; } #pk-audio-ov .pk-audio-btn[data-tip]::after { content:attr(data-tip); position:absolute; left:50%; bottom:calc(100% + 8px); transform:translate(-50%,4px); padding:4px 8px; border-radius:6px; background:var(--pk-bg); color:var(--pk-fg); border:1px solid var(--pk-bd); box-shadow:0 6px 16px rgba(0,0,0,.28); font-size:12px; line-height:1.35; white-space:nowrap; opacity:0; pointer-events:none; transition:opacity .14s ease,transform .14s ease; z-index:20; } #pk-audio-ov .pk-audio-btn[data-tip]::before { content:""; position:absolute; left:50%; bottom:calc(100% + 3px); transform:translateX(-50%); border:5px solid transparent; border-top-color:var(--pk-bd); opacity:0; pointer-events:none; transition:opacity .14s ease; z-index:20; } #pk-audio-ov .pk-audio-btn[data-tip]:hover::after,#pk-audio-ov .pk-audio-btn[data-tip]:focus-visible::after { opacity:1; transform:translate(-50%,0); } #pk-audio-ov .pk-audio-btn[data-tip]:hover::before,#pk-audio-ov .pk-audio-btn[data-tip]:focus-visible::before { opacity:1; } #pk-audio-ov .pk-audio-actions .pk-audio-btn[data-tip]::after { top:calc(100% + 8px); bottom:auto; transform:translate(-50%,-4px); } #pk-audio-ov .pk-audio-actions .pk-audio-btn[data-tip]::before { top:calc(100% + 3px); bottom:auto; border-top-color:transparent; border-bottom-color:var(--pk-bd); } #pk-audio-ov .pk-audio-actions .pk-audio-btn[data-tip]:hover::after,#pk-audio-ov .pk-audio-actions .pk-audio-btn[data-tip]:focus-visible::after { transform:translate(-50%,0); } #pk-audio-ov .pk-audio-btn svg { width:22px; height:22px; fill:currentColor; } #pk-audio-ov #pk_audio_play { width:48px; height:48px; background:var(--pk-pri); color:#fff; } #pk-audio-ov #pk_audio_play:hover { color:#fff; filter:brightness(1.08); } #pk-audio-ov .pk-audio-volume-wrap { display:flex; align-items:center; gap:8px; min-width:128px; } #pk-audio-ov .pk-audio-volume { width:86px; } #pk-audio-ov .pk-audio-playlist-panel { display:flex; flex:0 0 0; width:0; min-width:0; max-width:0; border-left:0; background:rgba(var(--pk-bg-rgb),.9); flex-direction:column; overflow:hidden; opacity:0; visibility:hidden; pointer-events:none; } #pk-audio-ov .pk-audio-playlist-open .pk-audio-playlist-panel { flex:0 0 300px; width:auto; max-width:320px; border-left:1px solid var(--pk-bd); opacity:1; visibility:visible; pointer-events:auto; } #pk-audio-ov .pk-audio-playlist-head { height:56px; flex:0 0 56px; display:flex; align-items:center; justify-content:space-between; gap:10px; padding:0 14px; border-bottom:1px solid var(--pk-bd); font-size:14px; font-weight:800; background:rgba(var(--pk-bg-rgb),.88); } #pk-audio-ov .pk-audio-playlist-head span:last-child { font-size:12px; font-weight:700; opacity:.62; } #pk-audio-ov .pk-audio-playlist-body { flex:1 1 auto; min-height:0; overflow:auto; padding:8px; scrollbar-gutter:stable; } #pk-audio-ov .pk-audio-playlist-body::-webkit-scrollbar { width:6px; height:6px; } #pk-audio-ov .pk-audio-playlist-body::-webkit-scrollbar-track { background:var(--pk-sb-bg); } #pk-audio-ov .pk-audio-playlist-body::-webkit-scrollbar-thumb { background:var(--pk-sb-th); border-radius:3px; } #pk-audio-ov .pk-audio-playlist-body::-webkit-scrollbar-thumb:hover { background:var(--pk-sb-hov); } #pk-audio-ov .pk-audio-playlist-item { width:100%; min-width:0; border:1px solid transparent; border-radius:8px; background:transparent; color:var(--pk-fg); display:grid; grid-template-columns:24px minmax(0,1fr); gap:6px; align-items:center; text-align:left; padding:9px 8px 9px 4px; box-sizing:border-box; cursor:pointer; position:relative; font-family:inherit; } #pk-audio-ov .pk-audio-playlist-item::before { content:""; position:absolute; left:0; top:8px; bottom:8px; width:3px; border-radius:3px; background:transparent; } #pk-audio-ov .pk-audio-playlist-item:hover { background:var(--pk-hl); } #pk-audio-ov .pk-audio-playlist-item-active { background:var(--pk-hl); border-color:var(--pk-bd); color:var(--pk-pri); } #pk-audio-ov .pk-audio-playlist-item-active::before { background:var(--pk-pri); } #pk-audio-ov .pk-audio-playlist-index { font-size:12px; font-weight:800; opacity:.58; font-variant-numeric:tabular-nums; text-align:right; } #pk-audio-ov .pk-audio-playlist-text { min-width:0; display:block; } #pk-audio-ov .pk-audio-playlist-name { min-width:0; display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:13px; font-weight:700; line-height:1.35; } #pk-audio-ov .pk-audio-playlist-meta { min-width:0; display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; opacity:.62; line-height:1.35; margin-top:2px; color:var(--pk-fg); } #pk-audio-ov .pk-audio-playlist-empty { height:120px; display:flex; align-items:center; justify-content:center; color:var(--pk-fg); opacity:.62; font-size:13px; } #pk-audio-ov #pk_audio { display:none; } #pk-audio-mini { position:fixed; right:24px; bottom:24px; z-index:2147483642; width:300px; max-width:calc(100vw - 16px); box-sizing:border-box; background:rgba(var(--pk-bg-rgb,255,255,255),.94); color:var(--pk-fg,#222); border:1px solid var(--pk-bd,rgba(0,0,0,.12)); border-radius:12px; box-shadow:0 18px 46px rgba(0,0,0,.36); backdrop-filter:blur(10px); overflow:hidden; font-family:inherit; line-height:1.5; user-select:none; } #pk-audio-mini.pk-dark { background:rgba(var(--pk-bg-rgb,32,32,32),.94); color:var(--pk-fg,#f5f5f5); border-color:var(--pk-bd,#333); box-shadow:0 18px 46px rgba(0,0,0,.52); } #pk-audio-mini .pk-audio-mini-drag { height:42px; display:flex; align-items:center; justify-content:space-between; gap:10px; padding:0 10px 0 14px; border-bottom:1px solid var(--pk-bd,rgba(0,0,0,.12)); background:rgba(var(--pk-bg-rgb,255,255,255),.86); cursor:move; touch-action:none; } #pk-audio-mini.pk-dark .pk-audio-mini-drag { background:rgba(var(--pk-bg-rgb,32,32,32),.86); } #pk-audio-mini .pk-audio-mini-fold { width:34px; height:34px; border:0; border-radius:50%; background:transparent; color:var(--pk-fg); display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0; position:relative; transition:background .15s,color .15s,transform .15s; flex-shrink:0; } #pk-audio-mini .pk-audio-mini-fold:hover { background:var(--pk-hl); color:var(--pk-pri); } #pk-audio-mini .pk-audio-mini-fold:active { transform:scale(.96); } #pk-audio-mini .pk-audio-mini-fold svg { width:18px; height:18px; fill:currentColor; } #pk-audio-mini .pk-audio-mini-body { padding:14px; display:flex; flex-direction:column; gap:10px; min-width:0; } #pk-audio-mini.pk-audio-mini-collapsed .pk-audio-mini-drag { border-bottom:0; } #pk-audio-mini.pk-audio-mini-collapsed .pk-audio-mini-body { display:none; } #pk-audio-mini .pk-audio-mini-name { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:14px; font-weight:800; color:var(--pk-fg); } #pk-audio-mini .pk-audio-mini-meta { min-height:18px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; color:var(--pk-fg); opacity:.66; } #pk-audio-mini .pk-audio-mini-controls { display:flex; align-items:center; justify-content:center; gap:8px; padding-top:2px; } #pk-audio-mini .pk-audio-mini-volume-wrap { width:34px; height:34px; position:relative; display:flex; align-items:center; justify-content:center; flex-shrink:0; } #pk-audio-mini .pk-audio-mini-volume-wrap::before { content:""; position:absolute; left:50%; bottom:100%; transform:translateX(-50%); width:44px; height:18px; background:transparent; } #pk-audio-mini .pk-audio-mini-volume-panel { position:absolute; left:50%; bottom:calc(100% + 2px); transform:translate(-50%,4px); width:34px; height:118px; display:flex; align-items:center; justify-content:center; opacity:0; visibility:hidden; pointer-events:none; transition:opacity .14s ease,transform .14s ease,visibility .14s; z-index:4; background:var(--pk-bg); border:1px solid var(--pk-bd); border-radius:14px; box-shadow:0 8px 24px rgba(0,0,0,.22); } #pk-audio-mini .pk-audio-mini-volume-panel::before { content:""; position:absolute; left:50%; bottom:-14px; transform:translateX(-50%); width:44px; height:18px; background:transparent; } #pk-audio-mini .pk-audio-mini-volume-wrap:hover .pk-audio-mini-volume-panel,#pk-audio-mini .pk-audio-mini-volume-wrap:focus-within .pk-audio-mini-volume-panel,#pk-audio-mini .pk-audio-mini-volume-wrap.pk-audio-mini-volume-active .pk-audio-mini-volume-panel { opacity:1; visibility:visible; pointer-events:auto; transform:translate(-50%,0); } #pk-audio-mini .pk-audio-mini-volume-panel::after { display:none; } #pk-audio-mini .pk-audio-mini-volume { -webkit-appearance:none; appearance:none; writing-mode:vertical-lr; direction:rtl; width:6px; height:84px; border-radius:999px; background:linear-gradient(to top,var(--pk-pri) 0%,var(--pk-pri) var(--pk-mini-vol-pct,0%),rgba(127,127,127,.28) var(--pk-mini-vol-pct,0%),rgba(127,127,127,.28) 100%); outline:none; cursor:pointer; padding:0; margin:0; } #pk-audio-mini .pk-audio-mini-volume::-webkit-slider-runnable-track { width:6px; height:84px; border-radius:999px; background:transparent; } #pk-audio-mini .pk-audio-mini-volume::-webkit-slider-thumb { -webkit-appearance:none; appearance:none; width:14px; height:14px; border-radius:50%; background:var(--pk-pri); border:2px solid var(--pk-bg); box-shadow:0 1px 5px rgba(0,0,0,.24); margin-left:-4px; } #pk-audio-mini .pk-audio-mini-volume::-moz-range-track { width:6px; height:84px; border-radius:999px; background:rgba(127,127,127,.28); } #pk-audio-mini .pk-audio-mini-volume::-moz-range-progress { width:6px; border-radius:999px; background:var(--pk-pri); } #pk-audio-mini .pk-audio-mini-volume::-moz-range-thumb { width:14px; height:14px; border-radius:50%; background:var(--pk-pri); border:2px solid var(--pk-bg); box-shadow:0 1px 5px rgba(0,0,0,.24); } #pk-audio-mini .pk-audio-mini-btn { width:34px; height:34px; border:0; border-radius:50%; background:transparent; color:var(--pk-fg); display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0; position:relative; transition:background .15s,color .15s,transform .15s; flex-shrink:0; } #pk-audio-mini .pk-audio-mini-btn:hover { background:var(--pk-hl); color:var(--pk-pri); } #pk-audio-mini .pk-audio-mini-btn:active { transform:scale(.96); } #pk-audio-mini .pk-audio-mini-btn svg { width:20px; height:20px; fill:currentColor; } #pk-audio-mini #pk_audio_mini_play { width:42px; height:42px; background:var(--pk-pri); color:#fff; } #pk-audio-mini #pk_audio_mini_play:hover { color:#fff; filter:brightness(1.08); } #pk-audio-mini .pk-audio-mini-btn[data-tip]::after { content:attr(data-tip); position:absolute; left:50%; bottom:calc(100% + 8px); transform:translate(-50%,4px); padding:4px 8px; border-radius:6px; background:var(--pk-bg); color:var(--pk-fg); border:1px solid var(--pk-bd); box-shadow:0 6px 16px rgba(0,0,0,.28); font-size:12px; line-height:1.35; white-space:nowrap; opacity:0; pointer-events:none; transition:opacity .14s ease,transform .14s ease; z-index:2; } #pk-audio-mini .pk-audio-mini-btn[data-tip]::before { content:""; position:absolute; left:50%; bottom:calc(100% + 3px); transform:translateX(-50%); border:5px solid transparent; border-top-color:var(--pk-bd); opacity:0; pointer-events:none; transition:opacity .14s ease; z-index:2; } #pk-audio-mini .pk-audio-mini-btn[data-tip]:hover::after,#pk-audio-mini .pk-audio-mini-btn[data-tip]:focus-visible::after { opacity:1; transform:translate(-50%,0); } #pk-audio-mini .pk-audio-mini-btn[data-tip]:hover::before,#pk-audio-mini .pk-audio-mini-btn[data-tip]:focus-visible::before { opacity:1; } #pk-audio-mini .pk-audio-mini-audio-host { display:none; } @media (max-width:760px){#pk-audio-ov .pk-audio-box.pk-audio-playlist-open{width:min(680px,100%);flex-direction:column;}#pk-audio-ov .pk-audio-box.pk-audio-playlist-open .pk-audio-playlist-panel{flex:0 0 auto;width:100%;max-width:none;max-height:240px;border-left:0;border-top:1px solid var(--pk-bd);}#pk-audio-ov .pk-audio-playlist-head{height:44px;flex-basis:44px;}} @media (max-width:640px){#pk-audio-ov{padding:10px;}#pk-audio-ov .pk-audio-main{padding:26px 18px 22px;}#pk-audio-ov .pk-audio-progress-row{grid-template-columns:46px 1fr 46px;gap:8px;padding:0 14px 16px;}#pk-audio-ov .pk-audio-controls{gap:8px;flex-wrap:wrap;}#pk-audio-ov .pk-audio-volume-wrap{min-width:112px;}#pk-audio-ov .pk-audio-name{font-size:16px;white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;}} @keyframes pkFadeInUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } .pk-img-ov { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 2147483640; background: rgba(0, 0, 0, 0.85); backdrop-filter: blur(5px); display: flex; align-items: center; justify-content: center; outline: none; user-select: none; } .pk-img-box { position: absolute !important; top: 10%; left: 50%; transform: translateX(-50%); background: #000; border-radius: 8px; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); overflow: visible !important; display: flex; flex-direction: column; align-items: center; justify-content: center; width: 90%; max-width: 1600px; min-width: 480px; height: 80%; transition: width 0.2s cubic-bezier(0.4, 0, 0.2, 1), height 0.2s cubic-bezier(0.4, 0, 0.2, 1), border-radius 0.2s, top 0.2s, left 0.2s, transform 0.2s; z-index: 10; box-sizing: border-box; border: none; } .pk-img-box.plist-active { height: calc(80% - 84px); border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .pk-img-box.full { width: 100%; max-width: none; height: 100%; border-radius: 0; top: 0 !important; left: 0 !important; transform: none !important; } .pk-img-box.full.plist-active { height: calc(100vh - 84px); } .pk-img-obj { flex: 1; width: 100%; height: 100%; min-height: 0; object-fit: contain; cursor: grab; transition: transform 0.1s linear; transform-origin: center center; } #pk_img_plist { position: absolute; top: calc(100% - 1px); left: 0; right: 0; z-index: 25; height: 84px; display: flex; flex-direction: column; pointer-events: none; } #pk_img_plist .pk-p-plist-strip { pointer-events: none; opacity: 0; transition: opacity 0.2s; } .pk-img-box.plist-active #pk_img_plist .pk-p-plist-strip { display: flex !important; opacity: 1 !important; pointer-events: auto !important; animation: pkFadeInOnly 0.2s ease; } #pk_img_plist_tab { pointer-events: auto !important; z-index: 30; cursor: pointer; } #pk_img_plist_scroll { pointer-events: auto !important; } .pk-img-obj:active { cursor: grabbing; } .pk-img-bar { position: absolute; top: 0; left: 0; right: 0; height: 50px; background: linear-gradient(to bottom, rgba(0, 0, 0, 0.6), transparent); display: flex; align-items: center; justify-content: space-between; padding: 0 20px; z-index: 20; opacity: 0; transition: opacity 0.3s; pointer-events: none; } .pk-img-bar > * { pointer-events: auto; } .pk-img-box:hover .pk-img-bar { opacity: 1; } .pk-img-title { color: #fff; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: bold; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8); } .pk-img-actions { display: flex; gap: 10px; } .pk-img-nav { position: absolute; top: 50%; transform: translateY(-50%); width: 50px; height: 100px; display: flex; align-items: center; justify-content: center; color: rgba(255, 255, 255, 0.5); cursor: pointer; z-index: 5; transition: background 0.2s, color 0.2s; border-radius: 4px; opacity: 0; } .pk-img-box:hover .pk-img-nav { opacity: 1; } .pk-img-nav:hover { background: rgba(0, 0, 0, 0.3); color: #fff; } .pk-img-prev { left: 10px; } .pk-img-next { right: 10px; } .pk-img-nav svg { width: 36px; height: 36px; stroke-width: 3; } .pk-img-btn { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; color: #eee; cursor: pointer; border-radius: 50%; background: transparent; transition: all 0.2s ease; flex-shrink: 0; } .pk-img-btn:not(#pk_img_close):hover { color: var(--pk-pri) !important; background: transparent !important; } #pk_img_close:hover { background: rgba(255, 255, 255, 0.15) !important; color: #fff !important; } .pk-img-btn svg { width: 20px; height: 20px; } .pk-tag-default { margin-top: -1px; margin-left: 10px; flex-shrink: 0; min-width: 32px; box-sizing: border-box; font-size: 10px; height: 18px; padding: 1px 6px 0 6px !important; border-radius: 20px; font-weight: normal; white-space: nowrap; cursor: default; display: inline-flex; align-items: center; justify-content: center; user-select: none; background-color: transparent; color: #999; border: 1px solid #ccc; } .pk-ov.pk-dark .pk-tag-default { background-color: transparent; color: #888; border-color: #555; text-shadow: none; } #pk-scan-dup, #pk-btn-exit { align-items: center !important; margin: 0 !important; flex-shrink: 0 !important; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .pk-status-dot::after { content: ''; position: absolute; top: 8px; right: 8px; width: 8px; height: 8px; background: #1a5eff; border-radius: 50%; border: 2px solid var(--pk-bg); box-shadow: 0 0 5px rgba(26, 94, 255, 0.5); animation: pk-pulse 2s cubic-bezier(0.45, 0.05, 0.55, 0.95) infinite; z-index: 11; pointer-events: none; } @keyframes pk-pulse { 0% { transform: scale(0.9); opacity: 0.6; box-shadow: 0 0 0 0 rgba(26, 94, 255, 0.7); } 50% { transform: scale(1.1); opacity: 1; box-shadow: 0 0 0 4px rgba(26, 94, 255, 0); } 100% { transform: scale(0.9); opacity: 0.6; box-shadow: 0 0 0 0 rgba(26, 94, 255, 0); } } .pk-tooltip { position: fixed; z-index: 2147483647 !important; background: var(--pk-tip-bg); color: var(--pk-tip-fg); border: 1px solid var(--pk-tip-bd); padding: 6px 12px; border-radius: 8px; font-size: 12px; font-weight: 500; line-height: 1.4; pointer-events: none; opacity: 0; transform: translateY(5px) scale(0.95); transition: opacity 0.15s ease, transform 0.15s ease; box-shadow: 0 4px 16px var(--pk-tip-sd); backdrop-filter: blur(4px); max-width: 300px; white-space: pre-wrap; } .pk-tooltip.show { opacity: 1; transform: translateY(0) scale(1); } .pk-drag-ghost { position: fixed; z-index: 2147483647; background: var(--pk-bg); border: 1px solid var(--pk-pri); color: var(--pk-fg); padding: 8px 12px; border-radius: 6px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); pointer-events: none; font-size: 13px; font-weight: 600; display: flex; align-items: center; gap: 8px; opacity: 0.9; transform: translate(15px, 15px); } .pk-row.pk-drop-target { background: var(--pk-sel-bg) !important; outline: 2px dashed var(--pk-pri); outline-offset: -2px; z-index: 10; } #pk-crumb span.pk-drop-target { background: var(--pk-sel-bg) !important; color: var(--pk-pri) !important; outline: 2px dashed var(--pk-pri); outline-offset: -2px; border-radius: 4px; z-index: 10; opacity: 1 !important; } .pk-crumb-item.pk-drop-target { background: var(--pk-sel-bg) !important; color: var(--pk-pri) !important; outline: 2px dashed var(--pk-pri); outline-offset: -2px; } .pk-empty { position: absolute; inset: 0; width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; pointer-events: none; padding-bottom: 10vh; z-index: 1; opacity: 0; animation: pkFadeInOnly 0.4s ease forwards; } .pk-empty svg { width: 35vmin; max-width: 220px; height: auto; margin-bottom: 3vmin; filter: drop-shadow(0 15px 25px rgba(0, 0, 0, 0.05)); } .pk-empty-txt { font-size: clamp(13px, 3vmin, 16px); color: #94a3b8; font-weight: 500; letter-spacing: 1px; } .pk-ov.pk-dark .pk-empty-txt { color: #666e75; } .pk-selection-box { position: fixed; inset: 0 auto auto 0; background: rgba(0, 103, 192, 0.1); border: 1px solid rgba(0, 103, 192, 0.4); z-index: 2147483647 !important; pointer-events: none; display: none; will-change: transform; box-sizing: border-box; } .pk-p-vol-indicator { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.75); color: #fff; padding: 12px 24px; border-radius: 12px; font-size: 20px; font-weight: bold; z-index: 120; display: none; align-items: center; gap: 10px; backdrop-filter: blur(8px); border: 1px solid rgba(255,255,255,0.15); pointer-events: none; font-family: "Inter", sans-serif; } .pk-p-vol-indicator svg { fill: #fff !important; width: 100%; height: 100%; filter: drop-shadow(0 1px 2px rgba(0,0,0,0.3)); } .pk-is-vol-active .pk-p-center-play, .pk-is-vol-active .pk-p-loading { display: none !important; opacity: 0 !important; } .pk-input-err-msg { color: #ff4d4f; font-size: 12px; margin-top: 8px; min-height: 18px; visibility: hidden; transition: opacity 0.2s; } .pk-ana-select { position: relative; width: 85px; flex-shrink: 0; } #an_val_min::-webkit-outer-spin-button, #an_val_min::-webkit-inner-spin-button, #an_val_max::-webkit-outer-spin-button, #an_val_max::-webkit-inner-spin-button, #sc_val_min::-webkit-outer-spin-button, #sc_val_min::-webkit-inner-spin-button, #sc_val_max::-webkit-outer-spin-button, #sc_val_max::-webkit-inner-spin-button, #sh_mod_cnt_val::-webkit-outer-spin-button, #sh_mod_cnt_val::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } #an_val_min, #an_val_max, #sc_val_min, #sc_val_max, #sh_mod_cnt_val { -moz-appearance: textfield; } #an_val_min, #an_val_max { padding-right:32px !important; } .pk-num-ctrl { position: absolute; right: 8px; top: 4px; bottom: 4px; display: flex; flex-direction: column; width: 24px; gap: 1px; z-index: 5; } .pk-num-btn { flex: 1; display: flex; align-items: center; justify-content: center; cursor: pointer; color: var(--pk-icon-c); border-radius: 3px; transition: all 0.1s; } .pk-num-btn:hover { background: var(--pk-hl); color: var(--pk-pri); } .pk-num-btn:active { transform: scale(0.9); } .pk-num-btn svg { width: 14px; height: 14px; stroke-width: 3; } .pk-ana-trigger { width: 100%; height: 42px; display: flex; align-items: center; justify-content: space-between; padding: 0 12px; border: 2px solid var(--pk-bd); border-radius: 8px; background: var(--pk-bg); color: var(--pk-fg); cursor: pointer; font-weight: 700; font-size: 14px; transition: border-color 0.2s; box-sizing: border-box; } .pk-ana-trigger:hover { border-color: var(--pk-pri); } .pk-ana-menu { position: absolute; top: 100%; left: 0; right: 0; background: var(--pk-bg); border: 1px solid var(--pk-bd); border-radius: 8px; margin-top: 6px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); z-index: 10010; display: none; overflow: hidden; } .pk-ana-item { padding: 10px 12px; cursor: pointer; font-size: 13px; color: var(--pk-fg); transition: background 0.1s; } .pk-ana-item:hover { background: var(--pk-hl); color: var(--pk-pri); } .pk-ana-item.act { color: var(--pk-pri); font-weight: 700; background: rgba(0, 103, 192, 0.05); } .pk-msg-toast { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--pk-toast-bg); color: var(--pk-toast-fg); padding: 12px 28px; border-radius: 12px; z-index: 20000; font-size: 14px; font-weight: 600; pointer-events: none; opacity: 0; transition: all 0.3s cubic-bezier(0.23, 1, 0.32, 1); text-align: left; display: flex; align-items: center; justify-content: center; gap: 12px; backdrop-filter: blur(8px); border: 1px solid var(--pk-toast-bd); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); } .pk-msg-toast.show { opacity: 1; transform: translate(-50%, -60%); } .pk-crumb-sep { width: 22px; height: 22px; margin: 0 2px; cursor: pointer; border-radius: 4px; color: #aaa; transition: all 0.2s; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; } .pk-crumb-sep:hover, .pk-crumb-sep.pk-active { background: var(--pk-hl); color: currentColor; } .pk-crumb-sep svg { width: 14px; height: 14px; stroke-width: 3.5; transition: transform 0.2s ease; } .pk-crumb-pop { position: fixed; background: var(--pk-bg); border: 1px solid var(--pk-bd); border-radius: 8px; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.35); z-index: 2147483647 !important; min-width: 180px; max-width: 320px; max-height: 50vh; overflow-y: auto; padding: 6px 0; opacity: 0; pointer-events: none; transition: opacity 0.1s ease; border-top: 1px solid rgba(255, 255, 255, 0.05); } .pk-crumb-pop.pk-show { opacity: 1; pointer-events: auto; } .pk-crumb-item { padding: 8px 12px; font-size: 13px; line-height: 1.5; cursor: pointer; display: flex; align-items: center; justify-content: flex-start; gap: 8px; color: var(--pk-fg); } body.pk-body-max .pk-crumb-pop .pk-crumb-item { font-size:16px; } body.pk-body-max .pk-crumb-pop .pk-crumb-name-wrap .pk-tag-default { font-size:14px !important; height:18px; } .pk-crumb-item:hover { background: var(--pk-hl); } .pk-crumb-empty { justify-content:center; cursor:default; opacity:.62; color:var(--pk-fg); } .pk-crumb-item.pk-moving { opacity: 0.4; filter: grayscale(1); cursor: wait; pointer-events: none; } .pk-crumb-item svg { flex-shrink: 0; } .pk-crumb-name-wrap { display: inline-flex; align-items: center; justify-content: flex-start; min-width: 0; max-width: calc(100% - 26px); flex: 0 1 auto; overflow: hidden; } .pk-crumb-name { min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 1 auto; padding-bottom: 2px; margin-bottom: -2px; } .pk-crumb-name-wrap .pk-tag-default { flex: 0 0 auto !important; margin-left: 6px; min-width: auto; height: 16px; padding: 0 6px; line-height: 1 !important; display: inline-flex; align-items: center; justify-content: center; white-space: nowrap !important; overflow: visible !important; text-overflow: clip !important; padding-bottom: 0 !important; margin-bottom: 0 !important; } @keyframes pkFadeInOnly { from { opacity: 0; } to { opacity: 1; } } .pk-share-modal { width: 540px !important; box-sizing: border-box; display: flex; flex-direction: column; gap: 20px; padding: 0 !important; overflow: visible !important; } .pk-s-sec { display: flex; flex-direction: column; gap: 12px; padding: 0 28px; box-sizing: border-box; } .pk-detail-cancel-btn { cursor: pointer; color: var(--pk-pri); font-size: 15px; padding: 8px 12px; border-radius: 6px; transition: background 0.2s; margin-left: -12px; } .pk-detail-cancel-btn:hover { background: var(--pk-hl); } #pk_edit_pwd_cancel, #pk_edit_phrase_cancel, #pk_mod_cnt_cancel { display:inline-flex; align-items:center; justify-content:center; min-width:64px; height:32px; padding:0 14px; border-radius:8px; color:#666; transition:background 0.18s ease, box-shadow 0.18s ease, color 0.18s ease; } .pk-modal-ov:not(.pk-dark) #pk_edit_pwd_cancel:hover, .pk-modal-ov:not(.pk-dark) #pk_edit_phrase_cancel:hover, .pk-modal-ov:not(.pk-dark) #pk_mod_cnt_cancel:hover { background:rgba(0,0,0,0.06); box-shadow:0 2px 8px rgba(0,0,0,0.08); color:#444; } .pk-modal-ov.pk-dark #pk_edit_pwd_cancel:hover, .pk-modal-ov.pk-dark #pk_edit_phrase_cancel:hover, .pk-modal-ov.pk-dark #pk_mod_cnt_cancel:hover { background:rgba(255,255,255,0.08); box-shadow:0 2px 8px rgba(0,0,0,0.22); color:#ddd; } .pk-share-stat-box { display: flex; background: var(--pk-hl); border-radius: 10px; padding: 10px 0; margin-bottom: 18px; border: 1px solid var(--pk-bd); } .pk-share-stat-item { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; position: relative; } .pk-share-stat-item:first-child::after { content: ''; position: absolute; right: 0; top: 25%; bottom: 25%; width: 1px; background: var(--pk-bd); } .pk-share-stat-val { font-size: 20px; font-weight: 700; color: var(--pk-fg); line-height: 1.1; font-family: "Inter", system-ui, sans-serif; } .pk-share-stat-lbl { font-size: 11px; color: #888; margin-top: 2px; font-weight: 500; } .pk-s-lbl { font-size: 13px; font-weight: 700; color: var(--pk-fg); opacity: 0.6; text-transform: uppercase; letter-spacing: 0.5px; } .pk-s-tabs { display: flex; background: var(--pk-hl); border-radius: 8px; padding: 4px; gap: 4px; border: 1px solid var(--pk-bd); } .pk-s-tab { flex: 1; height: 36px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: var(--pk-fg); font-size: 14px; border-radius: 6px; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); opacity: 0.7; } .pk-s-tab.act { background: var(--pk-bg); color: var(--pk-pri); opacity: 1; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); font-weight: 700; } .pk-s-opts { display: flex; flex-flow: row nowrap; gap: 20px; align-items: center; justify-content: flex-start; } .pk-s-opt { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; color: var(--pk-fg); white-space: nowrap; flex-shrink: 0; } .pk-s-lbl { font-size: 13px; font-weight: 600; color: var(--pk-fg); opacity: 0.8; } .pk-s-tabs { display: flex; background: var(--pk-hl); border-radius: 6px; padding: 3px; gap: 2px; border: 1px solid var(--pk-bd); } .pk-s-tab { flex: 1; height: 32px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: var(--pk-fg); font-size: 13px; border-radius: 4px; transition: all 0.2s; opacity: 0.7; } .pk-s-tab.act { background: var(--pk-bg); color: var(--pk-pri); opacity: 1; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); font-weight: bold; } .pk-s-opts { display: flex; flex-wrap: nowrap; gap: 12px; align-items: center; justify-content: space-between; } .pk-s-opt { display: flex; align-items: center; gap: 6px; cursor: pointer; font-size: 13px; color: var(--pk-fg); white-space: nowrap; flex-shrink: 0; } .pk-s-opt input[type="radio"] { appearance: none; width: 16px; height: 16px; border: 1px solid var(--pk-icon-c); border-radius: 50%; position: relative; cursor: pointer; margin: 0; background: var(--pk-bg); transition: all 0.2s; } .pk-s-opt input:checked { border-color: var(--pk-pri); border-width: 5px; } .pk-s-input { flex: 1; background: var(--pk-bg); border: 1px solid var(--pk-bd); border-radius: 4px; color: var(--pk-fg); padding: 5px 10px; font-size: 13px; outline: none; transition: border-color 0.2s; min-width: 0; } .pk-s-input:focus { border-color: var(--pk-pri); } .pk-s-input:disabled { opacity: 0.3; cursor: not-allowed; background: var(--pk-hl); } .pk-share-res-val { font-family: "SF Mono", "Consolas", monospace; font-weight: 600; color: var(--pk-pri) !important; } .pk-cal-pop { position: absolute; background: var(--pk-bg); color: var(--pk-fg); border: 1px solid var(--pk-bd); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); border-radius: 8px; z-index: 10005; display: flex; font-size: 13px; overflow: hidden; animation: pkFadeIn 0.2s; user-select: none; } .pk-cal-side { width: 110px; background: var(--pk-hl); border-right: 1px solid var(--pk-bd); display: flex; flex-direction: column; padding: 8px 0; } .pk-cal-side-item { padding: 8px 16px; cursor: pointer; color: var(--pk-fg); transition: background 0.2s; } .pk-cal-side-item:hover { background: rgba(0, 0, 0, 0.05); } .pk-cal-side-item.active { color: var(--pk-pri); font-weight: bold; background: var(--pk-bg); } .pk-cal-main { width: 280px; padding: 10px; display: flex; flex-direction: column; } .pk-cal-hd { display: flex; justify-content: space-between; align-items: center; padding: 0 8px 10px 8px; border-bottom: 1px solid var(--pk-bd); } .pk-cal-nav-btn { cursor: pointer; padding: 4px; border-radius: 4px; color: #888; } .pk-cal-nav-btn:hover { background: var(--pk-hl); color: var(--pk-pri); } .pk-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; margin-top: 10px; } .pk-cal-th { text-align: center; color: #888; font-size: 12px; padding-bottom: 6px; } .pk-cal-td { height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 4px; cursor: pointer; transition: all 0.2s; position: relative; color: var(--pk-fg); } .pk-cal-td:hover:not(.disabled):not(.selected) { background: var(--pk-hl); color: var(--pk-pri); } .pk-cal-td.selected { background: var(--pk-pri); color: #fff; font-weight: bold; } .pk-cal-td.disabled { color: #888; cursor: not-allowed; opacity: 0.7; } .pk-share-footer { display: flex; align-items: center; justify-content: space-between; margin-top: 12px; padding: 16px 24px 24px 24px; border-top: 1px solid var(--pk-bd); background: transparent; } .pk-btn-quiet-red { color: #d93025; font-size: 14px; font-weight: 500; cursor: pointer; padding: 8px 12px; border-radius: 6px; transition: background 0.2s; margin-left: -8px; } .pk-btn-quiet-red:hover { background: rgba(217, 48, 37, 0.08); } .pk-btn-primary-action { background: var(--pk-pri); color: #fff; border: none; height: 38px; padding: 0 20px; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s; } .pk-btn-primary-action:hover { filter: brightness(1.08); box-shadow: 0 2px 8px rgba(0, 103, 192, 0.25); } .pk-cal-td.today::after { content: ''; position: absolute; bottom: 4px; width: 4px; height: 4px; background: currentColor; border-radius: 50%; opacity: 0.5; } .pk-share-icon-wrap { position: relative; width: 30px; height: 30px; margin-right: 12px; flex-shrink: 0; transition: transform 0.2s ease; transform-origin: center center; display:flex; align-items:center; justify-content:center; line-height:0; overflow:visible; } .pk-share-icon-wrap > svg, .pk-share-icon-wrap > img { width: 100%; height: 100%; display: block; flex-shrink: 0; margin:0 !important; } .pk-share-icon-wrap > div:not(.pk-share-lock) { width:100%; height:100%; display:flex; align-items:center; justify-content:center; margin:0 !important; flex:0 0 100%; } .pk-share-icon-wrap > div:not(.pk-share-lock) > svg, .pk-share-icon-wrap > svg { width:100%; height:100%; display:block !important; margin:0 !important; transform:scale(0.94) !important; transform-origin:center center !important; } .pk-share-icon-disabled { transform:none !important; } .pk-share-lock { position: absolute; bottom: -2px; right: -4px; width: 14px; height: 14px; background: transparent; display: flex; align-items: center; justify-content: center; z-index: 10; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.5)) drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3)); border: none; box-shadow: none; pointer-events: none; } .pk-ov.pk-dark .pk-share-lock { filter: drop-shadow(0 0 2px rgba(0, 0, 0, 1)) drop-shadow(0 2px 5px rgba(0, 0, 0, 1)); } .pk-name { display: flex !important; align-items: center; min-width: 0; width: 100%; overflow: hidden; } .pk-name-txt { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; } .pk-select-item { padding: 10px 12px; border-radius: 5px; cursor: pointer; color: var(--pk-fg); font-size: 14px; transition: background 0.1s; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .pk-select-item:hover { background: var(--pk-hl); color: var(--pk-pri); } .pk-select-item.act { background: rgba(0, 103, 192, 0.1); color: var(--pk-pri); font-weight: 700; } .pk-select-label { position: absolute; top: 0; transform: translate3d(0, -50%, 0); -webkit-transform: translate3d(0, -50%, 0); backface-visibility: hidden; -webkit-backface-visibility: hidden; will-change: transform; left: 10px; background: var(--pk-bg); padding: 0 5px; font-size: 11px; color: var(--pk-pri); font-weight: bold; pointer-events: none; z-index: 10; line-height: 1; pointer-events: none; } .pk-field input, .pk-field select, .pk-share-modal input, .pk-ov input { transform: translateZ(0); will-change: transform; } .pk-maximized { position: fixed !important; width: 100% !important; height: 100% !important; max-width: none !important; top: 0 !important; left: 0 !important; border-radius: 0 !important; border: none !important; z-index: 2147483647 !important; } .pk-maximized .pk-sidebar { width: 190px !important; align-items: flex-start !important; padding: 20px 10px !important; overflow-x: hidden !important; box-sizing: border-box !important; } .pk-maximized .pk-nav-btn { width: 100% !important; max-width: 100% !important; justify-content: flex-start !important; padding: 0 15px !important; gap: 10px; height: 54px !important; border-radius: 8px !important; box-sizing: border-box !important; } .pk-nav-btn span { display: none; font-size: 14px; font-weight: 600; white-space: nowrap; } .pk-maximized .pk-nav-btn span { display: inline-block; } #pk-quota-wrap { margin-top: auto; width: 100%; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; } #pk-quota-head { width: 100%; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 2px; line-height: 1; } #pk-transfer-detail-btn { order: 1; display: flex; align-items: center; justify-content: center; width: 24px; min-width: 24px; height: 18px; margin: 0 0 2px 0; color: var(--pk-fg); opacity: 0.72; cursor: pointer; user-select: none; transition: none; line-height: 1; } #pk-quota-txt { order: 2; } #pk-transfer-detail-btn svg { width: 18px !important; height: 18px !important; display: block; } #pk-transfer-detail-btn:hover { color: var(--pk-pri); }1.04); } .pk-maximized #pk-quota-wrap { margin-top: auto !important; width: 100% !important; display: flex !important; flex-direction: column !important; justify-content: flex-end !important; align-items: stretch !important; padding: 0 10px 6px 10px !important; box-sizing: border-box !important; } .pk-maximized #pk-quota-head { flex-direction: row !important; justify-content: space-between !important; align-items: flex-end !important; gap: 6px !important; } .pk-maximized #pk-transfer-detail-btn { order: 2; display: flex !important; align-items: flex-end !important; margin: 0 0 -1px 6px !important; } .pk-maximized #pk-quota-txt { order: 1; } .pk-maximized #pk-quota-panel { width: 100% !important; align-items: flex-start !important; padding: 0 !important; margin-bottom: 0 !important; opacity: 0.9 !important; gap: 4px !important; transition: none !important; } .pk-cloud-area { width: 100%; height: 180px; background: #f1f3f5; border: none; border-radius: 8px; padding: 15px; font-size: 14px; line-height: 1.6; color: #1a1a1a; resize: none; outline: none; font-family: inherit; cursor: auto; } .pk-dark .pk-cloud-area { background: #2d2d2d !important; color: #f5f5f5 !important; caret-color: #fff !important; } .pk-dark .pk-cloud-area::placeholder { color: #666 !important; } .pk-cloud-area::placeholder { color: #adb5bd; } .pk-share-parse-panel { width:min(560px,calc(100% - 48px)); height:100%; margin:0 auto; display:flex; flex-direction:column; justify-content:center; gap:14px; color:var(--pk-fg); box-sizing:border-box; padding-bottom:96px; } .pk-share-parse-panel input { width:100%; height:42px; border:1px solid var(--pk-bd); border-radius:6px; background:var(--pk-bg); color:var(--pk-fg); padding:0 12px; box-sizing:border-box; font-size:14px; outline:none; } .pk-share-parse-panel input:focus { border-color:var(--pk-pri); box-shadow:0 0 0 2px rgba(0,103,192,.12); } .pk-share-parse-actions { display:flex; gap:8px; align-items:center; flex-wrap:wrap; } .pk-share-parse-panel .pk-share-parse-btn { width:auto; min-width:120px; height:36px; padding:0 18px; border:none; border-radius:6px; background:var(--pk-pri); color:#fff; cursor:pointer; font-weight:700; white-space:nowrap; } .pk-share-parse-panel .pk-share-parse-btn:disabled { opacity:.62; cursor:not-allowed; } .pk-share-parse-desc { margin:0; font-size:13px; color:#888; line-height:1.5; } .pk-share-parse-result { border:1px solid var(--pk-bd); border-radius:6px; padding:12px 14px; font-size:13px; line-height:1.65; background:var(--pk-hl); color:var(--pk-fg); word-break:break-all; } .pk-share-parse-result.ok { border-color:rgba(82,196,26,.35); background:rgba(82,196,26,.08); } .pk-share-parse-result.err { border-color:rgba(217,48,37,.35); background:rgba(217,48,37,.08); color:#d93025; } .pk-share-parse-result.loading { opacity:.78; } .pk-share-parse-list-state { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; flex-direction:column; gap:12px; color:var(--pk-fg); opacity:.72; font-size:13px; pointer-events:none; } .pk-share-parse-list-state.err { color:#d93025; opacity:.9; } .pk-share-insight-path { color:var(--pk-fg); font-size:12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } #pk_cloud_torrent_trigger:hover, #pk_cloud_change_dir:hover { text-decoration: underline; } .pk-maximized #pk-quota-bar-box { width: 100% !important; height: 6px !important; transition: none !important; border-radius: 3px !important; } .pk-maximized #pk-quota-txt { font-size: 12px !important; transform: none !important; opacity: 1; font-weight: normal !important; white-space: nowrap; } .pk-maximized .pk-hd { height: 60px !important; padding: 0 20px !important; } .pk-maximized .pk-tt { font-size: 24px !important; } .pk-maximized .pk-tt svg { width: 32px !important; height: 32px !important; } .pk-maximized .pk-tb { height: 60px !important; padding: 0 16px !important; gap: 10px !important; } .pk-maximized .pk-btn { height: 40px !important; font-size: 15px !important; padding: 0 16px !important; } .pk-btn svg { width: auto; height: auto; } #pk-down svg { width: 16px !important; height: 16px !important; display: block !important; transform: none !important; transform-origin: center center !important; shape-rendering: geometricPrecision; } #pk-theme svg, #pk-maximize svg { width: 16px !important; height: 16px !important; } #pk-close svg { width: 19px !important; height: 19px !important; } .pk-maximized #pk-theme svg, .pk-maximized #pk-maximize svg { width: 24px !important; height: 24px !important; } .pk-maximized #pk-close svg { width: 28px !important; height: 28px !important; } .pk-maximized .pk-grid-hd { height: 50px !important; font-size: 15px !important; padding: 0 26px 0 20px !important; } .pk-maximized:not(.pk-grid-view) .pk-row, .pk-maximized:not(.pk-grid-view) .pk-group-hd { height: 60px !important; font-size: 16px !important; padding: 0 20px !important; } .pk-maximized:not(.pk-grid-view) .pk-group-hd { border-top-width: 10px !important; border-bottom-width: 10px !important; } .pk-maximized.pk-grid-view .pk-group-hd { height: 60px !important; font-size: 16px !important; padding: 0 20px !important; border-top-width: 10px !important; border-bottom-width: 10px !important; box-sizing: border-box !important; align-items: center !important; } .pk-maximized.pk-grid-view .pk-group-hd > div { height: 100% !important; display: flex !important; align-items: center !important; } .pk-maximized.pk-grid-view .pk-group-hd .pk-name { height: 100% !important; display: flex !important; align-items: center !important; min-height: 0 !important; } .pk-maximized.pk-grid-view .pk-group-hd .pk-name .pk-name-txt, .pk-maximized.pk-grid-view .pk-group-hd .pk-name span { display: inline-flex !important; align-items: center !important; height: 100% !important; line-height: 1 !important; padding-top: 0 !important; padding-bottom: 0 !important; margin-top: 0 !important; } .pk-maximized:not(.pk-grid-view) .pk-name svg { width: 60px !important; height: 60px !important; margin-right: 20px !important; } .pk-maximized:not(.pk-grid-view) .pk-row .pk-name .pk-name-txt { white-space: normal !important; display: -webkit-box !important; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden !important; text-overflow: ellipsis !important; line-height: 1.35 !important; word-break: break-all !important; margin-top: 0 !important; } .pk-maximized.pk-grid-view .pk-row { padding: 0 !important; } .pk-max-icon-box { position: relative !important; width: 50px !important; height: 50px !important; margin-right: 20px !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; flex-shrink: 0 !important; vertical-align: middle !important; overflow: visible !important; } .pk-placeholder-icon { position: absolute !important; inset: 0 !important; display: flex !important; align-items: center !important; justify-content: center !important; z-index: 1 !important; transition: opacity 0.2s, visibility 0.2s; } .pk-placeholder-icon svg { width: 44px !important; height: 44px !important; object-fit: contain !important; } .pk-max-icon-box .pk-max-thumb { position: absolute !important; top: 0 !important; left: 0 !important; width: 50px !important; height: 50px !important; object-fit: cover !important; border-radius: 4px !important; opacity: 0; z-index: 2 !important; transition: opacity 0.25s ease-in-out; background: transparent; } .pk-maximized .pk-share-icon-wrap { width: 50px !important; height: 50px !important; margin-right: 20px !important; background: transparent !important; overflow: visible !important; } .pk-maximized .pk-row:has(.pk-max-thumb) .pk-name-txt { padding-left: 0 !important; } .pk-maximized .pk-nav-btn svg { width: 28px !important; height: 28px !important; } body:not(.pk-body-max):has(.pk-modal-ov) #pk-launch { filter: grayscale(1) brightness(0.6) !important; opacity: 0.5 !important; pointer-events: none !important; cursor: not-allowed !important; transition: filter 0.3s, opacity 0.3s; } body:has(.pk-ov:not([style*="display: none"])), body:has(.pk-img-ov), body:has(#pk-player-ov), body:has(#pk-audio-ov) { #pk-launch { display: none !important; visibility: hidden !important; opacity: 0 !important; pointer-events: none !important; } } .pk-maximized .pk-search input { height: 40px !important; font-size: 15px !important; padding: 0 70px 0 15px !important; } .pk-maximized #pk-search-btn { width: 18px !important; height: 18px !important; right: 14px !important; } .pk-maximized .pk-search-clear { width: 24px !important; height: 24px !important; right: 40px !important; } .pk-maximized input[type="checkbox"] { width: 18px !important; height: 18px !important; transform: none !important; margin: 0 8px 0 0 !important; } .pk-maximized .pk-star-icon, .pk-maximized .pk-star-toggle, .pk-maximized .pk-col[data-k="starred"] svg { width: 16px !important; height: 16px !important; } .pk-maximized .pk-view-switch { padding: 4px !important; } .pk-maximized .pk-view-btn { width: 38px !important; height: 32px !important; } .pk-maximized .pk-grid-hd { height: 58px !important; } .pk-maximized .pk-grid-hd.pk-grid-view-hd { padding: 0 24px 0 20px !important; gap: 14px !important; overflow: visible !important; } .pk-maximized .pk-grid-check-tools { padding-left: 5px !important; box-sizing: border-box !important; } .pk-maximized .pk-grid-sort-trigger { height: 36px !important; font-size: 13px !important; padding: 0 14px !important; min-width: 122px !important; } .pk-maximized .pk-grid-view .pk-row { border-radius: 22px !important; } .pk-maximized .pk-grid-card-body { padding: 12px 12px 14px !important; gap: 12px !important; } .pk-maximized .pk-gv-check { top: 18px !important; left: 18px !important; width: 22px !important; height: 22px !important; } .pk-maximized .pk-gv-more { top: 16px !important; right: 16px !important; width: 28px !important; height: 28px !important; } .pk-maximized .pk-gv-cover { height: calc(100% - 86px) !important; min-height: 180px !important; border-radius: 18px !important; } .pk-maximized .pk-gv-cover .pk-gv-icon svg, .pk-maximized .pk-gv-cover .pk-gv-icon img { width: 122px !important; height: 122px !important; max-width: 122px !important; max-height: 122px !important; } .pk-maximized .pk-gv-folder-shell { width: 216px !important; max-width: 216px !important; height: 152px !important; min-height: 152px !important; margin-top: 12px !important; transform: translateY(12px) !important; } .pk-maximized .pk-gv-folder-back { top: 19px !important; border-radius: 12px !important; } .pk-maximized .pk-gv-folder-tab { top: 6px !important; left: 17px !important; width: 68px !important; height: 25px !important; border-radius: 10px 10px 0 0 !important; } .pk-maximized .pk-gv-folder-preview { top: 0 !important; left: 17px !important; right: 17px !important; bottom: 25px !important; border-radius: 10px !important; } .pk-maximized .pk-gv-folder-fallback, .pk-maximized .pk-gv-folder-fallback svg { width: 116px !important; height: 116px !important; max-width: 116px !important; max-height: 116px !important; } .pk-maximized .pk-gv-folder-front { height: 90px !important; border-radius: 12px !important; } .pk-maximized .pk-gv-info { grid-template-rows:22px 18px !important; row-gap:4px !important; align-self:stretch !important; width:100% !important; min-width:0 !important; height:44px !important; min-height:44px !important; padding:0 2px !important; text-align:left !important; } .pk-maximized.pk-grid-view .pk-name .pk-name-txt { font-size:16px !important; line-height:22px !important; text-align:left !important; } .pk-maximized.pk-grid-view .pk-name { display:flex !important; align-items:center !important; justify-content:flex-start !important; align-self:stretch !important; width:100% !important; min-width:0 !important; height:22px !important; overflow:hidden !important; text-align:left !important; } .pk-maximized.pk-grid-view .pk-share-icon-wrap { width: 40px !important; height: 40px !important; margin-right: 16px !important; position: relative !important; display: block !important; overflow: visible !important; } .pk-maximized.pk-grid-view .pk-share-icon-wrap { width: 50px !important; height: 50px !important; margin-right: 20px !important; display: flex !important; align-items: center !important; justify-content: center !important; transform: none !important; overflow: visible !important; } .pk-maximized.pk-grid-view .pk-share-icon-wrap > svg { width: 100% !important; height: 100% !important; min-width: 100% !important; min-height: 100% !important; transform: scale(1.25) !important; transform-origin: center center !important; margin: 0 !important; } .pk-maximized:not(.pk-grid-view) .pk-name { display:flex !important; align-items:center !important; min-width:0 !important; width:100% !important; height:auto !important; overflow:visible !important; text-align:left !important; } .pk-maximized:not(.pk-grid-view) .pk-share-icon-wrap { position:relative !important; width:50px !important; height:50px !important; min-width:50px !important; min-height:50px !important; margin-right:20px !important; display:flex !important; align-items:center !important; justify-content:center !important; flex:0 0 50px !important; transform:none !important; overflow:visible !important; background:transparent !important; } .pk-maximized:not(.pk-grid-view) .pk-share-icon-wrap > svg { width:100% !important; height:100% !important; min-width:100% !important; min-height:100% !important; margin:0 !important; transform:scale(1.16) !important; transform-origin:center center !important; } .pk-maximized:not(.pk-grid-view) .pk-share-icon-wrap > div:not(.pk-share-lock) { width:100% !important; height:100% !important; display:flex !important; align-items:center !important; justify-content:center !important; transform:none !important; margin:0 !important; } .pk-maximized:not(.pk-grid-view) .pk-share-icon-wrap > div:not(.pk-share-lock) > svg { width:100% !important; height:100% !important; min-width:100% !important; min-height:100% !important; transform:scale(1.16) !important; transform-origin:center center !important; margin:0 !important; } .pk-maximized:not(.pk-grid-view) .pk-share-icon-wrap img { width:100% !important; height:100% !important; min-width:100% !important; min-height:100% !important; object-fit:contain !important; margin:0 !important; transform:none !important; } .pk-maximized:not(.pk-grid-view) .pk-share-lock { width:18px !important; height:18px !important; bottom:-2px !important; right:-6px !important; z-index:15 !important; } .pk-maximized:not(.pk-grid-view) .pk-share-lock svg { width:100% !important; height:100% !important; min-width:100% !important; min-height:100% !important; margin:0 !important; transform:none !important; display:block !important; color:#fff !important; } .pk-maximized .pk-row > div { font-size: 16px !important; font-weight: normal !important; } .pk-maximized .pk-row div[style*="font-size:12px"], .pk-maximized .pk-row div[style*="font-size: 12px"] { font-size: 15px !important; font-weight: 400 !important; opacity: 0.9; } .pk-maximized .pk-row div[style*="tabular-nums"] { font-weight: 400 !important; } .pk-maximized .pk-ft { height: 50px !important; font-size: 15px !important; padding: 0 20px !important; } .pk-maximized .pk-stat { font-size: 15px !important; } .pk-maximized .pk-ft .pk-btn { height: 36px !important; font-size: 15px !important; } .pk-maximized #pk-filter-cat-label { height: 40px !important; font-size: 15px !important; padding: 0 16px !important; } .pk-maximized #pk-filter-exts-wrap { height: 40px !important; } .pk-maximized .pk-f-ext { font-size: 15px !important; padding: 6px 12px !important; } .pk-maximized #pk-filter-exit-btn { height: 40px !important; font-size: 15px !important; padding: 0 20px !important; } .pk-maximized .pk-bl-area { font-size: 15px !important; line-height: 1.6 !important; } .pk-maximized #pk-crumb span { font-size: 16px !important; display: inline-flex !important; align-items: center !important; height: 32px !important; padding: 0 8px !important; border-radius: 4px !important; margin: auto 2px !important; } .pk-maximized #pk-crumb div span { font-size: 18px !important; } .pk-maximized #pk-crumb div span[style*="font-size:11px"], .pk-maximized #pk-crumb div span[style*="font-size: 11px"] { font-size: 14px !important; margin-left: 12px !important; } .pk-maximized #pk-crumb svg { width: 18px !important; height: 18px !important; vertical-align: middle !important; margin-top: -1px !important; } .pk-maximized .pk-crumb-sep { width: 26px !important; height: 26px !important; margin: auto 4px !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; } .pk-maximized .pk-crumb-sep svg { width: 20px !important; height: 20px !important; } .pk-custom-select { position: relative; width: 100%; } .pk-select-trigger { display: flex; align-items: center; justify-content: space-between; height: 44px; padding: 0 15px; border: 2px solid var(--pk-bd); border-radius: 8px; background: var(--pk-bg); color: var(--pk-fg); font-size: 14px; font-weight: 600; cursor: pointer; box-sizing: border-box; transition: all 0.2s; } .pk-select-trigger:hover { border-color: var(--pk-pri); } .pk-select-trigger svg { width: 16px !important; height: 16px !important; min-width: 16px; min-height: 16px; color: #999; flex-shrink: 0; } .pk-dropdown-wrap { position: relative; display: inline-flex; height: 32px; align-items: center; } .pk-dropdown-menu { position: absolute; top: 100%; right: 0; background: var(--pk-bg); border: 1px solid var(--pk-bd); border-radius: 8px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15); display: none; z-index: 10010; min-width: 140px; padding: 4px 0; margin-top: 6px; flex-direction: column; overflow: hidden; } .pk-dropdown-item { padding: 10px 16px; display: flex; align-items: center; gap: 8px; cursor: pointer; color: var(--pk-fg); font-size: 13px; transition: background 0.1s; white-space: nowrap; } .pk-dropdown-item:hover { background: var(--pk-hl); color: var(--pk-pri); } .pk-pop-max { width: 170px !important; min-width: 170px !important; padding: 6px 0 !important; border-radius: 10px !important; box-shadow: 0 8px 30px rgba(0,0,0,0.3) !important; } .pk-pop-max .pk-dropdown-item { padding: 12px 15px !important; font-size: 15px !important; gap: 10px !important; font-weight: 600 !important; } .pk-pop-max .pk-dropdown-item svg { width: 22px !important; height: 22px !important; } .pk-btn-arrow { margin-left: 2px; opacity: 0.6; transition: transform 0.2s; } .pk-aria-status-box { display: flex; align-items: center; gap: 6px; font-size: 11px; font-weight: bold; margin-top: 6px; transform: translateZ(0); -webkit-transform: translateZ(0); backface-visibility: hidden; will-change: transform; cursor: default; } .pk-setting-fieldset{position:relative;padding:25px 15px 15px 15px;border:2px solid var(--pk-bd);border-radius:8px;transition:border-color .2s;transform:translateZ(0);backface-visibility:hidden;}.pk-setting-fieldset:hover:not(.pk-inner-active){border-color:var(--pk-pri);}.pk-setting-fieldset.pk-inner-active{border-color:var(--pk-bd)!important;}.pk-setting-stack{display:flex;flex-direction:column;gap:15px;}.pk-setting-checkrow{display:flex;align-items:center;justify-content:space-between;height:44px;min-height:44px;border:2px solid var(--pk-bd);border-radius:8px;padding:0 12px;cursor:pointer;background:var(--pk-bg);transition:border-color .2s;box-sizing:border-box;}.pk-setting-checkrow:hover{border-color:var(--pk-pri);}.pk-setting-checkrow span{font-size:14px;font-weight:400;color:var(--pk-fg);user-select:none;line-height:1.4;}.pk-setting-checkrow input{width:16px;height:16px;accent-color:var(--pk-pri);cursor:pointer;flex-shrink:0;margin-left:12px;} .pk-token-eye { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); width: 28px; height: 28px; border: none; border-radius: 6px; background: transparent; color: #8a94a4; cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; transition: background 0.15s, color 0.15s; z-index: 20; } .pk-token-eye:hover { background: rgba(0,103,192,0.1); color: var(--pk-pri); } .pk-token-eye svg { width: 18px; height: 18px; display: block; } .pk-aria-dot { width: 8px; height: 8px; border-radius: 50%; background: #ccc; transform: translateZ(0); } .pk-aria-dot.ok { background: #52c41a; box-shadow: 0 0 8px rgba(82, 196, 26, 0.5); } .pk-aria-dot.err { background: #ff4d4f; } .pk-aria-dot.wait { background: var(--pk-pri); animation: pk-pulse 1.5s infinite; } .pk-dropdown-wrap.active .pk-btn-arrow { transform: rotate(180deg); } .pk-select-menu { position: absolute; top: 100%; left: 0; right: 0; background: var(--pk-bg); border: 1px solid var(--pk-bd); border-radius: 8px; margin-top: 6px; box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); z-index: 10010; display: none; overflow: hidden; max-height: 240px; } .pk-share-icon-wrap { position: relative; width: 30px; height: 30px; margin-right: 12px; flex-shrink: 0; display:flex; align-items:center; justify-content:center; transform:none !important; } .pk-share-icon-wrap > svg, .pk-share-icon-wrap > img { width: 100%; height: 100%; display: block; flex-shrink: 0; } .pk-share-icon-wrap > div:not(.pk-share-lock) { width:100%; height:100%; display:flex; align-items:center; justify-content:center; transform:none !important; margin:0 !important; } .pk-share-icon-wrap > div:not(.pk-share-lock) > svg, .pk-share-icon-wrap > svg { transform:scale(0.94) !important; transform-origin:center center !important; } .pk-share-lock { position: absolute; bottom: -3px; right: -6px; width: 17px; height: 17px; background: transparent; display: flex; align-items: center; justify-content: center; z-index: 10; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.5)) drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3)); border: none; box-shadow: none; pointer-events: none; } .pk-ov.pk-dark .pk-share-lock { filter: drop-shadow(0 0 2px rgba(0, 0, 0, 1)) drop-shadow(0 2px 5px rgba(0, 0, 0, 1)); } .pk-bl-marker { position: absolute !important; bottom: 0px !important; left: 4px !important; width: 10px !important; height: 10px !important; display: flex !important; align-items: center; justify-content: center; z-index: 99 !important; pointer-events: none; filter: drop-shadow(0 0 1px #fff) drop-shadow(0 0 2px rgba(255,255,255,0.5)); } .pk-maximized .pk-bl-marker { width: 18px !important; height: 18px !important; bottom: -1px !important; left: 9px !important; } .pk-min-icon, .pk-max-icon-box { position: relative !important; overflow: visible !important; display: inline-flex !important; } .pk-active-border { border-color: var(--pk-pri) !important; } #pk_dl_group { transform: translateZ(0); backface-visibility: hidden; will-change: border-color; } #pk_video_playback_group:hover, #pk_dl_group:hover, #pk_download_accel_group:hover { border-color: var(--pk-pri) !important; } #pk_dl_group.pk-typing-active:hover, #pk_download_accel_group.pk-typing-active:hover { border-color: var(--pk-bd) !important; } .pk-row > div.pk-name, .pk-min-icon, .pk-max-icon-box, .pk-min-media-box { overflow: visible !important; } .pk-maximized .pk-row .pk-name>img[style*="width:24px"] { width: 48px !important; height: 48px !important; margin-right: 20px !important; margin-left: -4px !important; } .pk-maximized .pk-row .pk-name>div[style*="width:24px"] { width: 48px !important; height: 48px !important; margin-right: 20px !important; margin-left: -4px !important; } .pk-maximized .pk-row .pk-name>div[style*="width:24px"] svg { width: 36px !important; height: 36px !important; } .pk-maximized .pk-row div[style*="width:100px"] { width: 200px !important; height: 8px !important; } .pk-min-icon-box { position: relative; width: 24px; height: 24px; margin-right: 12px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; } .pk-min-placeholder { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; z-index: 1; transition: opacity 0.2s; } .pk-min-thumb { position: absolute; inset: 0; width: 24px; height: 24px; object-fit: cover; border-radius: 4px; z-index: 2; opacity: 0; transition: opacity 0.2s; } .pk-drag-mask { position: absolute; inset: 0; background: rgba(255, 255, 255, 0.92); z-index: 9999; display: none; flex-direction: column; align-items: center; justify-content: center; pointer-events: none; backdrop-filter: blur(4px); } .pk-dark .pk-drag-mask { background: rgba(32, 32, 32, 0.92); } .pk-drag-icon { width: 80px; height: 80px; background: var(--pk-pri); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #fff; margin-bottom: 24px; box-shadow: 0 10px 30px rgba(26, 94, 255, 0.3); } .pk-drag-hint { font-size: 20px; font-weight: 700; color: var(--pk-fg); margin-bottom: 12px; } .pk-drag-path { font-size: 14px; color: #888; display: flex; align-items: center; gap: 6px; } body.pk-hide-all-ui #pk-launch, body.pk-hide-all-ui .pk-ov, body.pk-hide-all-ui .pk-modal-ov, body.pk-hide-all-ui .pk-img-ov, body.pk-hide-all-ui #pk-player-ov, body.pk-hide-all-ui #pk-audio-ov, body.pk-hide-all-ui .pk-cal-pop, body.pk-hide-all-ui .pk-crumb-pop, body.pk-hide-all-ui .pk-hist-pop, body.pk-hide-all-ui .pk-tooltip, body.pk-hide-all-ui .pk-msg-toast, body.pk-hide-all-ui .pk-float-bar-item, body.pk-hide-all-ui .pk-selection-box, body.pk-hide-all-ui .pk-drag-ghost { display: none !important; opacity: 0 !important; pointer-events: none !important; } .pk-ana-select-btn { border: 1px solid var(--pk-bd); background: var(--pk-bg); color: var(--pk-fg); height: 32px; border-radius: 6px; padding: 0 10px; font-size: 13px; cursor: pointer; display: none; align-items: center; gap: 6px; transition: all 0.2s; white-space: nowrap; margin-right: 8px; } #pk-win:not(.pk-analyze-group-tools-on) #pk-ana-select-btn, #pk-win:not(.pk-analyze-group-tools-on) #pk-ana-sort-btn, #pk-win.pk-share-parse-mode #pk-ana-select-btn, #pk-win.pk-share-parse-mode #pk-ana-sort-btn { display: none !important; pointer-events: none !important; } #pk-share-parse-back-list.pk-share-exit-compact > span { display: none !important; } #pk-share-parse-back-list.pk-share-exit-compact { padding: 0 8px !important; min-width: 32px; justify-content: center; gap: 0 !important; } #pk-share-parse-back-list.pk-share-exit-cold:hover:not(:disabled),#pk-share-parse-back-list.pk-share-exit-cold:focus,#pk-share-parse-back-list.pk-share-exit-cold:active{background:transparent!important;filter:none!important;} .pk-ana-select-btn:hover { border-color: var(--pk-pri); color: var(--pk-pri); background: rgba(var(--pk-bg-rgb), 0.05); } .pk-ana-pop { position: absolute; background: var(--pk-bg); border: 1px solid var(--pk-bd); border-radius: 8px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); z-index: 1000; display: none; flex-direction: column; padding: 8px; width: 340px; gap: 4px; pointer-events: auto; } .pk-ana-pop-row { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; } .pk-ana-opt { padding: 8px 4px; border-radius: 4px; cursor: pointer; font-size: 12px; color: var(--pk-fg); transition: background 0.1s; text-align: center; border: 1px solid transparent; background: var(--pk-hl); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .pk-ana-opt:hover { background: var(--pk-sel-bg); color: var(--pk-pri); border-color: var(--pk-sel-bd); } `; ; const sleep = ms => new Promise(r => setTimeout(r, ms)); function getLogicalRect(el) { if (!el || typeof el.getBoundingClientRect !== 'function') return { top: 0, right: 0, bottom: 0, left: 0, width: 0, height: 0 }; return el.getBoundingClientRect(); } const esc = s => (s || '').replace(/[&<>"']/g, m => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[m])); const fmtSize = n => { n = parseInt(n || 0, 10); if (isNaN(n)) return ''; if (n === 0) return '0 KB'; const u = ['B', 'KB', 'MB', 'GB', 'TB']; let i = 0; while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; } return (n < 10 ? n.toFixed(2) : n.toFixed(1)) + ' ' + u[i]; }; const fmtDate = t => { if (!t) return '-'; const d = new Date(new Date(t).getTime() + (8 * 60 * 60 * 1000)); const pad = n => String(n).padStart(2, '0'); return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`; }; const fmtDur = s => { s = Math.max(0, parseInt(s, 10) || 0); if (s <= 0) return "00:00"; const h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60), sc = s % 60; const mmss = String(m).padStart(2, '0') + ':' + String(sc).padStart(2, '0'); return h > 0 ? String(h).padStart(2, '0') + ':' + mmss : mmss; }; function gmGet(key, def) { if (typeof GM_getValue !== 'undefined') { let v = GM_getValue(key, def); return (v === null) ? def : v; } return def; } function gmSet(key, val) { if (typeof GM_setValue !== 'undefined') return GM_setValue(key, val); } async function gmSetAsync(key, val) { const r = gmSet(key, val); if (r && typeof r.then === 'function') await r; } let pkScriptUpdateCheckPromise=null; function getScriptVersion(){try{return String((typeof GM_info!=='undefined'&&GM_info.script&&GM_info.script.version)||'0').replace(/^v/i,'').trim();}catch(e){return '0';}} function compareScriptVersion(a,b){const norm=v=>String(v||'0').replace(/^v/i,'').split(/[+-]/)[0].split('.').map(x=>parseInt(x,10)||0);const pa=norm(a),pb=norm(b);for(let i=0;idb?1:-1;}return 0;} function readScriptUpdateCache(){try{const raw=localStorage.getItem(CONF.scriptUpdateCacheKey);if(!raw)return null;const data=JSON.parse(raw);return data&&typeof data==='object'?data:null;}catch(e){return null;}} function writeScriptUpdateCache(data){try{localStorage.setItem(CONF.scriptUpdateCacheKey,JSON.stringify(data));}catch(e){}} async function fetchScriptUpdateInfo(force=false){const now=Date.now();const cached=readScriptUpdateCache();if(!force&&cached&&cached.checkedAt&&(now-cached.checkedAt){let result={checkedAt:now,ok:false,latestVersion:'',homepage:CONF.scriptUpdateProjectUrl,changelog:'',error:''};try{const url=`${CONF.scriptUpdateManifestUrl}${CONF.scriptUpdateManifestUrl.includes('?')?'&':'?'}_t=${now}`;const res=await fetch(url,{cache:'no-store'});if(!res.ok)throw new Error(`HTTP ${res.status}`);const data=await res.json();const latestVersion=String(data.version||data.latestVersion||data.tag||'').replace(/^v/i,'').trim();if(!latestVersion)throw new Error('Empty version');result={checkedAt:now,ok:true,latestVersion,homepage:String(data.homepage||data.url||CONF.scriptUpdateProjectUrl),changelog:String(data.changelog||data.changelogUrl||''),error:''};}catch(e){result.error=e&&e.message?e.message:String(e||'');}writeScriptUpdateCache(result);return result;})();if(!force)pkScriptUpdateCheckPromise=job;try{return await job;}finally{if(pkScriptUpdateCheckPromise===job)pkScriptUpdateCheckPromise=null;}} function isScriptUpdateNew(info){return !!(info&&info.ok&&info.latestVersion&&compareScriptVersion(info.latestVersion,getScriptVersion())>0);} function getScriptUpdateDismissKey(version){return `${CONF.scriptUpdateDismissPrefix}${String(version||'').replace(/[^0-9A-Za-z_.-]/g,'_')}`;} function isScriptUpdateDismissed(version){try{return localStorage.getItem(getScriptUpdateDismissKey(version))==='1';}catch(e){return false;}} function markScriptUpdateDismissed(version){try{localStorage.setItem(getScriptUpdateDismissKey(version),'1');}catch(e){}} function formatScriptUpdateCheckedAt(ts){const n=Number(ts)||0;if(!n)return L.str_script_update_not_checked;const d=new Date(n);if(!Number.isFinite(d.getTime()))return L.str_script_update_not_checked;const p=x=>String(x).padStart(2,'0');return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;} function getScriptUpdateLatestText(info){if(info&&info.ok&&info.latestVersion)return info.latestVersion;if(info&&info.checkedAt)return L.str_script_update_failed;return L.str_script_update_not_checked;} function getDefaultPotPlayerProtocolState() { return { schemaVersion: CONF.potplayerProtocolStateSchemaVersion, repairVersion: "", confirmedRepairVersion: "", installRegDownloadedAt: 0, deleteRegDownloadedAt: 0, browserPolicyRegDownloadedAt: 0, customPlayerPath: "", customPlayerPathUpdatedAt: 0, pathBoundRepairVersion: "", userConfirmedFixedAt: 0, lastPromptAt: 0, neverAutoPrompt: false, suppressUntil: 0 }; } function normalizePotPlayerProtocolState(rawState) { const def = getDefaultPotPlayerProtocolState(); let data = rawState; if (typeof data === 'string') { try { data = data ? JSON.parse(data) : null; } catch (e) { data = null; } } if (!data || typeof data !== 'object' || Array.isArray(data)) return def; const toTime = (v) => { const n = Number(v); return Number.isFinite(n) && n > 0 ? n : 0; }; return { schemaVersion: CONF.potplayerProtocolStateSchemaVersion, repairVersion: typeof data.repairVersion === 'string' ? data.repairVersion : "", confirmedRepairVersion: typeof data.confirmedRepairVersion === 'string' ? data.confirmedRepairVersion : "", installRegDownloadedAt: toTime(data.installRegDownloadedAt), deleteRegDownloadedAt: toTime(data.deleteRegDownloadedAt), browserPolicyRegDownloadedAt: toTime(data.browserPolicyRegDownloadedAt), customPlayerPath: typeof data.customPlayerPath === 'string' ? data.customPlayerPath : "", customPlayerPathUpdatedAt: toTime(data.customPlayerPathUpdatedAt), pathBoundRepairVersion: typeof data.pathBoundRepairVersion === 'string' ? data.pathBoundRepairVersion : "", userConfirmedFixedAt: toTime(data.userConfirmedFixedAt), lastPromptAt: toTime(data.lastPromptAt), neverAutoPrompt: data.neverAutoPrompt === true, suppressUntil: toTime(data.suppressUntil) }; } function readPotPlayerProtocolState() { try { return normalizePotPlayerProtocolState(gmGet(CONF.potplayerProtocolStateKey, '')); } catch (e) { return getDefaultPotPlayerProtocolState(); } } function writePotPlayerProtocolState(state) { try { gmSet(CONF.potplayerProtocolStateKey, normalizePotPlayerProtocolState(state)); } catch (e) {} } function updatePotPlayerProtocolState(patch = {}) { const state = readPotPlayerProtocolState(); Object.assign(state, patch, { repairVersion: CONF.potplayerProtocolRepairVersion }); writePotPlayerProtocolState(state); return state; } function recordPotPlayerProtocolRegDownload(kind) { const now = Date.now(); let patch; if (kind === 'delete') patch = { deleteRegDownloadedAt: now }; else if (kind === 'browser_policy') patch = { browserPolicyRegDownloadedAt: now }; else patch = { installRegDownloadedAt: now }; return updatePotPlayerProtocolState(patch); } function recordPotPlayerProtocolUserFixed() { const now = Date.now(); const state = readPotPlayerProtocolState(); const patch = { confirmedRepairVersion: CONF.potplayerProtocolRepairVersion, userConfirmedFixedAt: now }; if (state.customPlayerPath) { patch.pathBoundRepairVersion = CONF.potplayerProtocolRepairVersion; } return updatePotPlayerProtocolState(patch); } function recordPotPlayerProtocolAutoPrompt() { return updatePotPlayerProtocolState({ lastPromptAt: Date.now() }); } function suppressPotPlayerAutoPromptToday() { return updatePotPlayerProtocolState({ suppressUntil: Date.now() + CONF.potplayerSuppressTodayTTL }); } function disablePotPlayerAutoPrompt() { return updatePotPlayerProtocolState({ neverAutoPrompt: true }); } function shouldAutoPromptPotPlayerRepair(playUrl) { const cleanUrl = String(playUrl || '').trim(); if (!cleanUrl) return false; const now = Date.now(); const launchState = readPotPlayerLaunchState(); const protocolState = readPotPlayerProtocolState(); if (launchState.consecutiveFailCount < CONF.potplayerAutoRepairFailThreshold) return false; if (launchState.lastLikelyOpenAt > 0 && now - launchState.lastLikelyOpenAt < CONF.potplayerLikelyOpenTrustTTL) return false; if (protocolState.suppressUntil > now) return false; if (protocolState.neverAutoPrompt === true) return false; if (protocolState.lastPromptAt > 0 && now - protocolState.lastPromptAt < CONF.potplayerPromptCooldown) return false; return true; } function schedulePotPlayerAutoRepairPrompt(playUrl, opts = {}) { const cleanUrl = String(playUrl || '').trim(); if (!cleanUrl) return; if (opts.autoRepairPrompt === false) return; const source = normalizePotPlayerLaunchSource(opts.source); const autoPromptDelay = Number.isFinite(Number(opts.autoRepairPromptDelay)) ? Math.max(0, Number(opts.autoRepairPromptDelay)) : 180; setTimeout(() => { if (!shouldAutoPromptPotPlayerRepair(cleanUrl)) return; recordPotPlayerProtocolAutoPrompt(); openPotPlayerProtocolRepairHelper(cleanUrl, { source, autoPrompt: true, button: null }); }, autoPromptDelay); } function clearPotPlayerLaunchFailCount() { const state = readPotPlayerLaunchState(); state.consecutiveFailCount = 0; writePotPlayerLaunchState(state); return state; } function escapeRegString(value) { return String(value || '').replace(/\\/g, '\\\\').replace(/"/g, '\\"'); } function normalizePotPlayerCustomPath(input) { return String(input || '').trim().replace(/^["']+|["']+$/g, '').trim(); } function validatePotPlayerCustomPath(input) { const path = normalizePotPlayerCustomPath(input); if (!path) return { ok: false, path }; if (!/\.exe$/i.test(path)) return { ok: false, path }; if (!(/[A-Za-z]:\\/.test(path) || path.includes('\\'))) return { ok: false, path }; if (/[\r\n]/.test(path)) return { ok: false, path }; return { ok: true, path }; } function savePotPlayerCustomPath(input) { const checked = validatePotPlayerCustomPath(input); if (!checked.ok) return { ok: false, changed: false, path: checked.path }; const state = readPotPlayerProtocolState(); const oldPath = state.customPlayerPath || ""; const changed = oldPath !== checked.path; const patch = { customPlayerPath: checked.path }; if (changed) { patch.customPlayerPathUpdatedAt = Date.now(); patch.confirmedRepairVersion = ""; patch.pathBoundRepairVersion = ""; } updatePotPlayerProtocolState(patch); return { ok: true, changed, wasConfigured: !!oldPath, path: checked.path }; } function getValidSavedPotPlayerPath() { const state = readPotPlayerProtocolState(); const checked = validatePotPlayerCustomPath(state.customPlayerPath); return checked.ok ? checked.path : ""; } function buildPotPlayerInstallReg(customPlayerPath = "") { const checked = validatePotPlayerCustomPath(customPlayerPath); const hasCustomPath = checked.ok; const command = hasCustomPath ? `"${checked.path}" "%1"` : 'cmd.exe /d /s /c "start "" "PotPlayerMini64.exe" "%1" || start "" "PotPlayerMini.exe" "%1" || start "" "PotPlayer.exe" "%1""'; const defaultIcon = hasCustomPath ? `"${checked.path}",1` : 'PotPlayerMini64.exe,1'; return [ 'Windows Registry Editor Version 5.00', '', '; PikPak Enhancement Master - PotPlayer protocol repair', '; User-level protocol registration only. No browser policy is written.', hasCustomPath ? '; This file binds potplayer:// to the custom PotPlayer executable path below.' : '; This file uses the PotPlayer executable discoverable from the Windows path.', '', '[HKEY_CURRENT_USER\\Software\\Classes\\potplayer]', '@="URL:PotPlayer Protocol"', '"URL Protocol"=""', '', '[HKEY_CURRENT_USER\\Software\\Classes\\potplayer\\DefaultIcon]', `@="${escapeRegString(defaultIcon)}"`, '', '[HKEY_CURRENT_USER\\Software\\Classes\\potplayer\\shell]', '@="open"', '', '[HKEY_CURRENT_USER\\Software\\Classes\\potplayer\\shell\\open]', '@="open"', '', '[HKEY_CURRENT_USER\\Software\\Classes\\potplayer\\shell\\open\\command]', `@="${escapeRegString(command)}"`, '' ].join('\r\n'); } function buildPotPlayerDeleteReg() { return [ 'Windows Registry Editor Version 5.00', '', '; PikPak Enhancement Master - PotPlayer protocol cleanup', '; User-level cleanup only. System-level registrations are not modified.', '', '[-HKEY_CURRENT_USER\\Software\\Classes\\potplayer]', '' ].join('\r\n'); } function buildPotPlayerBrowserPolicyReg() { return [ 'Windows Registry Editor Version 5.00', '', '; PikPak Enhancement Master - optional browser external protocol helper', '; Advanced option only. These HKLM policy keys may require administrator rights.', '; Enterprise, school, or company devices may be controlled by existing policies.', '', '[HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Google\\Chrome]', '"ExternalProtocolDialogShowAlwaysOpenCheckbox"=dword:00000001', '', '[HKEY_LOCAL_MACHINE\\SOFTWARE\\Policies\\Microsoft\\Edge]', '"ExternalProtocolDialogShowAlwaysOpenCheckbox"=dword:00000001', '' ].join('\r\n'); } function makeUtf16LeBlob(text) { const input = String(text || ''); const buffer = new ArrayBuffer(2 + input.length * 2); const view = new DataView(buffer); view.setUint16(0, 0xFEFF, true); for (let i = 0; i < input.length; i++) { view.setUint16(2 + i * 2, input.charCodeAt(i), true); } return new Blob([buffer], { type: 'application/octet-stream' }); } function downloadPotPlayerRegFile(kind) { const isDelete = kind === 'delete'; const isBrowserPolicy = kind === 'browser_policy'; const savedPath = getValidSavedPotPlayerPath(); if (!isDelete && !isBrowserPolicy && !savedPath) return false; const content = isBrowserPolicy ? buildPotPlayerBrowserPolicyReg() : (isDelete ? buildPotPlayerDeleteReg() : buildPotPlayerInstallReg(savedPath)); const blob = makeUtf16LeBlob(content); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = isBrowserPolicy ? CONF.potplayerBrowserPolicyRegFileName : (isDelete ? CONF.potplayerRegDeleteFileName : CONF.potplayerRegCustomInstallFileName); a.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); recordPotPlayerProtocolRegDownload(isBrowserPolicy ? 'browser_policy' : (isDelete ? 'delete' : 'install')); return true; } function getDefaultPotPlayerLaunchState() { return { schemaVersion: CONF.potplayerLaunchStateSchemaVersion, lastLikelyOpenAt: 0, lastLikelyFailAt: 0, consecutiveFailCount: 0, lastFailSource: "", lastCopiedAt: 0, lastLaunchAt: 0 }; } function normalizePotPlayerLaunchState(rawState) { const def = getDefaultPotPlayerLaunchState(); let data = rawState; if (typeof data === 'string') { try { data = data ? JSON.parse(data) : null; } catch (e) { data = null; } } if (!data || typeof data !== 'object' || Array.isArray(data)) return def; const toTime = (v) => { const n = Number(v); return Number.isFinite(n) && n > 0 ? n : 0; }; const toCount = (v) => { const n = parseInt(v, 10); return Number.isFinite(n) && n > 0 ? n : 0; }; return { schemaVersion: CONF.potplayerLaunchStateSchemaVersion, lastLikelyOpenAt: toTime(data.lastLikelyOpenAt), lastLikelyFailAt: toTime(data.lastLikelyFailAt), consecutiveFailCount: toCount(data.consecutiveFailCount), lastFailSource: typeof data.lastFailSource === 'string' ? data.lastFailSource : "", lastCopiedAt: toTime(data.lastCopiedAt), lastLaunchAt: toTime(data.lastLaunchAt) }; } function readPotPlayerLaunchState() { try { return normalizePotPlayerLaunchState(gmGet(CONF.potplayerLaunchStateKey, '')); } catch (e) { return getDefaultPotPlayerLaunchState(); } } function writePotPlayerLaunchState(state) { try { gmSet(CONF.potplayerLaunchStateKey, normalizePotPlayerLaunchState(state)); } catch (e) {} } function normalizePotPlayerLaunchSource(source) { return ['normal', 'error_fallback', 'unknown'].includes(source) ? source : 'unknown'; } function recordPotPlayerLaunchAttempt(source, launchAt = Date.now()) { const state = readPotPlayerLaunchState(); const time = Number.isFinite(Number(launchAt)) && Number(launchAt) > 0 ? Number(launchAt) : Date.now(); state.lastLaunchAt = time; writePotPlayerLaunchState(state); return state; } function recordPotPlayerLaunchResult(result, opts = {}) { const now = Date.now(); const launchAt = Number.isFinite(Number(opts.launchAt)) && Number(opts.launchAt) > 0 ? Number(opts.launchAt) : now; const copiedAt = Number.isFinite(Number(opts.copiedAt)) && Number(opts.copiedAt) > 0 ? Number(opts.copiedAt) : now; const source = normalizePotPlayerLaunchSource(opts.source); const state = readPotPlayerLaunchState(); if (result === 'likely_open') { state.lastLikelyOpenAt = now; state.lastLaunchAt = launchAt; state.consecutiveFailCount = 0; state.lastFailSource = ""; } else if (result === 'likely_fail') { state.lastLikelyFailAt = now; state.lastLaunchAt = launchAt; state.lastCopiedAt = copiedAt; state.consecutiveFailCount += 1; state.lastFailSource = source; } else { return state; } writePotPlayerLaunchState(state); return state; } function getBlurScope() { const legacyBlur = gmGet('pk_blur_thumb', false); const scope = gmGet('pk_blur_scope', legacyBlur ? 'list' : 'off'); return ['off', 'list', 'grid', 'both'].includes(scope) ? scope : (legacyBlur ? 'list' : 'off'); } function isBlurEnabledForView(view) { const scope = getBlurScope(); return scope === 'both' || scope === view; } const calcSha1 = async (file) => { if (!window.crypto || !window.crypto.subtle) return ""; const hexFromBytes = (bytes) => Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase(); const sha1Bytes = async (buffer) => new Uint8Array(await crypto.subtle.digest('SHA-1', buffer)); const calcXunleiCid = async () => { const CID_SAMPLE_SIZE = 0x5000; if (file.size < 0xF000) return hexFromBytes(await sha1Bytes(await file.arrayBuffer())); const head = file.slice(0, CID_SAMPLE_SIZE); const midStart = Math.floor(file.size / 3); const mid = file.slice(midStart, midStart + CID_SAMPLE_SIZE); const tail = file.slice(Math.max(0, file.size - CID_SAMPLE_SIZE), file.size); return hexFromBytes(await sha1Bytes(await new Blob([head, mid, tail]).arrayBuffer())); }; const calcXunleiGcid = async () => { let partSize = 0x40000; while ((file.size / partSize) > 0x200 && partSize < 0x200000) partSize <<= 1; const digests = []; if (file.size === 0) digests.push(await sha1Bytes(new ArrayBuffer(0))); for (let offset = 0; offset < file.size; offset += partSize) { const buffer = await file.slice(offset, Math.min(file.size, offset + partSize)).arrayBuffer(); digests.push(await sha1Bytes(buffer)); } const merged = new Uint8Array(digests.length * 20); digests.forEach((bytes, index) => merged.set(bytes, index * 20)); return hexFromBytes(await sha1Bytes(merged.buffer)); }; const queryGcidByCid = async (cid) => { try { const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/resource/cid?cid=${encodeURIComponent(String(cid || '').toLowerCase())}&file_size=${encodeURIComponent(String(file.size || 0))}`, { method: 'GET', headers: getHeaders() }); if (!res.ok) return ""; const data = await res.json().catch(() => null); return data && data.gcid ? String(data.gcid).toUpperCase() : ""; } catch (e) { console.warn('[Upload] CID precheck failed:', e); return ""; } }; const cid = await calcXunleiCid(); const gcid = await queryGcidByCid(cid); return gcid || await calcXunleiGcid(); }; function getLang(){const u=gmGet('pk_lang','');if(u)return u;const n=navigator.language.toLowerCase();return (n==='zh'||n.startsWith('zh-cn')||n.startsWith('zh-sg'))?'zh':(n.startsWith('zh-tw')||n.startsWith('zh-hk')||n.startsWith('zh-mo'))?'tc':(n.startsWith('id')||n.startsWith('in'))?'id':n.startsWith('ms')?'ms':n.startsWith('ko')?'ko':n.startsWith('ja')?'ja':'en';} const I18N_CONF={defaultLang:'zh',remoteLangs:['tc','en','ko','ja','id','ms'],manifestUrl:'https://cdn.jsdelivr.net/gh/digbug82/PikPak_Enhancement_Master@main/i18n/manifest.json',baseUrl:'https://cdn.jsdelivr.net/gh/digbug82/PikPak_Enhancement_Master@main/i18n/',cachePrefix:'pk_i18n_',manifestKey:'pk_i18n_manifest',cacheTTL:7*24*60*60*1000,manifestTTL:24*60*60*1000,requiredKeys:['title','modal_settings_title','label_lang','msg_settings_saved','btn_nav_home']}; let pkRemoteI18n=null; let pkI18nReadyPromise=null; let pkI18nReadyLang=''; function getI18nCacheKey(lang){return `${I18N_CONF.cachePrefix}${lang}`;} function readI18nCache(lang,expectedVersion=''){try{const raw=gmGet(getI18nCacheKey(lang),'');if(!raw)return null;const parsed=typeof raw==='string'?JSON.parse(raw):raw;if(!parsed||parsed.lang!==lang||!parsed.data||typeof parsed.data!=='object'||Array.isArray(parsed.data))return null;if(parsed.expiresAt&&parsed.expiresAtObject.prototype.hasOwnProperty.call(data,k)));} async function loadI18nManifest(force=false){if(!force){const cached=readI18nManifestCache();if(isValidI18nManifest(cached))return cached;}const manifestUrl=`${I18N_CONF.manifestUrl}${I18N_CONF.manifestUrl.includes('?')?'&':'?'}_t=${Date.now()}`;const manifest=await fetchI18nJson(manifestUrl);if(!isValidI18nManifest(manifest))throw new Error('Invalid i18n manifest');writeI18nManifestCache(manifest);return manifest;} async function loadRemoteLangPack(force=false,langOverride=''){const lang=langOverride||getLang();if(!I18N_CONF.remoteLangs.includes(lang)||!I18N_CONF.manifestUrl){pkRemoteI18n=null;return null;}const manifest=await loadI18nManifest(force);const version=String(manifest.version||'');if(!force){const cached=readI18nCache(lang,version);if(cached){pkRemoteI18n={lang,version:cached.version,data:cached.data};return pkRemoteI18n;}}const file=manifest.files&&manifest.files[lang];if(!file){pkRemoteI18n=null;return null;}const baseUrl=manifest.baseUrl||I18N_CONF.baseUrl;const fileUrl=/^https?:\/\//.test(file)?file:`${baseUrl}${file}`;const data=await fetchI18nJson(`${fileUrl}${fileUrl.includes('?')?'&':'?'}_v=${encodeURIComponent(version)}`);if(!isValidI18nPayload(data))throw new Error('Invalid i18n payload');writeI18nCache(lang,version,data);pkRemoteI18n={lang,version,data};return pkRemoteI18n;} function ensureI18nReady(force=false,langOverride=''){const lang=langOverride||getLang();if(force||!pkI18nReadyPromise||pkI18nReadyLang!==lang){pkI18nReadyLang=lang;pkI18nReadyPromise=loadRemoteLangPack(force,lang).catch(()=>null);}return pkI18nReadyPromise;} async function ensureI18nReadyBeforeOpen(langOverride=''){const lang=langOverride||getLang();if(lang==='zh')return null;try{return await Promise.race([ensureI18nReady(false,lang),sleep(4000).then(()=>null)]);}catch(e){return null;}} const T_LOCAL = { zh: { title: "PikPak 增强大师", str_original: "原画", str_folders: "文件夹", str_files: "文件", unit_folders: "个文件夹", unit_days: "天", unit_month: "月", unit_sec: "秒", str_no_files: "暂无文件", str_items: "项", col_name: "名称", col_size: "大小", col_dur: "类型/时长", col_duration_only: "时长", col_progress: "播放进度", col_play_time: "播放时间", col_date: "修改日期", col_remaining: "剩余时长", col_path: "路径", col_old: "原名称", col_new: "新名称", col_type: "类型", lbl_folder_first: "文件夹置顶", tag_default: "默认", current_dir: "当前目录", str_same_folder: "(同文件夹)", lbl_dont_show: "不再提醒", lbl_dont_show_session: "本次查重不再提示", str_empty_filename: "(空文件名)", str_empty_dir: "(空目录)", btn_filter: "筛选", title_file_filter: "文件筛选", cat_all: "全部", cat_video: "视频", cat_audio: "音频", cat_image: "图片", cat_document: "文档", cat_software: "软件", cat_archive: "压缩包", cat_torrent: "BT种子", cat_other: "其他", btn_exit_filter: "退出筛选", ctx_property: "属性", title_property: "文件属性", lbl_prop_name: "文件名称", lbl_prop_size: "文件大小", lbl_prop_count: "文件数量", lbl_prop_ctime: "创建时间", lbl_prop_mtime: "修改时间", lbl_prop_source: "添加来源", lbl_prop_link: "资源链接", lbl_prop_path: "文件位置", str_prop_cloud: "云添加", str_prop_share: "来自分享", str_prop_user: "用户上传", str_prop_unknown: "未知来源", fmt_prop_count: "包含 {f} 个文件,{d} 个文件夹", str_prop_offline: "离线任务", btn_nav_home: "主页", btn_nav_share: "我的分享", btn_nav_offline: "离线下载", btn_nav_recent: "最近添加", btn_nav_history: "播放历史", btn_nav_starred: "收藏夹", btn_nav_trash: "回收站", menu_share_parse: "分享解析", title_share_parse: "分享解析", tip_share_preview_limit_end: "预览范围的末尾", desc_share_parse_space_notice: "解析分享内容,未保存前不占用存储空间", msg_share_preview_limited_save: "分享预览可能存在播放限制,保存到我的 PikPak 后可完整播放", msg_share_preview_soft_limit: "后续内容无法保证播放,保存到我的 PikPak 后可完整观看", msg_share_ext_preview_limited_save: "外部播放视频可能不完整,保存到我的 PikPak 后可完整播放", ph_share_link: "请输入 PikPak 分享链接", ph_share_pass: "提取码(无则留空)", btn_share_parse: "解析", btn_share_parse_loading: "解析中...", btn_share_parse_clipboard: "从剪切板导入", msg_share_clipboard_empty: "剪切板为空", msg_share_clipboard_no_link: "剪切板中未找到有效分享链接", msg_share_clipboard_denied: "无法读取剪切板,请手动粘贴", msg_share_parse_invalid_link: "分享链接无效", msg_share_parse_empty_link: "请输入分享链接", msg_share_parse_success: "分享解析成功", msg_share_parse_failed: "分享解析失败", msg_share_parse_bad_pass: "提取码错误", msg_share_parse_expired: "分享已失效或已过期", msg_share_parse_network_error: "网络异常,分享解析失败", msg_share_parse_auth_error: "登录态或权限异常,请重新登录后再试", msg_share_parse_not_found: "分享不存在", msg_share_parse_cancelled: "分享已被取消", msg_share_parse_unknown_error: "接口返回未知错误,分享解析失败", msg_share_parse_need_parse_first: "请先解析分享链接", msg_share_parse_loading_files: "正在加载分享文件列表...", msg_share_parse_load_failed: "分享文件列表加载失败", msg_share_parse_token_missing: "未获取到分享访问凭证", msg_share_parse_token_expired: "分享访问凭证已失效,请重新解析", msg_share_parse_detail_failed: "分享目录加载失败", label_share_parse_root: "分享根目录", label_share_parse_path_root: "分享解析", btn_share_parse_save_selected: "保存选中项到我的 PikPak", btn_share_parse_saving: "保存中...", msg_share_parse_save_progress: "正在保存 {done}/{total} 项...", msg_share_parse_save_task_progress: "等待保存任务完成 {done}/{total} 个...", msg_share_parse_select_first: "请先选择要保存的分享文件或文件夹", msg_share_parse_save_need_token: "未获取到分享访问凭证,请重新解析", msg_share_parse_save_need_list: "请先加载分享目录", msg_share_parse_save_start: "正在保存到我的 PikPak...", msg_share_parse_save_success: "已保存到我的 PikPak", msg_share_parse_save_failed: "保存到我的 PikPak 失败", msg_share_parse_save_partial_failed: "部分文件保存失败", msg_share_parse_save_space_not_enough: "空间不足,无法保存", msg_share_parse_save_token_expired: "分享访问凭证已失效,请重新解析", msg_share_parse_save_share_expired: "分享已失效或已过期", msg_share_parse_save_cancelled: "分享已取消或删除", msg_share_parse_save_too_many: "文件数量过多,无法保存", msg_share_parse_save_task_failed: "保存任务失败", msg_share_parse_save_auth_error: "登录态已失效,请重新登录后再试", msg_share_parse_save_network_error: "网络异常,保存到我的 PikPak 失败", msg_share_unzip_no_access: "未获取到分享压缩包访问凭证,请重新解析分享。", label_share_parse_selected_count: "已选择 {n} 项", btn_share_parse_insight: "分享文件透视", btn_share_parse_stop_scan: "停止扫描", btn_share_parse_back_to_list: "返回分享目录", msg_share_parse_scan_done: "当前分享全部文件扫描完成", msg_share_parse_scan_failed: "分享文件扫描失败", msg_share_parse_scan_empty: "未扫描到分享文件", msg_share_parse_scan_need_list: "请先加载分享文件列表", msg_share_parse_scan_progress: "已扫描 {folder} 个文件夹,发现 {file} 个文件", btn_nav_upload: "我的上传", title_offline: "我的离线", trash_title: "回收站", trash_notice: "回收站的文件最多保存15天", trash_remaining_today: "今日过期", trash_remaining_unknown: "无法计算", ctx_open: "打开", ctx_add_bl: "添加到资源管理器", ctx_remove_bl: "从资源管理器移除", ctx_rename: "重命名", ctx_copy: "复制", ctx_copy_name: "复制文件名", ctx_copy_link: "复制链接", ctx_del: "删除", ctx_down: "下载", ctx_star: "添加星标", ctx_unstar: "取消星标", ctx_locate: "在文件夹中查看", ctx_share: "分享", btn_down: "下载", tip_down: "下载 [Alt] + [D]", btn_aria2: "发送 Aria2", tip_aria2: "发送 Aria2 [Alt] + [A]", btn_refresh_short: "刷新", tip_refresh: "刷新 [F5]", btn_newfolder: "新建文件夹", tip_newfolder: "新建文件夹 [F8]", btn_del: "删除", tip_del: "删除 [Delete]", btn_deselect: "取消选择", tip_deselect: "取消选择 [Esc]", btn_invert: "反选", btn_copy: "复制", tip_copy: "复制 [Ctrl] + [C]", btn_cut: "移动", tip_cut: "移动 [Ctrl] + [X]", btn_paste: "粘贴", tip_paste: "粘贴 [Ctrl] + [V]", btn_clear_history: "删除历史", tip_clear_history: "删除历史 [Delete]", btn_clear_all_history: "清空全部", tip_clear_all_history: "清空全部播放历史 [Shift] + [Delete]", btn_restore: "还原", tip_restore: "还原 [R]", btn_del_forever: "永久删除", tip_del_forever: "永久删除 [Delete]", btn_empty_trash: "清空回收站", tip_empty_trash: "清空回收站 [Shift] + [Delete]", btn_exit: "退出", btn_close: "关闭", txt_preview_title: "TXT 预览", txt_preview_loading: "正在加载文本...", txt_preview_too_large: "文件过大,请下载后在本地查看。", txt_preview_load_failed: "TXT 内容加载失败,请稍后重试。", msg_open_download_after: "请下载后打开", txt_preview_decode_failed: "文本编码解析失败,无法预览该文件。", txt_preview_empty: "文本内容为空。", txt_preview_close: "关闭", txt_preview_fullscreen: "网页全屏", txt_preview_exit_fullscreen: "退出网页全屏", txt_preview_parse_links: "解析磁链", txt_preview_parse_links_working: "正在提交云下载...", txt_preview_no_links: "没有识别到可下载链接", txt_preview_links_submitted: "已识别 {n} 个链接,已提交云下载", txt_preview_links_failed: "TXT 链接提交云下载失败", tip_close: "关闭 [Esc]", tip_theme: "切换主题 [Alt] + [T]", tip_rotate: "旋转 [R]", tip_mirror: "镜像翻转 [H]", tip_flip_v: "垂直翻转 [V]", tip_maximize: "最大化 [M]", tip_minimize: "最小化 [M]", tip_web_fullscreen: "网页全屏", tip_exit_web_fullscreen: "退出网页全屏", tip_full_screen: "全屏 [Enter]", tip_star_project: "喜欢这个项目?Star 支持一下。", btn_view_file: "查看文件", btn_copy_text: "复制", btn_stop: "停止", btn_exit_script: "返回登录界面", btn_settings: "设置", btn_logout: "退出 PikPak", msg_logout_confirm: "确定要退出登录吗?", tip_settings: "设置和更多 [Alt] + [S]", tip_transfer_quota_detail: "传输配额详情", modal_transfer_quota_title: "传输配额详情", transfer_quota_desc: "传输配额包括每个账号每月享有的传输流量,以及非会员每日的下行限额,通常足以满足常规使用。这里显示了您的传输配额使用详情。", transfer_quota_monthly: "每月传输配额", transfer_quota_cloud_download: "云下载流量", transfer_quota_download: "下行流量", transfer_quota_upload: "上传流量", transfer_quota_daily_non_vip: "每日限额 · 非会员", transfer_quota_download_note: "包括在线播放、查看和下载的传输", transfer_quota_used_total: "已用 {u} / 共 {t}", err_transfer_quota_fetch: "获取传输配额详情失败。", lbl_upload_to: "上传文件至: ", msg_move_done: "移动完成。", msg_upload_dup_confirm: "发现 {n} 个名称与大小相同的文件,是否跳过?", btn_upload: "本地上传", btn_up_file: "上传文件", btn_up_folder: "上传文件夹", btn_cloud_download: "云下载", btn_up_pause: "暂停任务", tip_up_pause: "暂停任务 [Alt] + [P]", btn_up_start: "开始任务", tip_up_start: "开始任务 [Alt] + [G]", btn_up_del: "删除任务", tip_up_del: "删除任务 [Delete]", btn_up_clear_all: "清空任务", tip_up_clear_all: "清空任务 [Shift] + [Delete]", tip_upload_official_fallback: "上传异常时建议使用官方上传", btn_retry_task: "重试任务", tip_retry_task: "重试任务 [R]", col_task_status: "任务状态", col_task_progress: "离线进度", col_up_speed: "速度", col_up_status: "状态", lbl_task_run: "进行中", lbl_task_fail: "已失败", lbl_task_ok: "已完成", lbl_up_run: "进行中", lbl_up_pause: "已中断", lbl_up_downloading: "下载中", lbl_up_done: "已完成", tip_up_pause_desc: "包含手动暂停及报错的任务", title_cloud_task: "创建云下载任务", ph_cloud_links: "支持链接格式:\n- 各种下载链接,如magnet。\n- YouTube、X (Twitter)、TikTok、Facebook等分享链接。\n通过换行可一次性添加多条链接。", lbl_save_to: "文件将被保存至:", lbl_default_folder: "默认文件夹", btn_via_torrent: "通过 Torrent 创建", tip_cloud_save_path: "常规云下载文件会保存在 My Pack 目录,来自其他 App 的云下载文件会保存至 My [XYZ] 目录,[XYZ] 为 App 名称。", lbl_smart_fix: "自动修复防屏蔽磁链 (提取特征码/剔除文字干扰)", title_save_method: "保存方式", msg_save_snapshot_desc: "此链接只能被存储为网页快照。", tip_snapshot_details: "PikPak 不能从此链接中直接采集媒体文件,您可以保存网页快照。PikPak 将尽可能保存完整的网页内容到快照文件中。", btn_save_snapshot: "保存快照", btn_create_now: "立即创建", btn_modify: "修改", str_snap_link_count_suffix: " 等 {n} 个链接", btn_cancel_share: "取消分享", share_copy_suffix: "复制这段内容后打开 PikPak-App,畅享极速秒播", share_copy_pwd: "密码", title_share_detail: "分享详情", ctx_share_detail: "查看分享详情", ctx_share_copy: "复制链接和密码", col_view: "浏览", col_save: "保存", col_share_time: "分享时间", col_share_status: "分享状态", lbl_limit_reached: "次数已满", lbl_limit_tip: "限制次数", lbl_share_view: "浏览", lbl_share_save: "保存", lbl_share_link_title: "分享链接", lbl_share_pwd_title: "密码", lbl_share_expire_title: "有效期", btn_copy_link_pwd: "复制链接和密码", str_expire_suffix: "天后过期", ph_edit_pwd: "输入分享密码,支持 4-10 位", btn_close_pwd: "关闭密码", str_no_pwd: "无密码", title_edit_share_code: "分享代码修改", ph_edit_share_code: "支持 5-18 位文字、数字、及符号等", btn_add_share_code: "添加分享代码", btn_del_share_code: "删除分享代码", share_title: "分享文件", share_mode: "分享方式", share_public: "公开链接", share_encrypted: "加密链接", share_expiry: "设置有效期", share_pass: "设置提取码", share_count: "设置提取次数", share_count_ed: "提取次数", share_perm: "永久有效", share_unlimit: "不限", share_rand: "系统随机", share_custom: "自定义", share_days: "天", share_times: "次", btn_share_start: "立即分享", cal_custom_title: "自定义有效期", lbl_share_code: "提取码", btn_copy_share: "复制全部", str_share_expired: "已过期", str_share_deleted: "文件已删", title_edit_pwd: "密码修改", lbl_share_code_title: "分享代码", btn_migrate: "数据迁移", tip_migrate: "打包选中项并准备迁移至新账号 [Alt] + [M]", btn_export_m3u: "导出 M3U", tip_export_m3u: "导出选中视频的 M3U 播放列表 [Alt] + [L]", msg_m3u_no_video: "未选中视频", msg_m3u_export_limit: "一次最多导出 {n} 个视频", msg_m3u_export_progress: "正在导出 M3U {done}/{total}...", msg_m3u_share_limited_save: "视频可能不完整,保存到我的 PikPak 后可完整播放", msg_m3u_no_valid_link: "没有可导出的视频直链", msg_m3u_export_done: "M3U 已导出:{n} 个视频", msg_m3u_export_partial: "M3U 已导出:{n} 个视频,跳过 {s} 个", msg_migrate_confirm: "确定要将选中的 {n} 个项目迁移至其他账号吗?\n\n打包成功后,会自动退出。\n登录【目标账号】以完成接收。", msg_migrate_packing: "正在构建加密迁移包...", msg_migrate_ready: "✅ 迁移包已就绪!\n\n即将退出当前账号,请登录您的【目标账号】。\n重新登录后系统将自动接管转存工作。\n(注:迁移数据包将在 1 天后自动过期失效)", err_migrate_ban: "打包失败:选中的内容中包含违规或被官方限制分享的资源。\n请剔除违规文件后重试。", msg_migrate_detect: "📦 检测到来自另一个账号的迁移数据包!\n\n共包含 {n} 个项目。\n接收后将保存到以下路径:\n主页 / Pack From Shared\n若该文件夹不存在,将自动创建。\n\n是否立即开始迁移?\n选择“是”将执行迁移;选择“否”将取消本次迁移。", msg_migrate_same_account: "迁移已取消:您登录的仍然是原账号,存根已清除。", msg_migrate_saving: "正在从加密通道高速转存数据...", msg_migrate_success: "🎉 跨账号迁移完成!\n所有文件已成功转存至当前账号。", ph_pass_range: "4-10位字符", cal_week_days:["日", "一", "二", "三", "四", "五", "六"], cal_months:["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"], title_file_analysis: "文件分析", btn_scan: "文件透视", lbl_scan_selected: "对选中的 {n} 个项目执行文件透视", lbl_keyword_filter: "排除关键词", ph_keyword_filter: "排除包含的关键词,多个用逗号分隔", lbl_scan_current: "对当前路径下所有项目执行文件透视", tip_dup: "文件查重", lbl_dup_selected: "对选中的 {n} 个项目执行文件查重", lbl_dup_current: "对当前路径下所有项目执行文件查重", tip_scan_dup: "筛选或查重文件", lbl_dup_reset: "↺ 复原 (取消置顶 & 清空选择)", lbl_dup_select_folder: "📂 按文件夹选择", lbl_dup_select_folder_short: "📂 文件夹", lbl_dup_invert: "反选模式", lbl_dup_invert_short: "反选", tip_dup_invert_limit: "仅按文件夹选择可用", fmt_dup_count: "({n}个重复)", tag_hash: "精准匹配", tag_hash_short: "精准", tag_name: "名称相似", tag_name_short: "名称", tag_sim: "时长相似", tag_sim_short: "时长", label_dup_video: "视频文件 (精准匹配 + 时长相似 + 名称相似)", label_dup_image: "图片文件 (精准匹配 + 名称相似)", label_dup_other: "其他文件 (精准匹配 + 名称相似)", btn_analyze: "文件夹分析", tip_analyze: "筛选或查重文件夹", btn_export: "导出目录", tip_export: "生成并下载当前路径的文件树列表", title_export_format: "导出目录样式", lbl_export_current: "对当前路径下所有项目执行目录导出", opt_tree_view: "目录树", opt_list_view: "目录列表", opt_grid_view: "网格视图", msg_exporting: "正在生成目录树...", str_analyze_results: "匹配结果", opt_ana_large: "文件夹透视", lbl_analyze_selected: "对选中的 {n} 个项目执行文件夹透视", lbl_analyze_current: "对当前路径下所有项目执行文件夹透视", opt_ana_sim: "文件夹查重", lbl_ana_sim_selected: "对选中的 {n} 个项目执行文件夹查重", lbl_ana_sim_current: "对当前路径下所有项目执行文件夹查重", title_algo_help: "查重算法说明", algo_help_content: "名称匹配:查找名称和体积相近的文件夹群组。\n相似度匹配:查找内部文件高度重合的文件夹群组。\n包含率匹配:查找小文件夹的内容被大文件夹完全覆盖的子集冗余。\n\n精度:相似度匹配 > 包含率匹配 > 名称匹配\n范围:包含率匹配 > 相似度匹配", lbl_threshold: "阈值", lbl_sim_score: "相似度", lbl_containment: "包含率", lbl_name_match: "名称匹配", lbl_sim_match: "相似度匹配", lbl_contain_match: "包含率匹配", lbl_ana_min: "下限", lbl_ana_max: "上限", btn_prune: "清理空文件夹", tip_prune: "清理空文件夹 [Ctrl] + [Delete]", btn_rename: "重命名", tip_rename: "重命名 [F2]", btn_bulkrename: "批量重命名", tip_bulkrename: "批量重命名 [F2]", title_blacklist: "资源管理器", btn_blacklist_run: "立即运行清理", btn_clear_list: "清空列表", tip_bl_desc: "下列项目在【删除】时会跳过,仅通过【立即运行清理】查找删除", tip_blacklist_input: "资源管理器 [Alt] + [Delete]", label_bl_folder: "文件夹名单 (精准查找)", label_bl_file: "文件名单 (精准查找)", lbl_type_folder: "文件夹", lbl_type_file: "文件", ph_bl_file: "请通过“粘贴”或文件右键菜单“添加到资源管理器”导入。", modal_bl_preview: "检索结果", btn_bl_delete: "删除选中项", modal_rename_title: "重命名", modal_rename_multi_title: "批量重命名", label_replace: "替换/删除", label_replace_note: "区分大小写", label_include_ext: "包含后缀", label_regex: "正则 (Regex)", placeholder_find: "查找内容", placeholder_replace: "替换为 (留空删除)", label_jav: "FC2 纯净命名", lbl_rn_pattern: "命名模板", lbl_rn_case_convert: "大小写转换", opt_rn_lower: "全部小写 (abc)", lbl_rn_mode_series: "剧集模式", lbl_rn_mode_format: "格式化", lbl_rn_mode_ad: "前缀去广告", lbl_rn_mode_ext: "后缀修复", opt_rn_upper: "全部大写 (ABC)", opt_rn_title: "首字母大写 (Abc)", lbl_rn_width_convert: "全半角转换", opt_rn_width_half: "全角转半角 (A->A)", opt_rn_width_full: "半角转全角 (A->A)", lbl_rn_preview_title: "变更预览", tip_jav_mode_desc: "✨ 智能提取FC2并去除无关字符", tip_ad_remove_desc: "🧹 智能滤除头部广告、网址及垃圾符号,自动清洗Emoji并修复括号格式", tip_ext_fix_desc: "🧩 根据文件真实类型 (MIME) 智能修正后缀", label_replace_find: "查找内容", label_replace_to: "替换为", btn_unzip: "批量解压", tip_unzip: "批量解压 [Alt] + [U]", btn_unzip_all: "全部解压", btn_unzip_selected: "解压选中 {n} 项", btn_understand_unzip: "理解并解压", title_input_pwd: "需要解压密码", lbl_pwd_prompt: "请输入密码:", btn_ext: "外部播放", tip_ext: "使用PotPlayer播放或获取播放链接 [Alt] + [E]", btn_img_search: "以图搜图 [F]", tip_pip: "画中画 [P]", str_no_sub: "无字幕", lbl_sub_sel: "字幕选择", lbl_show_sub: "显示字幕", btn_sub_search: "搜索在线字幕", btn_sub_cloud: "打开云盘字幕", btn_sub_local: "打开本地字幕", lbl_sub_pos: "字幕位置", lbl_sub_bottom: "底部", lbl_sub_top: "顶部", lbl_sub_bg_op: "背景透明", lbl_sub_size: "字幕大小", lbl_sub_offset: "字幕进度", title_sel_sub: "选择字幕", ph_sub_search: "输入关键词,下方链接自动更新...", str_compat_mode: "兼容模式", lang_code: "zh", btn_go_search: "🔍 去 {n} 手动搜", btn_restart: "从头播放", btn_prev_video: "上一个 [Ctrl + ←]", btn_next_video: "下一个 [Ctrl + →]", tip_plist_open: "展开 [E]", tip_plist_close: "收起 [E]", tab_sub: "字幕", tab_size: "尺寸", tab_more: "更多", lbl_ratio: "比例", lbl_direction: "方向", opt_ratio_def: "默认", btn_rot_l: "向左旋转", btn_rot_r: "向右旋转", btn_flip_h: "水平翻转", btn_flip_v: "垂直翻转", lbl_play_end: "当播放结束", opt_list_loop: "列表循环", opt_single_loop: "单集循环", opt_play_stop: "播完暂停", lbl_skip_op: "跳过片头", lbl_skip_ed: "跳过片尾", btn_start_play: "开始播放", btn_copy_link: "复制链接", opt_player_script: "脚本播放器", opt_player_potplayer: "PotPlayer", msg_potplayer_launching: "正在唤起 PotPlayer...", msg_potplayer_maybe_failed: "可能未唤起 PotPlayer,播放链接已复制,可手动粘贴到播放器。", msg_potplayer_link_copied: "链接已复制", potplayer_fix_entry: "PotPlayer 协议修复", potplayer_fix_title: "PotPlayer 协议修复助手", potplayer_fix_desc: "如果点击 PotPlayer 后没有响应,通常是系统没有正确注册 potplayer:// 协议,或协议指向旧路径。请按下面步骤填写 PotPlayer 程序路径并生成修复文件。", potplayer_fix_manual_tip: "如果曾经修复过错误路径,请先下载并执行清理旧协议文件。\n推荐流程:填写 PotPlayer 程序路径 → 保存路径 → 下载协议修复文件 → 手动执行 .reg → 点击“已执行修复文件,重新尝试”。", potplayer_fix_copy_link: "复制播放链接", potplayer_fix_download_delete: "下载清理旧协议文件", potplayer_fix_i_have_fixed: "已执行修复文件,重新尝试", potplayer_fix_close: "关闭", potplayer_fix_copy_success: "播放链接已复制", potplayer_fix_install_downloaded: "协议修复文件已下载,请双击执行后再点击重新尝试", potplayer_fix_delete_downloaded: "旧协议清理文件已下载,请双击执行后再重新生成修复文件", potplayer_fix_confirmed: "已确认执行修复文件,正在重新尝试", potplayer_fix_skip_today: "今日不再提示", potplayer_fix_never_auto: "不再自动提示", potplayer_fix_reason_auto: "已连续多次未检测到 PotPlayer 打开信号", potplayer_fix_skip_today_done: "今日不再自动提示", potplayer_fix_never_auto_done: "已关闭自动提示", potplayer_fix_auto_title: "PotPlayer 可能需要协议修复", potplayer_fix_advanced_desc: "请填写 PotPlayer.exe、PotPlayerMini64.exe 或 PotPlayerPortable.exe 的完整路径,不要填写其他程序。", potplayer_fix_path_label: "PotPlayer 程序路径", potplayer_fix_path_placeholder: "例如 C:\\PotPlayer\\PotPlayerMini64.exe", potplayer_fix_save_path: "保存路径", potplayer_fix_path_saved: "路径已保存,请重新下载并执行协议修复文件", potplayer_fix_path_invalid: "请输入有效的 PotPlayer exe 路径", potplayer_fix_retry_need_path: "请先填写 PotPlayer 程序路径,并下载执行协议修复文件", potplayer_fix_path_changed: "路径已变更,修复状态已失效,请重新下载并执行协议修复文件", potplayer_fix_download_custom_install: "下载协议修复文件", potplayer_fix_version_outdated: "已保存的修复记录较旧,建议重新生成协议修复文件", potplayer_fix_browser_policy: "下载兜底修复文件", potplayer_fix_browser_policy_desc: "兜底修复文件仅当已执行协议修复文件、PotPlayer 本体能正常打开,但浏览器中点击 PotPlayer 仍无响应时使用。可能需要管理员权限。", potplayer_fix_browser_policy_downloaded: "浏览器协议确认修复文件已下载", potplayer_fix_path_status_empty: "请先填写 PotPlayer 程序路径", potplayer_fix_path_status_confirmed: "已记录当前路径,请确认已执行修复文件后重新尝试", potplayer_fix_path_status_unconfirmed: "路径已保存,请下载并执行协议修复文件", tip_copy_link: "复制链接 [Alt] + [C]", opt_player_other: "其他 (导出链接)", lbl_player: "播放器", btn_mark: "标记", lbl_resolution: "清晰度", str_switch_compat: "当前清晰度不可用,已为您恢复至 {n}", type_img: "图像", type_doc: "文档", type_archive: "压缩文件", type_sub: "字幕文件", type_app: "应用程序", type_suffix: "文件", label_turbo_mode: "极速模式", desc_turbo_mode: "自动开启并替代网页界面 (推荐)", lbl_aria2_status: "RPC 测试", ph_aria2_secret: "密钥 (选填)", str_connected: "连接成功", str_conn_fail: "连接失败", str_connecting: "正在测试...", tip_mixed_content: "常用 RPC 端口:\n• 6800 (Aria2 默认)\n• 16800 (Motrix 默认)", picker_title: "选择文件夹", picker_all: "全部文件", picker_new: "新建文件夹", picker_sort_new: "更新", trash_sort_more_to_less: "剩余时长更多", trash_sort_less_to_more: "剩余时长更少", picker_sort_star_new: "星标·更新", picker_sort_star_old: "星标·更旧", picker_sort_type_dur_asc: "类型/时长·顺序", picker_sort_type_dur_desc: "类型/时长·逆序", picker_sort_duration_asc: "时长·顺序", picker_sort_duration_desc: "时长·逆序", picker_sort_progress_more: "播放更多", picker_sort_progress_less: "播放更少", picker_sort_old: "更旧", picker_sort_large: "更大", picker_sort_small: "更小", picker_sort_path_asc: "路径顺序", grid_folder_count: "{n}项", picker_sort_path_desc: "路径逆序", title_select_file: "选择文件", placeholder_search: "搜索文件...", placeholder_search_short: "搜索", title_search_hist: "搜索历史", btn_clear_hist: "清空", lbl_global_search: "全盘搜索", lbl_search_path: "搜索包含路径", lbl_search_path_short: "路径", str_search_results: "搜索结果", modal_settings_title: "设置", label_lang: "语言 (Language)", label_keep_pos: "保持浏览位置 (返回时定位)", label_sort_pref: "排序偏好", opt_sort_indep: "每个文件夹独立", opt_sort_global: "全部相同", desc_sort_indep: "调整排序方式仅针对当前文件夹生效", desc_sort_global: "全部文件夹使用相同的排序", label_view_pref: "视图模式偏好", opt_view_indep: "每个文件夹独立", opt_view_global: "全部相同", desc_view_indep: "调整视图仅对当前文件夹生效", desc_view_global: "全部文件夹使用相同的列表/网格视图", label_search_engine: "搜图引擎", opt_engine_google: "Google Lens (综合)", opt_engine_yandex: "Yandex (综合)", opt_engine_saucenao: "SauceNAO (Pixiv/插画)", opt_engine_tracemoe: "trace.moe (动漫截图)", lbl_video_playback_settings: "视频播放设置", label_default_open_player: "默认打开方式", label_default_video_quality: "默认视频清晰度/外部播放清晰度", label_video_load_progress_cache: "打开视频时读取播放进度", opt_quality_1080p: "1080P", opt_quality_720p: "720P", opt_quality_480p: "480P", label_dup_strictness: "相似匹配阈值", opt_strict: "严格", opt_loose: "宽松", desc_comic_mode: "纯图片或纯视频文件夹默认 A-Z 排序", label_aria2_url: "Aria2 地址", label_aria2_token: "Aria2 密钥", label_aria2_config: "Aria2 配置", label_aria2_keep_structure: "下载保存文件夹内部结构", label_download_accel_enable: "自定义下载加速域名", label_download_accel_domain: "下载加速域名", desc_download_accel_domain: "下载直链使用自定义加速域名", label_download_accel_mode: "反代改写模式", label_download_accel_mode_prefix: "前缀完整原始链接", label_download_accel_mode_query: "URL 参数传递", label_download_accel_query_param: "URL 参数名", msg_download_accel_invalid_domain: "下载加速域名无效", str_aria2_zero_byte_skipped: "0KB 文件已跳过", label_privacy_mode: "隐私图", label_blur_cover: "模糊媒体封面缩略图", label_hide_button_text: "隐藏按钮文字", desc_hide_button_text: "只保留图标与悬浮提示", opt_privacy_off: "关闭", opt_privacy_list: "仅列表视图", opt_privacy_grid: "仅网格视图", opt_privacy_both: "列表和网格视图", label_dl_filter_ext: "下载后缀过滤 (例: .txt, .jpg)", label_dl_filter_name: "下载名称过滤 (关键词或全名)", lbl_dl_filter: "下载过滤", desc_dl_filter: "浏览器下载/Aria2推送时自动排除匹配的文件", lbl_config_manage: "配置管理", btn_export_data: "导出备份", btn_import_data: "导入备份", btn_clean_data: "清除本地数据", title_clean_data: "选择清理项目", msg_clean_confirm: "确定要彻底删除选中的本地数据吗?此操作无法撤销。", msg_clean_success: "本地数据已清理,页面即将刷新...", opt_cfg_index: "全盘索引 (已同步的目录结构/文件快照)", opt_cfg_pref: "偏好设置 (UI 外观/操作习惯/排序偏好)", opt_cfg_rules: "管理规则 (资源管理器/分享次数限制/搜索记录/下载规则)", opt_cfg_vault: "密码金库 (解压密码记忆)", opt_cfg_history: "视频缓存 (视频播放进度/视频时长缓存)", opt_cfg_cache: "运行缓存 (文件夹修改时间/最后浏览位置/指纹)", msg_import_confirm: "导入的配置将与当前设置合并(合并名单/记录,覆盖冲突的基础设置),是否继续?", msg_import_success: "配置导入成功,页面即将刷新...", err_invalid_config: "无效的配置文件:未检测到指纹标识或格式错误", err_json_format: "文件解析失败:JSON 语法错误或文件已损坏", lbl_storage: "存储空间", lbl_browse_exp: "浏览体验", lbl_skip_bl_on_del: "删除时跳过管理器中记录资源", lbl_pwd_manage: "解压密码管理", title_pwd_vault: "密码金库", lbl_pwd_try_count: "单个压缩包密码匹配上限", tip_pwd_manual: "每行记录一个密码,回车换行", str_root_dir_cn: "根目录", btn_ana_select: "一键勾选", btn_in_group_sort: "组内排序", opt_sort_time: "时间", opt_sort_size: "大小", opt_sort_path: "路径", opt_sort_name: "名称", opt_keep_new: "保留最新的", opt_keep_old: "保留最旧的", opt_keep_large: "保留最大的", opt_keep_small: "保留最小的", opt_keep_short: "保留名称最短的", opt_keep_long: "保留名称最长的", label_clipboard_magnet_focus: "剪贴板磁链识别", desc_clipboard_magnet_focus: "回到前台时检测剪贴板磁链", msg_magnet_preview_desc: "已识别到剪贴板磁链,确认后将直接创建云下载任务。", msg_magnet_preview_fail: "未获取到公开预览信息,仍可继续添加。", str_magnet_unknown_name: "未知资源", str_no_preview: "暂无预览图", lbl_magnet_count: "文件数", lbl_magnet_size: "总大小", lbl_magnet_type: "类型", lbl_magnet_hash: "磁链", btn_magnet_continue: "高速云下载", lbl_magnet_preview_source: "预览信息来自", msg_magnet_preview_rate_limited: "预览服务请求过于频繁,已临时降级。仍可继续添加。", msg_magnet_preview_timeout: "预览服务响应超时,仍可继续添加。", msg_magnet_preview_network: "预览服务暂不可用,仍可继续添加。", loading: "加载中...", loading_detail: "正在全速索引目录结构...", loading_dup: "分析重复项... ({p}%)", str_loading_placeholder: "加载中...", str_load_failed: " (加载失败)", str_load_failed_simple: "加载失败", str_waiting_token: "正在同步登录状态...", str_speed: "速度", status_scanning_selection: "扫描选中项... {n}", status_ready: "{n} 个项目", sel_count: "已选择 {n} 个项目", str_cached: "已缓存:", str_retries: "重试:", str_failed: "失败:", str_success: "成功:", str_stopping: "正在停止...", str_merging: "合并数据中...", str_rendering: "渲染列表中...", str_scanning: "扫描中...", str_analyzing: "分析中...", str_deleting: "删除中...", str_saving: "保存中...", str_checking_bl: "匹配名单记录中...", str_processing: "系统正在全速处理中...", str_cleanup_done: "清理完成。", str_copying: "复制到剪贴板...", str_moving: "准备移动...", str_sorting: "正在排序...", str_refreshing: "刷新中...", str_refreshing_cache: "刷新缓存中...", str_group: "组", str_init_rename: "初始化重命名...", str_renaming: "重命名中...", str_calc_changes: "计算变更中...", str_scanning_dir: "扫描目录结构...", str_init_op: "初始化操作...", str_init_scan: "正在初始化全盘扫描...", str_upload_1: "正在上传 (节点 1/3)...", str_upload_2: "节点1超时,切换节点 2...", str_upload_3: "节点2超时,尝试最后节点...", str_upload_fail_copy: "上传失败,准备写入剪贴板...", str_preparing: "准备解压...", str_unzipping: "正在解压: {n}", str_unzipping_state: "解压中...", str_unzipping_prog_0: "解压中: 0%", str_unzipping_prog_100: "解压中: 100%", str_unzipping_prog_fmt: "解压中: {n}%", msg_task_waiting: "等待中...", msg_task_hashing: "校验文件中...", msg_task_init_upload: "初始化上传...", msg_task_uploading: "正在上传...", msg_task_init_part: "初始化分片...", str_loc_tracing: "正在回溯路径...", str_loc_stale: "缓存已过期,正在同步云端...", str_verifying: "正在验证...", str_server_indexing: "服务器索引中... ({n}/5)", str_creating_task_n: "创建任务 ({n}/{t})...", msg_submit_request: "正在提交请求... {c}/{t}", msg_wait_server: "等待服务器处理... ({c}/{t})", msg_server_processing: "服务器处理中... ({c}/{t})", str_jav_querying: "正在查询...", lbl_done_check: "✔ 完成", msg_limit_updated: "提取次数已更新", str_selected_items: "已选项目", title_alert: "提示", title_confirm: "确认", title_prompt: "输入", btn_ok: "确定", btn_yes: "是", btn_no: "否", btn_save: "保存配置", btn_cancel: "取消", btn_create: "创建", btn_skip: "跳过", msg_down_success: "已成功调用浏览器下载 {n} 个文件。", msg_clear_history_done: "已从历史记录中移除", msg_skip_unzipped: "已跳过 {n} 个已解压的项目。", msg_unzip_skip_del_confirm: "检测到 {n} 个压缩包在当前路径存在同名文件夹且状态标记为已解压,大概率已完成解压。是否将其移入回收站?\n\n(建议:请先检查同名文件夹内容是否完整)", msg_cancel_share_confirm: "确定要取消选中的 {n} 个分享吗?\n链接将立即失效。", msg_pwd_updated: "密码已更新", msg_exp_updated: "有效期已更新", msg_cancel_share_done: "已取消 {n} 个分享。", msg_cancel_share_ing: "正在取消分享...", msg_drag_drop_hint: "将文件拖拽到此处并释放", str_drag_files: " 等 {n} 个文件", msg_creating_share: "正在创建分享...", title_share_result: "分享成功", msg_no_files: "选中的项目为空。", msg_scan_filter_empty: "筛选条件已排除全部文件。", warn_del: "确定要删除选中的 {n} 项吗?", msg_clear_sel_confirm: "已选中 {n} 个重复文件,确认要取消当前的勾选吗?", str_bl_stat: "匹配: {n} 项 | 已选中: {m} 项", str_hits: "命中", msg_settings_saved: "设置已保存。页面将刷新。", msg_settings_saved_plain: "设置已保存。", msg_script_update_new: "检测到新版 V{v}。受发布体积限制,当前版本可能无法自动同步完整更新,可前往 GitHub 项目主页查看更新日志与项目说明。", msg_script_update_open: "查看说明", label_script_update_info: "版本信息", label_script_current_version: "当前版本", label_script_latest_version: "最新版本", label_script_last_check: "上次检查", str_script_update_not_checked: "未检查", str_script_update_failed: "检查失败", str_name_conflict: "(可能重名)", msg_newfolder_prompt: "新文件夹名称:", msg_rename_prompt: "输入新名称:", msg_copy_done: "已复制。请选择粘贴位置。", msg_cut_done: "准备移动。请选择粘贴位置。", msg_paste_empty: "没有可粘贴的项目。", msg_copy_empty: "剪贴板为空或无文本数据。", msg_add_success: "已追加 {n} 行数据。", msg_del_done: "已删除选中行。", msg_del_select: "请先点击选中要删除的行!", msg_del_items_done: "已删除 {n} 个项目。", msg_copy_success: "复制成功", str_redirecting: "正在跳转 Google Lens...", msg_manual_paste: "图床上传超时。已复制截图,请在新窗口按 {cmd}", msg_starring: "正在添加星标...", msg_unstarring: "正在取消星标...", msg_star_added: "已添加星标", msg_unstar_done: "已取消星标", msg_empty_trash_confirm: "确定要清空回收站吗?此操作无法撤销!", msg_trash_emptied: "回收站已清空。", msg_del_forever_confirm: "确定要彻底删除这 {n} 个项目吗?此操作无法撤销!", msg_restore_done: "已成功还原 {n} 个项目。", msg_auto_sub_load: "已自动加载字幕:{n}", msg_dl_sub: "正在下载字幕...", msg_fallback_report: "当前源播放异常,已切换备用源:{n}", tip_manual_sub: "提示:下载 .srt 或 .vtt 后,直接拖入播放器即可加载。", msg_resume_hint: "已为您从 {t} 继续播放,点击这里 ", msg_unzip_confirm_n: "确定要在当前目录解压这 {n} 个文件吗?", msg_task_paused: "已暂停", msg_task_added: "已添加 {n} 个上传任务", msg_task_fast_success: "秒传成功", msg_task_upload_done: "上传完成", log_dirty_data: "拦截到脏数据入侵!请求模式与当前视图不符,已物理熔断。", msg_network_unstable: "网络连接波动,正在自动恢复...", msg_skip_locked: "{n} 个被锁定", msg_skip_self: "{n} 个原地移动", msg_skip_conflict: "{n} 个子路径忙碌", msg_skip_invalid: "已自动跳过无效项: ", msg_creating_cloud_task: "正在创建云下载任务...", str_parsing_torrent: "正在解析种子文件...", err_torrent_no_info: "解析失败:未发现有效信息", err_file_read: "文件读取失败", msg_cloud_task_finish: "创建完成:{s} 成功,{f} 失败", msg_cloud_task_success: "🎉 已成功创建 {n} 个任务", msg_prepare_restore: "准备还原...", msg_smart_matching_n: "正在智能匹配密码 ({n}个)...", msg_system_busy_retry: "系统繁忙,等待重试 ({n}/5)...", msg_unzip_running_bg: "[{n}] 已在云端解压中,请稍后刷新查看", msg_share_code_updated: "分享代码已更新", msg_ghost_task_resume: "云端未完成 (点击恢复重选文件)", msg_parsing_files: "正在解析文件,请稍候...", msg_token_expired_retry: "登录过期,正在重试...", msg_oss_upload_forbidden: "直传签名失败,请重试或临时使用官方上传。", str_empty_str: "(空)", err_clipboard_failed: "写入剪贴板失败", err_cors_blocked: "安全拦截: 跨域访问被拒绝", err_worker_failed: "工作线程遇到错误", str_target_folder: "目标文件夹", str_unknown_name: "未知名称", btn_default: "默认", msg_retry_submitted: "已重试提交 {n} 个任务", msg_aria2_batch_fail_log: "\n\n检测到失败项较多,已为您自动导出完整错误清单 (.txt)", str_aria2_fetch_err: "(获取链接失败)", str_aria2_rpc_err: "(投递失败)", str_aria2_aborted: "(已取消)", str_aria2_fail_file_name: "Aria2_失败清单", msg_op_blocked_moving: "⚠️ 操作拦截\n\n后台正在执行文件搬运,请等待当前任务完成后再操作。", msg_analyze_only_normal_dir: "请选择文件夹。", msg_analyze_no_large_folders: "没有发现符合阈值范围的文件夹 ({s})", title_del_task_confirm_fmt: "确认删除 {n} 条传输任务?", lbl_del_cloud_files_too: "同时删除云盘内的文件", msg_task_del_success_fmt: "已删除 {n} 个任务", title_clear_task_confirm: "确认清空所有上传任务?", msg_task_clear_success_fmt: "已清空 {n} 个上传任务", msg_unzip_virtual_view_warn: "您正在虚拟视图中操作。解压后的文件将存放在各压缩包所在的原始文件夹中,而不会直接出现在当前列表中。

是否继续?", msg_smart_matching_file: "智能匹配密码中... ({n})", msg_unzip_batch_submitted: "✅ 已完成 {n} 个解压任务", msg_unzip_batch_skipped: " ({n} 个跳过)", msg_unzip_check_source: "。请前往源目录查看结果。", msg_task_deleted: "任务已删除", msg_scan_done: "扫描完成!\n共发现 {n} 个文件,遍历了 {f} 个文件夹。", msg_down_progress: "正在调用浏览器下载...", msg_down_confirm_total: "✅ 扫描完毕,共找到 {n} 个文件,总大小约 {s}。\n\n⚠️ 警告:当前任务规模较大,浏览器批量下载可能导致页面卡顿或被浏览器拦截。\n建议当文件数超过 {fc} 个或总大小超过 {fs} 时,优先使用 Aria2。\n\n是否仍然使用浏览器下载?", msg_aria2_sending_batch: "🚀 正在分批发送任务至 Aria2...", msg_aria2_check_fail: "Aria2 连接失败!\n请检查 URL 和 Token。", msg_aria2_sent: "已将 {n} 个文件发送到 Aria2。", msg_aria2_test_fail: "Aria2 连接失败。\n仍然保存设置吗?", title_aria2_fail: "连接测试失败", msg_batch_scanning: "🚀 正在高速扫描目录结构...", msg_batch_hydrating: "⚡ 正在并行提取下载链路...", msg_batch_no_files: "未发现可下载的文件。", msg_batch_filtered: "下载过滤规则已跳过 {n} 个文件。", msg_batch_all_filtered: "已全部过滤:{n} 个文件均命中下载过滤规则。", msg_dup_none: "未发现重复项。", msg_bl_stop: "操作已停止。", msg_bl_add_done: "已将 {n} 个项目添加到记录。", msg_bl_remove_done: "已从记录中移除 {n} 个项目。", msg_bl_empty: "名单列表为空,无法运行。", msg_bl_clear_confirm: "确定要清空所有记录条目吗?此操作不可恢复。", msg_blacklist_run_none: "网盘中未发现符合名单条件的项目。", msg_bl_run_limit: "⚠️ 模式限制\n\n清理操作涉及物理文件递归操作。目前处于非标准目录,无法准确定位物理扫描范围。\n\n请返回主页常规文件夹后再执行清理。", msg_del_protected: "资源管理器中已记录的 {n} 个文件,已保护并跳过删除。", msg_del_none: "没有可删除的文件。", rn_tip_wait: "请设置规则", rn_tip_jav: "点击上方按钮开始智能匹配", rn_tip_none: "没有匹配的项目或名称", rn_tip_series_folder_only: "选中项均为文件夹,没有匹配的文件", rn_stat: "匹配: {n} 项 | 有效变更: {m} 项", rn_warn_confirm: "确定要重命名 {n} 个文件吗?", msg_bulkrename_done: "已重命名 {n} 个项目。", msg_rn_all_skipped: "❌ 所有项目均因重名被跳过,未执行任何操作。", msg_rn_fail_count: "跳过已重名 {n} 个项目", msg_prune_confirm: "是否开始搜索当前列表中的空文件夹?", msg_prune_none: "未发现空文件夹。", msg_prune_found: "发现了 {n} 个空文件夹。\n是否立即删除?", msg_global_warn: "即将开始全盘文件同步。\n\n文件同步后缓存到本地内存中,网页刷新前持续存在。\n\n是否继续?", msg_init_scan_sel: "正在初始化选中项扫描...", warn_clear_history: "确定要从历史记录中移除选中的 {n} 项吗?\n(这不会删除您的云端文件)", warn_clear_all_history: "确定要清空全部播放历史吗?\n这会清除播放历史,但不会删除您的云端文件。", msg_clear_all_history_done: "已清空播放历史", err_official_history_delete: "播放历史删除失败", err_official_history_clear: "播放历史清空失败", msg_aria2_not_set: "检测到您尚未配置 Aria2,请填写后继续:", str_jav_no_match: "(未匹配到番号)", msg_unzip_fail: "解压请求失败", msg_jszip_fail: "JSZip 加载失败,请检查网络。", msg_turbo_activated: "极速模式已激活:脚本已深度接管网页逻辑,确保稳定流畅。", msg_ana_warn: "文件夹查重提示:判定基于算法推测,删除前请务必人工核对,以防误删。", err_migrate_too_many: "⚠️ 选中项过多\n\n当前请求数据量 ({s}MB) 已超过服务器 4MB 的硬性限制。\n\n【解决方案】:\n请在常规目录中直接选中【文件夹】进行迁移,而不是在全盘透视模式下选中海量独立文件。", msg_migrate_quota_err: "⚠️ 目标账号空间不足!\n\n系统提示:{d}\n\n是否保留迁移记录?\n(选\"是\"保留记录,清理空间后刷新页面可重试;选\"否\"则终止本次迁移)", msg_migrate_err_keep: "迁移异常:\n{e}\n\n是否保留迁移记录以便稍后重试?\n(选\"否\"将清除记录,不再弹窗)", title_migrate_fail: "迁移失败", err_invalid_links: "请输入正确的链接", err_invalid_torrent: "无效的种子文件格式", err_torrent_complex: "解析复杂度过高,可能是非法文件", err_torrent_format: "种子文件结构损坏", err_torrent_len: "字段长度解析异常", err_torrent_char: "解析遇到非法字符", err_share_code_exists: "该分享代码已被占用", err_folder_not_ready: "云端文件夹正在创建中,请稍后再试", err_item_deleted: "该项目不存在", err_clipboard_denied: "剪贴板访问被拒绝", err_worker: "工作线程错误", err_capture: "截图失败。", err_captcha_simple: "验证失败。请在网页列表手动收藏一次文件以完成验证。", err_sub_dl_fail: "字幕下载失败: ", err_sub_drop_type: "只解析字幕类型文件", err_codec_t1: "无法播放视频编码 ({c})", err_codec_t2: "您的浏览器不支持该视频格式。
请点击下方按钮调用外部播放器。", err_web_unsupported_t1: "无法在网页中播放此视频", err_web_unsupported_t2: "当前设备或浏览器可能不支持该视频的网页解码。
请点击下方按钮调用外部播放器。", err_pwd_simple: "密码错误", err_task_exists: "任务已存在", err_network_break: "图片节点网络断流,请再次点击重试", err_no_failed_task: "未选中失败的任务", err_unknown: "未知错误", err_invalid_regex: "无效的正则表达式", err_parent_not_found: "文件夹不存在", msg_sys_error: "不允许操作系统文件夹", msg_video_fail: "无法获取视频链接。", audio_player_title: "音乐播放器", audio_loading: "正在加载音频...", audio_ready: "音频已就绪", audio_playing: "正在播放", audio_paused: "已暂停", audio_play_failed: "音频播放失败", audio_link_missing: "无法获取音频链接", audio_format_unsupported: "当前音频格式可能不受浏览器支持", audio_link_expired: "音频链接已失效,请重试", audio_share_unsupported: "暂不支持播放此分享音频", audio_prev: "上一首 [Ctrl + ←]", audio_next: "下一首 [Ctrl + →]", audio_mini_prev: "上一首", audio_mini_next: "下一首", audio_play: "播放", audio_pause: "暂停", audio_mute: "静音", audio_unmute: "取消静音", audio_volume: "音量", audio_playlist: "播放列表", audio_playlist_close: "收起列表", audio_playlist_empty: "暂无音频", audio_progress: "播放进度", audio_format: "音频格式", audio_mode_order: "顺序播放", audio_mode_list: "列表循环", audio_mode_single: "单曲循环", audio_mode_shuffle: "随机播放", audio_mini_mode: "弹窗模式", audio_mini_restore: "恢复播放器", audio_mini_close: "关闭音乐", err_paste_descendant: "不能移动或复制到当前或当前子目录下", err_quota_exceeded: "存储空间不足", err_name_exists: "文件名称不能重复", err_share_pass: "自定义提取码需为 4-10 位字符", err_share_limit: "分享失败:单次最多只能分享 100 个项目", err_migrate_limit: "迁移失败:单次最多只能迁移 100 个项目", lbl_hard_delete: "彻底删除 (不进入回收站)", str_error: "错误", str_error_crit: "严重错误", str_action_failed: "操作失败", str_scan_error: "扫描错误", err_limit_too_low: "修改失败:新次数 ({n}) 必须大于当前已保存次数 ({s})", err_vault_max: "密码金库最多仅支持存储 50 个常用密码", err_pwd_len: "单个密码长度不能超过 127 个字符", } }; function getStrings() { const lang = getLang(); const base = T_LOCAL.zh || {}; const local = T_LOCAL[lang] || {}; const remote = pkRemoteI18n && pkRemoteI18n.lang === lang ? pkRemoteI18n.data : null; return mergeI18nPack(mergeI18nPack(base, remote), local); }; const pkI18nBootReady = ensureI18nReady(); let cachedCredKey = null; let cachedCaptchaKey = null; let memoryCapturedToken = ''; document.addEventListener('pk-token-captured', (e) => { memoryCapturedToken = e.detail; }); function resetHeaderCache() { cachedCredKey = null; cachedCaptchaKey = null; memoryCapturedToken = ''; } function purgeAllCachesOnLogout() { resetHeaderCache(); if (typeof globalCache !== 'undefined') globalCache.clear(); if (typeof globalLineageMap !== 'undefined') globalLineageMap.clear(); if (typeof globalParentIndex !== 'undefined') globalParentIndex.clear(); if (typeof globalDirtyFolders !== 'undefined') globalDirtyFolders.clear(); if (typeof scannedFolderIds !== 'undefined') scannedFolderIds.clear(); if (typeof backgroundQueue !== 'undefined') backgroundQueue.length = 0; if (typeof isBackgroundRunning !== 'undefined') isBackgroundRunning = false; if (typeof DurationProber !== 'undefined') DurationProber.reset(); const ui = document.querySelector('.pk-ov'); if (ui) { ui.remove(); document.body.style.overflow = ''; document.documentElement.style.overflow = ''; } const btn = document.getElementById('pk-launch'); if (btn) btn.remove(); if (typeof pkState !== 'undefined' && pkState) pkState = null; if (typeof globalSavedState !== 'undefined') globalSavedState = null; if (typeof globalPreloadPromise !== 'undefined') globalPreloadPromise = null; } async function confirmedLogout(reason = 'unknown', graceMs = 5000, waitMs = 5200) { console.warn(`[Auth] Confirmed logout requested: ${reason}`); if (typeof window.pkEnterAuthRecoveryWindow === 'function') { window.pkEnterAuthRecoveryWindow(`confirmed-logout:${reason}`, graceMs); } await sleep(800); const isAuthReady = await waitForAuth(waitMs); if (isAuthReady) { console.warn(`[Auth] Logout aborted, auth recovered: ${reason}`); if (typeof window.pkMarkAuthRecovered === 'function') window.pkMarkAuthRecovered(); return false; } console.warn(`[Auth] Logout confirmed after recheck: ${reason}`); if (typeof purgeAllCachesOnLogout === 'function') purgeAllCachesOnLogout(); if (!location.href.includes('/login')) window.location.href = 'https://mypikpak.com/drive/login'; return true; } (() => { let authCheckTimer = null; let authRouteGraceTimer = null; let authRecoveryUntil = 0; let officialAuthReadyUntil = 0; let pageHiddenAt = document.hidden ? Date.now() : 0; const AUTH_PURGE_DELAY_MS = 1200; const AUTH_ROUTE_GRACE_MS = 4000; const AUTH_RESUME_GRACE_MS = 5000; const AUTH_RESUME_MIN_HIDDEN_MS = 45000; const OFFICIAL_AUTH_READY_TTL_MS = 2 * 60 * 1000; const isLoginRoute = () => location.href.includes('/login') || location.pathname.includes('login'); const hasCredentialSnapshot = () => { if (memoryCapturedToken && memoryCapturedToken.length > 10) return true; for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); if (k && k.startsWith('credentials')) { try { const v = JSON.parse(localStorage.getItem(k)); if (v && v.access_token) return true; } catch {} } } return false; }; const markAuthRecovered = () => { authRecoveryUntil = 0; if (authRouteGraceTimer) { clearTimeout(authRouteGraceTimer); authRouteGraceTimer = null; } }; const enterAuthRecoveryWindow = (reason = 'unknown', graceMs = AUTH_ROUTE_GRACE_MS) => { authRecoveryUntil = Math.max(authRecoveryUntil, Date.now() + graceMs); if (authRouteGraceTimer) clearTimeout(authRouteGraceTimer); authRouteGraceTimer = setTimeout(() => { authRouteGraceTimer = null; if (isLoginRoute() && !hasCredentialSnapshot()) { checkAndPurgeAuth(`grace-expired:${reason}`); } else if (isOfficialAuthReady()) { markAuthRecovered(); } }, graceMs + 50); }; const isOfficialAuthReady = () => officialAuthReadyUntil > Date.now(); const markOfficialAuthReady = (reason = 'official-auth-ready') => { officialAuthReadyUntil = Date.now() + OFFICIAL_AUTH_READY_TTL_MS; markAuthRecovered(); }; const markOfficialAuthError = (reason = 'official-auth-error') => { officialAuthReadyUntil = 0; enterAuthRecoveryWindow(reason, AUTH_ROUTE_GRACE_MS); }; document.addEventListener('pk-official-auth-ready', (e) => { const wasRecovering = authRecoveryUntil > Date.now(); markOfficialAuthReady(e.detail?.url || 'official-auth-ready'); if (wasRecovering && typeof window.pkForceManagerReloadAfterAuth === 'function') { window.pkForceManagerReloadAfterAuth('official-auth-ready'); } }); document.addEventListener('pk-official-auth-error', (e) => { markOfficialAuthError(`official-auth-error:${e.detail?.status || 'unknown'}`); }); const checkAndPurgeAuth = (reason = 'unknown') => { if (authCheckTimer) clearTimeout(authCheckTimer); const extraWait = authRecoveryUntil > Date.now() ? (authRecoveryUntil - Date.now() + 50) : 0; authCheckTimer = setTimeout(() => { if (hasCredentialSnapshot()) { markAuthRecovered(); return; } if (typeof purgeAllCachesOnLogout === 'function') purgeAllCachesOnLogout(); if (!isLoginRoute()) window.location.href = 'https://mypikpak.com/drive/login'; }, Math.max(AUTH_PURGE_DELAY_MS, extraWait)); }; const handlePageResume = (reason = 'resume') => { const hiddenFor = pageHiddenAt ? (Date.now() - pageHiddenAt) : 0; pageHiddenAt = 0; if (hiddenFor < AUTH_RESUME_MIN_HIDDEN_MS && reason !== 'pageshow-bfcache') return; enterAuthRecoveryWindow(`resume:${reason}`, AUTH_RESUME_GRACE_MS); checkAndPurgeAuth(`resume:${reason}`); }; document.addEventListener('pk-token-captured', () => { const wasRecovering = authRecoveryUntil > Date.now(); markAuthRecovered(); if (wasRecovering && typeof window.pkForceManagerReloadAfterAuth === 'function') { window.pkForceManagerReloadAfterAuth('token-captured'); } }); document.addEventListener('visibilitychange', () => { if (document.hidden) { pageHiddenAt = Date.now(); return; } handlePageResume('visibility'); }); window.addEventListener('pageshow', (e) => { if (e.persisted) { handlePageResume('pageshow-bfcache'); return; } if (pageHiddenAt) handlePageResume('pageshow'); }); window.addEventListener('focus', () => { if (pageHiddenAt) handlePageResume('focus'); }); window.addEventListener('storage', (e) => { if (e.key && (e.key.startsWith('credentials') || e.key.startsWith('captcha') || e.key === 'pk_captured_captcha')) { if (!e.newValue) { enterAuthRecoveryWindow(`storage-remove:${e.key}`); checkAndPurgeAuth(`storage-remove:${e.key}`); } else { resetHeaderCache(); markAuthRecovered(); } } }); const _origSetItem = Storage.prototype.setItem; Storage.prototype.setItem = function(key, value) { _origSetItem.apply(this, arguments); if (key && (key.startsWith('credentials') || key.startsWith('captcha') || key === 'pk_captured_captcha')) { resetHeaderCache(); markAuthRecovered(); } }; const _origRemoveItem = Storage.prototype.removeItem; Storage.prototype.removeItem = function(key) { _origRemoveItem.apply(this, arguments); if (key && (key.startsWith('credentials') || key.startsWith('captcha'))) { enterAuthRecoveryWindow(`remove:${key}`); checkAndPurgeAuth(`remove:${key}`); } }; const checkAuthRoute = () => { if (isLoginRoute()) { enterAuthRecoveryWindow('route-login'); checkAndPurgeAuth('route-login'); } else { markAuthRecovered(); } }; const _origPushState = history.pushState; history.pushState = function() { _origPushState.apply(this, arguments); checkAuthRoute(); }; const _origReplaceState = history.replaceState; history.replaceState = function() { _origReplaceState.apply(this, arguments); checkAuthRoute(); }; window.pkEnterAuthRecoveryWindow = enterAuthRecoveryWindow; window.pkMarkAuthRecovered = markAuthRecovered; window.pkIsAuthRecoveryActive = () => authRecoveryUntil > Date.now(); window.addEventListener('popstate', checkAuthRoute); })(); function getHeaders() { const recoveryActive = typeof window.pkIsAuthRecoveryActive === 'function' && window.pkIsAuthRecoveryActive(); let token = memoryCapturedToken || '', captcha = ''; if (recoveryActive) { cachedCredKey = null; cachedCaptchaKey = null; } if (!token && cachedCredKey) { try { const v = JSON.parse(localStorage.getItem(cachedCredKey)); if (v && v.access_token) token = v.token_type + ' ' + v.access_token; } catch {} } if (!token) { for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); if (k && k.startsWith('credentials')) { try { const v = JSON.parse(localStorage.getItem(k)); if (v && v.access_token) { token = v.token_type + ' ' + v.access_token; cachedCredKey = k; break; } } catch {} } } } if (cachedCaptchaKey) { try { const v = JSON.parse(localStorage.getItem(cachedCaptchaKey)); if (v) captcha = v.captcha_token; } catch {} } if (!captcha) { captcha = localStorage.getItem('pk_captured_captcha') || ''; if (!captcha) { for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); if (k && k.startsWith('captcha')) { try { const v = JSON.parse(localStorage.getItem(k)); if (v) { captcha = v.captcha_token; cachedCaptchaKey = k; break; } } catch {} } } } } const headers = { 'Content-Type': 'application/json', 'Authorization': token, 'x-device-id': localStorage.getItem('deviceid') || '', 'x-captcha-token': captcha }; if (headers.Authorization && headers.Authorization.length > 10 && typeof window.pkMarkAuthRecovered === 'function' && typeof window.pkIsOfficialAuthReady === 'function' && window.pkIsOfficialAuthReady()) { window.pkMarkAuthRecovered(); } return headers; } let authReadyProbePromise = null; let authReadyProbeAt = 0; async function probeAuthReadyByAbout(headers) { if (!headers || !headers.Authorization || headers.Authorization.length <= 10) return false; if (typeof window.pkIsOfficialAuthReady === 'function' && window.pkIsOfficialAuthReady()) return true; if (authReadyProbePromise) return authReadyProbePromise; if (Date.now() - authReadyProbeAt < 900) return false; authReadyProbeAt = Date.now(); authReadyProbePromise = (async () => { try { const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/about?_t=${Date.now()}`, { method: 'GET', headers, cache: 'no-store' }); if (!res.ok) { if ((res.status === 400 || res.status === 401 || res.status === 403) && typeof window.pkEnterAuthRecoveryWindow === 'function') { window.pkEnterAuthRecoveryWindow(`about-probe-${res.status}`, 5000); } return false; } const data = await res.clone().json().catch(() => null); if (data && (data.kind === 'drive#about' || data.quota || data.user || data.sub)) { if (typeof window.pkMarkOfficialAuthReady === 'function') window.pkMarkOfficialAuthReady('about-probe'); return true; } } catch (e) {} return false; })().finally(() => { authReadyProbePromise = null; }); return authReadyProbePromise; } async function waitForAuth(timeout = 10000) { const start = Date.now(); let probeCount = 0; let hardTimeout = timeout + ((typeof window.pkIsAuthRecoveryActive === 'function' && window.pkIsAuthRecoveryActive()) ? 3000 : 0); while (Date.now() - start < hardTimeout) { const h = getHeaders(); if (h.Authorization && h.Authorization.length > 10) { if (typeof window.pkIsOfficialAuthReady === 'function' && window.pkIsOfficialAuthReady()) { if (typeof window.pkMarkAuthRecovered === 'function') window.pkMarkAuthRecovered(); return true; } if ((probeCount % 3) === 0 && await probeAuthReadyByAbout(h)) return true; } if ((probeCount++ % 5) === 0 && typeof resetHeaderCache === 'function') { resetHeaderCache(); } if (typeof window.pkIsAuthRecoveryActive === 'function' && window.pkIsAuthRecoveryActive()) { hardTimeout = Math.max(hardTimeout, timeout + 3000); } await sleep((typeof window.pkIsAuthRecoveryActive === 'function' && window.pkIsAuthRecoveryActive()) ? 250 : 200); } return false; } async function apiList(parentId, limit = 1000, onProgress, signal, trashed = false, isBackground = false) { let all = [], next = null, safe = 5000; const TIMEOUT_MS = 25000; const filters = trashed ? `&filters=${encodeURIComponent('{"trashed":{"eq":true}}')}&parent_id=*` : `&parent_id=${parentId || ''}`; do { let pageRetries = 0; const MAX_PAGE_RETRIES = 5; let pageSuccess = false; while (pageRetries < MAX_PAGE_RETRIES && !pageSuccess) { if (signal?.aborted) throw new DOMException('Aborted by user', 'AbortError'); const url = `https://api-drive.mypikpak.com/drive/v1/files?thumbnail_size=SIZE_MEDIUM&limit=${limit}${filters}&with_audit=true&_t=${Date.now()}${next ? `&page_token=${next}` : ''}`; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS); if (signal) signal.addEventListener('abort', () => controller.abort(), { once: true }); try { const res = await fetch(url, { headers: getHeaders(), signal: controller.signal }); clearTimeout(timeoutId); if (!res.ok) { if (res.status === 404) { console.warn(`[API] 404 Not Found (Skipped): ${parentId || 'Root'}`); return []; } if (res.status === 401 || res.status === 403) { console.warn(`[API] ${res.status} Unauthorized. Throwing hard auth error...`); localStorage.removeItem('pk_captured_captcha'); resetHeaderCache(); throw new Error(`API Error ${res.status}`); } if (res.status === 400) { console.warn(`[API] 400 Error. Possible captcha intercept.`); localStorage.removeItem('pk_captured_captcha'); resetHeaderCache(); try { if (typeof showToast !== 'undefined') showToast(getStrings().err_captcha_simple, 'error'); } catch(e){} throw new Error('CAPTCHA_INTERCEPT'); } if (res.status === 429) { await sleep(3000 * (pageRetries + 1)); throw new Error('RATE_LIMIT'); } throw new Error(`HTTP_${res.status}`); } syncTime(res.headers); const data = await res.json(); if (!data.files && data.next_page_token) { throw new Error('PAGINATION_INCOMPLETE'); } if (data.files) { const validFiles = data.files.filter(f => trashed ? f.trashed : !f.trashed).map(f => minifyFile(f, isBackground)); all.push(...validFiles); if (onProgress) onProgress(all.length); if (isBackground && parentId !== undefined && typeof globalCache !== 'undefined') { globalCache.set(parentId, { items: [...all], nextToken: data.next_page_token }); } } next = data.next_page_token; pageSuccess = true; if (next) await sleep(50); } catch (e) { clearTimeout(timeoutId); pageRetries++; safe--; let isTimeout = false; let errMsg = e.message; if (e.name === 'AbortError' && !(signal && signal.aborted)) { isTimeout = true; e = new Error('FETCH_TIMEOUT'); errMsg = 'Local Timeout'; } const isNetworkError = e.name === 'TypeError' || errMsg.includes('fetch') || errMsg.includes('PAGINATION') || isTimeout; if ((isNetworkError || errMsg === 'AUTH_RETRY') && safe > 0) { const backoff = pageRetries === 1 ? 500 : Math.min(pageRetries * 2000, 10000); console.warn(`[API] Retry ${pageRetries}/${MAX_PAGE_RETRIES} for ${parentId || 'Root'} due to ${errMsg}. Wait ${backoff}ms`); await sleep(backoff); continue; } throw e; } } } while (next && safe > 0); return all; } async function apiGet(id) { const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/files/${id}?thumbnail_size=SIZE_MEDIUM&_t=${Date.now()}`, { headers: getHeaders() }); if (!res.ok) throw new Error(`API Error ${res.status}`); return res.json(); } async function apiAction(action, data) { const method = action.includes('batch') ? 'POST' : 'PATCH'; const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/files${action}`, { method: method, headers: getHeaders(), body: JSON.stringify(data) }); syncTime(res.headers); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(err.error_description || `API Error ${res.status}`); } return res.json(); }; async function apiShareList(limit = 100) { let all =[], next = null, safe = 50; do { const url = `https://api-drive.mypikpak.com/drive/v1/share/list?limit=${limit}&thumbnail_size=SIZE_SMALL&_t=${Date.now()}${next ? `&page_token=${next}` : ''}`; const res = await fetch(url, { headers: getHeaders() }); if (!res.ok) throw new Error(`Share API Error ${res.status}`); const json = await res.json(); const list = json.data || []; const normalized = list.map(item => ({ id: item.share_id, kind: 'pikpak#share', name: item.title, size: item.file_size, modified_time: item.create_time, icon_link: item.icon_link, view_count: item.view_count, save_count: item.restore_count, limit_count: (function(){ const store = JSON.parse(gmGet('pk_share_limits', '{}')); return store[item.share_id] || parseInt(item.limit_count || 0); })(), share_status: item.share_status, share_status_text: item.share_status_text, expiration_days: item.expiration_days, expiration_left: item.expiration_left, expiration_at: item.expiration_at, phrase: item.phrase || "", share_url: item.share_url, pass_code: item.pass_code, parent_id: 'share_root' })); all.push(...normalized); if (!next) { const graveyard = JSON.parse(gmGet('pk_expired_shares', '[]')); const serverIds = new Set(all.map(x => x.id)); const phantoms = graveyard.filter(x => !serverIds.has(x.id)); all.push(...phantoms); } next = json.next_page_token; safe--; } while (next && safe > 0); const graveyard = JSON.parse(gmGet("pk_expired_shares", "[]")); const graveyardIds = new Set(graveyard.map(x => x.id)); const filteredServerList = all.filter(it => !graveyardIds.has(it.id)); return [...filteredServerList, ...graveyard]; } async function apiTaskList(limit = 500, onBatch = null, session = null, signal = null) { let totalFetched = 0; const state = session || { phase: 'active', nextToken: null, completed: false }; const fetchList = async (phases, maxCount, startToken = null) => { let next = startToken; let page = 0; const maxPages = 100; do { if (signal && signal.aborted) break; const filters = encodeURIComponent(JSON.stringify({ "phase": { "in": phases } })); const url = `https://api-drive.mypikpak.com/drive/v1/tasks?type=offline&limit=${limit}&filters=${filters}&thumbnail_size=SIZE_SMALL&with_reference_resource=true&_t=${Date.now()}${next ? `&page_token=${next}` : ''}`; let res = null; for (let i = 0; i < 3; i++) { try { res = await fetch(url, { headers: getHeaders() }); if (res.ok) break; if (res.status === 401 || res.status === 403) throw new Error(`API Error ${res.status}`); if (res.status === 429) await sleep(2000); } catch (e) { if (e.message.includes('401') || e.message.includes('403')) throw e; await sleep(1000); } } if (!res || !res.ok) break; const data = await res.json(); const tasks = data.tasks || []; if (tasks.length > 0) { state.nextToken = data.next_page_token; if (onBatch) onBatch(tasks, data.next_page_token, phases.includes('COMPLETE') ? 'done' : 'active'); totalFetched += tasks.length; } next = data.next_page_token; page++; if (totalFetched >= maxCount || page >= maxPages) break; if (next) await sleep(50); } while (next); return next; }; if (state.phase === 'active') { const activePhases = "PHASE_TYPE_UNKNOW,PHASE_TYPE_PENDING,PHASE_TYPE_RUNNING,PHASE_TYPE_PAUSED,PHASE_TYPE_ERROR"; const lastToken = await fetchList(activePhases, 2000, state.nextToken); if (!lastToken) { state.phase = 'done'; state.nextToken = null; } else { return totalFetched; } } if (state.phase === 'done') { const donePhases = "PHASE_TYPE_COMPLETE"; const lastToken = await fetchList(donePhases, 15000, state.nextToken); if (!lastToken) { state.completed = true; state.nextToken = null; } } return totalFetched; } async function apiCancelTask(ids, deleteFiles = false) { let filesToDelete = []; if (deleteFiles && typeof pkState !== 'undefined' && pkState.itemMap) { ids.forEach(taskId => { const task = pkState.itemMap.get(taskId); if (task && task.id === taskId && task.file_id) { filesToDelete.push(task.file_id); } }); } const BATCH_SIZE = 50; for (let i = 0; i < ids.length; i += BATCH_SIZE) { const chunk = ids.slice(i, i + BATCH_SIZE); const idStr = chunk.join(','); const url = `https://api-drive.mypikpak.com/drive/v1/tasks?task_ids=${idStr}&delete_files=${deleteFiles}&_t=${Date.now()}`; try { const res = await fetch(url, { method: 'DELETE', headers: getHeaders() }); if (!res.ok && res.status !== 404) throw new Error(`Task Del Err ${res.status}`); } catch(e) { console.warn("Task delete warning:", e); } } if (filesToDelete.length > 0) { const trashUrl = `https://api-drive.mypikpak.com/drive/v1/files:batchTrash`; for (let i = 0; i < filesToDelete.length; i += 100) { const fileChunk = filesToDelete.slice(i, i + 100); await fetch(trashUrl, { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: fileChunk }) }); } } return true; } async function apiAddOfflineTask(url, parentId = '', extraParams = {}) { const apiUrl = `https://api-drive.mypikpak.com/drive/v1/files`; const payload = { kind: "drive#file", upload_type: "UPLOAD_TYPE_URL", url: { "url": url }, params: { from: 'manual', with_thumbnail: 'true', ...extraParams } }; if (parentId) { payload.parent_id = parentId; } else { payload.folder_type = 'DOWNLOAD'; } const res = await fetch(apiUrl, { method: 'POST', headers: getHeaders(), body: JSON.stringify(payload) }); if (!res.ok) { const err = await res.json().catch(() => ({})); if (res.status === 400 && err.error_code === 9) throw new Error(getStrings().err_task_exists); throw new Error(err.error_description || `Add Task Error ${res.status}`); } return await res.json(); } async function apiGetSharePhrases(shareId) { const url = `https://api-drive.mypikpak.com/phrase/v1/content/info/share?id=${shareId}`; const res = await fetch(url, { headers: getHeaders() }); if (!res.ok) return []; const json = await res.json(); return (json.data || []).map(x => x.phrase); } async function apiUpdateSharePhrase(shareId, newPhrase, oldPhrase = "") { const url = `https://api-drive.mypikpak.com/phrase/v1/content/share`; const isDelete = newPhrase === ""; const isCreate = !oldPhrase && !isDelete; const payload = { content: shareId, type: "share" }; if (isCreate) { payload.phrase = newPhrase; } else { payload.old_phrase = oldPhrase; payload.new_phrase = newPhrase; } const res = await fetch(url, { method: isCreate ? 'POST' : 'PATCH', headers: getHeaders(), body: JSON.stringify(payload) }); if (!res.ok) { const err = await res.json().catch(() => ({})); if (res.status === 400) throw new Error(getStrings().err_share_code_exists); throw new Error(err.error_description || `API Error ${res.status}`); } return res.json(); } async function apiUpdateShare(shareId, data) { const url = `https://api-drive.mypikpak.com/drive/v1/share`; const payload = { share_id: shareId, ...data }; const res = await fetch(url, { method: 'PATCH', headers: getHeaders(), body: JSON.stringify(payload) }); syncTime(res.headers); if (!res.ok) { const err = await res.json().catch(()=>({})); throw new Error(err.error_description || `API Error ${res.status}`); } return await res.json(); } async function apiCancelShare(ids) { const url = `https://api-drive.mypikpak.com/drive/v1/share:batchDelete`; const res = await fetch(url, { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: ids }) }); if (!res.ok) { console.warn("[Share] Batch delete failed, trying sequential delete..."); for (const id of ids) { await fetch(`https://api-drive.mypikpak.com/drive/v1/share/${id}`, { method: 'DELETE', headers: getHeaders() }); } return true; } return true; } // Original Logic by digbug82. Modification does not grant ownership. async function coreRecursiveEngine(roots, options) { const { signal, onFile, onFolder, onProgress, preferFresh = false } = options; const L = getStrings(); let queue = [...roots]; let activeTasks = new Set(); let inFlight = new Set(); let pendingRetries = 0; const stats = { folders: 0, files: 0, retries: 0, cacheHits: 0, currentConcurrency: 10 }; const USER_LIMIT = parseInt(localStorage.getItem('pk_user_limit') || "200"); const ABSOLUTE_MAX = 128; const MIN_CONCURRENCY = 5; const processFolder = async (current) => { inFlight.add(current.id); try { let files = []; const folderId = current.id === 'root' ? '' : (current.id || ''); if (!preferFresh && typeof globalCache !== 'undefined' && globalCache.has(folderId)) { const cachedData = globalCache.get(folderId); if (Array.isArray(cachedData)) { files = cachedData; stats.cacheHits++; } } let isFromNetwork = false; let start = 0; if (files.length === 0) { start = performance.now(); files = await apiList(folderId, 1000, null, signal, false, true); isFromNetwork = true; if (typeof globalCache !== 'undefined') globalCache.set(folderId, files); if (typeof scannedFolderIds !== 'undefined') scannedFolderIds.add(folderId); } if (signal && signal.aborted) return; const nextSubFolders = []; for (const f of files) { if (f.kind === 'drive#folder') { const myLineage = [...(current.lineage || []), { id: f.id, name: f.name }]; nextSubFolders.push({ ...f, lineage: myLineage, depth: (current.depth || 0) + 1, retryCount: 0 }); } else { stats.files++; if (onFile) onFile(f, current); } } if (onFolder) onFolder(current, files, nextSubFolders); queue.push(...nextSubFolders); stats.folders++; if (isFromNetwork) { const rtt = performance.now() - start; const DYNAMIC_MAX = Math.min(USER_LIMIT, ABSOLUTE_MAX); if (rtt < 800) { if (stats.currentConcurrency < DYNAMIC_MAX) stats.currentConcurrency += 1; } else if (rtt > 3000) { stats.currentConcurrency = Math.max(MIN_CONCURRENCY, Math.floor(stats.currentConcurrency * 0.8)); } } } catch (err) { if (err.name === 'AbortError' || (signal && signal.aborted)) return; stats.currentConcurrency = Math.max(MIN_CONCURRENCY, Math.floor(stats.currentConcurrency * 0.5)); stats.retries++; current.retryCount = (current.retryCount || 0) + 1; pendingRetries++; try { const backoff = current.retryCount === 1 ? 1000 : Math.min(current.retryCount * 5000, 60000); const reason = err.message || "Unknown Error"; console.warn(`[ZeroLoss] Folder: ${current.name} | Reason: ${reason} | Attempt: ${current.retryCount} | Re-queueing...`); stats.isRetrying = true; if (onProgress) onProgress(stats); await sleep(backoff); if (signal && !signal.aborted) { queue.unshift(current); } } finally { stats.isRetrying = false; pendingRetries--; } } finally { inFlight.delete(current.id); if (onProgress) onProgress(stats); } }; while ((queue.length > 0 || inFlight.size > 0 || pendingRetries > 0) && (!signal || !signal.aborted)) { while (queue.length > 0 && activeTasks.size < stats.currentConcurrency && (!signal || !signal.aborted)) { const folder = queue.pop(); if (inFlight.has(folder.id) && folder.retryCount === 0) continue; const p = processFolder(folder); const pWrapper = p.finally(() => activeTasks.delete(pWrapper)); activeTasks.add(pWrapper); } if (activeTasks.size > 0) { await Promise.race(activeTasks).catch(() => {}); } else if (pendingRetries > 0 || inFlight.size > 0) { await sleep(100); } } } const version = (typeof GM_info !== 'undefined' && GM_info.script) ? GM_info.script.version : "1.0.0"; console.log("%c PikPak Enhancement Master %c v" + version + " %c digbug82 %c AGPL-3.0-or-later ", "color:#fff; background:#1a5eff; padding:3px 0; border-radius:4px 0 0 4px; font-weight:bold;", "color:#fff; background:#333; padding:3px 8px;", "color:#fff; background:#f57c00; padding:3px 8px; font-weight:bold;", "color:#fff; background:#d93025; padding:3px 8px; border-radius:0 4px 4px 0; font-weight:bold;"); function getIcon(item) { const isFolder = item.kind === 'drive#folder' || (item.kind === 'drive#task' && ( (item.mime_type && item.mime_type.includes('folder')) || (item.icon_link && item.icon_link.includes('folder')) )); if (isFolder) { const isRootMyPack = (item.name === CONF.SYSTEM_FOLDER_NAME) && (!item.parent_id || item.parent_id === '' || item.parent_id === 'root' || item._isSystemRoot); if (isRootMyPack) return CONF.typeIcons.systemFolder; return CONF.typeIcons.folder; } const name = (item.name || '').toLowerCase(); const ext = name.split('.').pop(); const mime = (item.mime_type || '').toLowerCase(); const duration = (item.params && item.params.duration) || 0; if (ext === 'apk') return CONF.typeIcons.apk; if (ext === 'txt') return CONF.typeIcons.text; if (ext === 'html' || ext === 'htm') return CONF.typeIcons.web; if (['srt', 'vtt', 'ass', 'ssa'].includes(ext)) return CONF.typeIcons.subtitle; if (['exe', 'msi', 'bat', 'cmd', 'elf', 'dmg', 'pkg', 'app'].includes(ext)) return CONF.typeIcons.executable; const isArchiveExt = ['zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'iso'].includes(ext); if (mime.startsWith('video/') || duration > 0) return CONF.typeIcons.video; if (mime.startsWith('image/')) return CONF.typeIcons.image; if (mime.startsWith('audio/')) return CONF.typeIcons.audio; if (mime === 'application/pdf') return CONF.typeIcons.pdf; if (mime.startsWith('text/') || mime.includes('word') || mime.includes('excel') || mime.includes('powerpoint') || mime.includes('officedocument') || mime === 'application/rtf') return CONF.typeIcons.text; if (isArchiveExt || mime.includes('zip') || mime.includes('rar') || mime.includes('7z') || mime.includes('tar') || mime.includes('archive') || mime.includes('compressed')) { const isUnzipped = item.params && (item.params.global_file_kind === '1' || item.params.global_file_root); return isUnzipped ? CONF.typeIcons.archiveUnzipped : CONF.typeIcons.archive; } return CONF.typeIcons.file; } function getPreviewIconFailCache() { if (!window.pkPreviewIconFailCache) window.pkPreviewIconFailCache = new Set(); return window.pkPreviewIconFailCache; } function isPreviewIconFailed(src) { return !!src && getPreviewIconFailCache().has(src); } function markPreviewIconFailed(src) { if (src) getPreviewIconFailCache().add(src); } function bindPreviewIconFallback(root) { if (!root) return; root.querySelectorAll('img[data-pk-preview-src]').forEach(img => { if (img.dataset.pkPreviewBound === '1') return; img.dataset.pkPreviewBound = '1'; img.addEventListener('error', () => { markPreviewIconFailed(img.dataset.pkPreviewSrc || img.getAttribute('src') || ''); img.style.display = 'none'; const fallback = img.nextElementSibling; if (fallback) fallback.style.display = 'inline-flex'; }, { once: true }); }); } async function openManager(initialCache, preloadPromise) { const L = getStrings(); const lang = getLang(); function normalizeAriaRpcUrl(url) { let u = (url || '').trim(); if (!u) u = 'http://localhost:6800/jsonrpc'; if (!/^https?:\/\//i.test(u) && !/^wss?:\/\//i.test(u)) u = 'http://' + u; if (!u.includes('/jsonrpc') && !u.includes('?')) { u = u.endsWith('/') ? u + 'jsonrpc' : u + '/jsonrpc'; } return u; } function buildAriaRpcParams(token, params = []) { const t = (token || '').trim(); return t ? [`token:${t}`, ...params] : params; } function getBoolPref(key, def) { const v = gmGet(key, def); if (v === undefined || v === null) return !!def; if (v === true || v === false) return v; if (typeof v === 'string') { const s = v.trim().toLowerCase(); if (s === 'true') return true; if (s === 'false') return false; } return !!v; } function isValidDownloadAccelHost(hostname) { const host = String(hostname || '').trim().toLowerCase(); if (!host) return false; if (host === 'localhost') return true; if (host.startsWith('xn--') || host.includes('.xn--')) return false; if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(host)) return host.split('.').every(n => Number(n) >= 0 && Number(n) <= 255); return /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/.test(host); } function normalizeDownloadAccelBase(input) { const raw = String(input || '').trim(); if (!raw) return ''; try { const candidate = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `https://${raw}`; const u = new URL(candidate); if (!/^https?:$/i.test(u.protocol) || !u.hostname || u.origin === 'null') return ''; if (!isValidDownloadAccelHost(u.hostname)) return ''; const path = (u.pathname || '').replace(/\/+$/, ''); return `${u.origin}${path === '/' ? '' : path}${u.search}`; } catch (e) { return ''; } } function normalizeDownloadAccelMode(input) { return String(input || '').trim().toLowerCase() === 'query' ? 'query' : 'prefix'; } function normalizeDownloadAccelQueryParam(input) { const param = String(input || '').trim(); return /^[A-Za-z0-9_.~-]+$/.test(param) ? param : 'url'; } function getDownloadAccelModePref() { return normalizeDownloadAccelMode(gmGet('pk_download_accel_mode', CONF.downloadAccelMode)); } function getDownloadAccelQueryParamPref() { return normalizeDownloadAccelQueryParam(gmGet('pk_download_accel_query_param', CONF.downloadAccelQueryParam)); } function rewriteDownloadLinkForAccel(rawUrl) { try { if (!rawUrl) return rawUrl; if (!getBoolPref('pk_download_accel_enable', CONF.downloadAccelEnable)) return rawUrl; const accelBase = normalizeDownloadAccelBase(gmGet('pk_download_accel_domain', CONF.downloadAccelDomain)); if (!accelBase) return rawUrl; const raw = String(rawUrl).trim(); const src = new URL(raw); if (!/^https?:$/i.test(src.protocol) || !src.hostname) return rawUrl; const mode = getDownloadAccelModePref(); if (mode === 'query') { const sep = accelBase.includes('?') ? (/[?&]$/.test(accelBase) ? '' : '&') : '?'; return `${accelBase}${sep}${encodeURIComponent(getDownloadAccelQueryParamPref())}=${encodeURIComponent(raw)}`; } return `${accelBase}/${raw}`; } catch (e) { return rawUrl; } } function isZeroByteFile(file) { if (!file) return false; const rawSize = file.size ?? file.file_size ?? file.bytes; if (rawSize === 0 || rawSize === '0') return true; const n = Number(rawSize); return Number.isFinite(n) && n === 0; } function getDownloadFilterRules() { const fExtStr = gmGet('pk_dl_filter_ext', '').toLowerCase(); const fNameStr = gmGet('pk_dl_filter_name', '').toLowerCase(); const fExts = fExtStr.split(/[,,]/).map(s => s.trim().replace(/^\./, '')).filter(Boolean); const fNames = fNameStr.split(/[,,]/).map(s => s.trim()).filter(Boolean); const fSizeMinStr = gmGet('pk_dl_filter_size_min', ''); const fSizeMaxStr = gmGet('pk_dl_filter_size_max', ''); const fSizeUnitStr = gmGet('pk_dl_filter_size_unit', 'MB'); let fSizeMin = fSizeMinStr ? parseFloat(fSizeMinStr) : -1; let fSizeMax = fSizeMaxStr ? parseFloat(fSizeMaxStr) : -1; if (fSizeUnitStr === 'GB') { if (fSizeMin >= 0) fSizeMin *= 1024 * 1024 * 1024; if (fSizeMax >= 0) fSizeMax *= 1024 * 1024 * 1024; } else if (fSizeUnitStr === 'TB') { if (fSizeMin >= 0) fSizeMin *= 1024 * 1024 * 1024 * 1024; if (fSizeMax >= 0) fSizeMax *= 1024 * 1024 * 1024 * 1024; } else { if (fSizeMin >= 0) fSizeMin *= 1024 * 1024; if (fSizeMax >= 0) fSizeMax *= 1024 * 1024; } return { fExts, fNames, fSizeMin, fSizeMax, hasRules: fExts.length > 0 || fNames.length > 0 || (Number.isFinite(fSizeMin) && fSizeMin >= 0) || (Number.isFinite(fSizeMax) && fSizeMax >= 0) }; } function isDownloadFilterBlocked(item, rules = getDownloadFilterRules()) { if (!item) return false; if (item.kind === 'drive#folder') return false; const lowName = String(item.name || '').toLowerCase(); const ext = lowName.split('.').pop(); if (rules.fExts.some(e => ext === e)) return true; if (rules.fNames.some(n => lowName.includes(n))) return true; if (rules.fSizeMin >= 0 || rules.fSizeMax >= 0) { const sz = parseInt(item.size || 0, 10); if (Number.isFinite(sz)) { if (rules.fSizeMin >= 0 && sz < rules.fSizeMin) return true; if (rules.fSizeMax >= 0 && sz > rules.fSizeMax) return true; } } return false; } function downloadEmptyFile(file) { try { const blob = new Blob([''], { type: 'application/octet-stream' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = (file && file.name) || 'empty'; a.style.display = 'none'; document.body.appendChild(a); a.click(); setTimeout(() => { if (a.parentNode) a.remove(); URL.revokeObjectURL(url); }, 1000); return true; } catch (e) { return false; } } function isAriaWsRpcUrl(url) { return /^wss?:\/\//i.test(url || ''); } function getAriaRpcError(data) { const list = Array.isArray(data) ? data : [data]; const hit = list.find(x => x && x.error); if (!hit) return null; const err = hit.error || {}; return new Error(err.message || `RPC ${err.code || 'Error'}`); } function aria2RpcHttpRequest(url, payload, timeout = 5000) { return new Promise((resolveReq, rejectReq) => { GM_xmlhttpRequest({ method: 'POST', url, data: JSON.stringify(payload), headers: { 'Content-Type': 'application/json' }, timeout, onload: (r) => { if (r.status !== 200) { rejectReq(new Error('HTTP ' + r.status)); return; } try { const data = r.responseText ? JSON.parse(r.responseText) : null; const rpcErr = getAriaRpcError(data); if (rpcErr) rejectReq(rpcErr); else resolveReq(data); } catch (e) { rejectReq(new Error('Invalid JSON')); } }, onerror: () => rejectReq(new Error('Network Error')), ontimeout: () => rejectReq(new Error('Timeout')) }); }); } function aria2RpcWsRequest(url, payload, timeout = 5000) { return new Promise((resolveReq, rejectReq) => { let ws = null; let settled = false; const requests = Array.isArray(payload) ? payload : [payload]; const pending = new Set(requests.map(req => String(req.id))); const results = []; const finish = (ok, value) => { if (settled) return; settled = true; clearTimeout(timer); try { if (ws && ws.readyState <= 1) ws.close(); } catch (e) {} ok ? resolveReq(value) : rejectReq(value); }; const timer = setTimeout(() => finish(false, new Error('Timeout')), timeout); try { ws = new WebSocket(url); } catch (e) { finish(false, e); return; } ws.onopen = () => { try { requests.forEach(req => ws.send(JSON.stringify(req))); } catch (e) { finish(false, e); } }; ws.onmessage = (ev) => { try { const data = JSON.parse(ev.data); const list = Array.isArray(data) ? data : [data]; const rpcErr = getAriaRpcError(list); if (rpcErr) { finish(false, rpcErr); return; } list.forEach(item => { if (!item || item.id === undefined || item.id === null) return; const id = String(item.id); if (!pending.has(id)) return; pending.delete(id); results.push(item); }); if (pending.size === 0) { finish(true, Array.isArray(payload) ? results : results[0]); } } catch (e) { finish(false, new Error('Invalid JSON')); } }; ws.onerror = () => finish(false, new Error('Network Error')); ws.onclose = () => { if (!settled) finish(false, new Error('Network Error')); }; }); } function aria2RpcRequest(url, payload, timeout = 5000) { const rpcUrl = normalizeAriaRpcUrl(url); return isAriaWsRpcUrl(rpcUrl) ? aria2RpcWsRequest(rpcUrl, payload, timeout) : aria2RpcHttpRequest(rpcUrl, payload, timeout); } if (document.querySelector('.pk-ov')) return; document.body.style.overflow = 'hidden'; document.documentElement.style.overflow = 'hidden'; const S = { path: (globalSavedState && globalSavedState.path) ? [...globalSavedState.path] : [{ id: '', name: L.btn_nav_home }], items: [], itemMap: new Map(), display: [], sel: new Set(), selMode: 'explicit', selEx: new Set(), cache: initialCache || new Map(), sort: (globalSavedState && globalSavedState.sort) ? globalSavedState.sort : 'modified_time', dir: (globalSavedState && globalSavedState.dir !== undefined) ? globalSavedState.dir : 1, scanning: false, dupMode: false, dupRunning: false, folderFirst: false, dupReasons: new Map(), dupGroups: new Map(), dupRawGroups: [], dupGridMeta: null, dupGridMetaKey: '', dupGridMetaLayoutKey: '', dupGridMetaDisplayRef: null, dupGridMetaDirty: true, dupGridScrollRaf: 0, dupGridScrollEndTimer: 0, dupGridScrollDirty: false, dupGridLastScrollTop: 0, dupGridLastRenderedTop: -1, offlineFilters: { running: true, failed: true, complete: true }, uploadFilters: { running: true, paused: true, complete: true }, activeId: null, clipItems: [], clipType: '', clipSourceParentId: null, loading: false, pagingLoading: false, lastSelIdx: -1, dupConfig: { video: true, image: true, other: true }, search: '', viewMode: (globalSavedState && globalSavedState.viewMode) ? globalSavedState.viewMode : (() => { const readSavedFolderView = (key, fallback = 'grid') => { try { const prefs = JSON.parse(gmGet('pk_folder_view_prefs', '{}')); const saved = prefs[key]; const mode = typeof saved === 'string' ? saved : (saved && saved.viewMode); return mode === 'list' ? 'list' : (mode === 'grid' ? 'grid' : fallback); } catch(e) { return fallback; } }; if (globalSavedState && globalSavedState.trashMode) { return readSavedFolderView('trash_root', 'list'); } if (gmGet('pk_view_independent', false)) { return readSavedFolderView('root', 'grid'); } return gmGet('pk_file_view_mode', 'grid') === 'list' ? 'list' : 'grid'; })(), sortId: 0, isFlattened: false, filterState: { active: false, cat: 'all', ext: 'all' }, suppressClearConfirm: false, preSearchPath: null, lastGlobalResults: [], folderLineageMap: globalLineageMap, strictFolderFirstSnapshot: null, trashMode: (globalSavedState && globalSavedState.trashMode) || false, shareMode: (globalSavedState && globalSavedState.shareMode) || false, shareParseMode: (globalSavedState && globalSavedState.shareParseMode) || false, shareParseInfo: null, shareParseDraft: { raw: '', pass_code: '' }, shareParseLoading: false, shareParseError: '', shareParseReqId: 0, shareParseListActive: false, shareParseItems: [], shareParseDisplay: [], shareParseItemMap: new Map(), shareParseFolderCache: new Map(), shareParsePath: [], shareParseCurParentId: '', shareParseNextPageToken: '', shareParseAutoLoadToken: 0, shareParseAutoLoadRunning: false, shareParseAutoLoadTarget: '', shareParseNavSessionKey: '', shareParseNavBackStack: [], shareParseNavForwardStack: [], shareParseNavBusy: false, shareParseNavSuspendRecord: false, shareParseLatestChildId: null, shareParseListLoading: false, shareParseListError: '', shareParseListReqId: 0, shareParseSaving: false, shareParseSaveReqId: 0, shareParseSaveError: '', shareParseSaveResult: null, shareParseInsightMode: false, shareParseInsightItems: [], shareParseInsightDisplay: [], shareParseInsightItemMap: new Map(), shareParseInsightFilterState: { active: false, cat: 'all', ext: 'all' }, shareParseInsightScanning: false, shareParseInsightStop: false, shareParseInsightProgress: { folder: 0, file: 0 }, shareParseInsightError: '', shareParseInsightReqId: 0, shareParseInsightReturnPath: [], shareParseInsightReturnParentId: '', shareParseInsightToastStopped: false, starredMode: (globalSavedState && globalSavedState.starredMode) || false, recentMode: (globalSavedState && globalSavedState.recentMode) || false, recentRefreshFirstPageOnly: false, recentLoadNextPageOnly: false, recentLoadingNextPage: false, historyMode: (globalSavedState && globalSavedState.historyMode) || false, offlineMode: (globalSavedState && globalSavedState.offlineMode) || false, uploadMode: (globalSavedState && globalSavedState.uploadMode) || false, navBuckets: { home: { backStack: [], current: null, forwardStack: [] }, starred: { backStack: [], current: null, forwardStack: [] }, recent: { backStack: [], current: null, forwardStack: [] } }, navScrollStore: { home: Object.create(null), starred: Object.create(null), recent: Object.create(null) }, navActiveBucket: null, navFrozenBucket: null, navContext: 'none', navSyncSig: '', navMaxHistory: CONF.mouseSideNavHistoryMax, navSuspendRecord: false, navTransitionBusy: false, navLastModalType: '', navVideoBackArmed: false, scanId: 0, preloaded: (globalSavedState || (initialCache && initialCache.has('root'))) ? true : false, preLoadPromise: preloadPromise || null, blSet: new Set(), blFolderSet: new Set(), starredSet: new Set(), pendingMap: new Map(), durationMap: new Map(), loadedThumbs: new Set(), movingIds: new Set(), movingSourceId: null, movingDestId: null, uploadTasks: (globalSavedState && globalSavedState.uploadTasks) ? globalSavedState.uploadTasks : [], broadcast: new BroadcastChannel('pk_act_sync'), getNavBucketKey: () => { if (S.starredMode) return 'starred'; if (S.recentMode) return 'recent'; if (S.trashMode || S.shareMode || S.shareParseMode || S.historyMode || S.offlineMode || S.uploadMode) return null; if (S.isFlattened || S.dupMode || S.analyzeMode) return null; const cur = Array.isArray(S.path) && S.path.length ? S.path[S.path.length - 1] : null; if (cur && typeof cur.id === 'string' && (cur.id.startsWith('virtual_') || cur.id === 'analyze_root')) return null; return 'home'; }, getActiveNavBucket: () => { const key = S.getNavBucketKey(); return key ? S.navBuckets[key] : null; }, getShareParseNavSessionKey: () => { const info = S.shareParseInfo || {}; return info.share_id && info.pass_code_token ? `${info.share_id}|${info.pass_code_token}` : ''; }, resetShareParseSideNav: () => { S.shareParseNavSessionKey = ''; S.shareParseNavBackStack = []; S.shareParseNavForwardStack = []; S.shareParseNavBusy = false; S.shareParseNavSuspendRecord = false; }, ensureShareParseNavSession: () => { const key = S.getShareParseNavSessionKey(); if (!key) { S.resetShareParseSideNav(); return ''; } if (S.shareParseNavSessionKey !== key) { S.shareParseNavSessionKey = key; S.shareParseNavBackStack = []; S.shareParseNavForwardStack = []; S.shareParseNavBusy = false; S.shareParseNavSuspendRecord = false; } return key; }, cloneShareParsePath: (path = S.shareParsePath) => (Array.isArray(path) ? path : []).map(n => ({ id: n && n.id || '', name: n && n.name || '', parent_id: n && n.parent_id || '', _sharePanel: !!(n && n._sharePanel), _shareRoot: !!(n && n._shareRoot) })), isSameShareParsePath: (a, b) => { if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if ((a[i] && a[i].id || '') !== (b[i] && b[i].id || '')) return false; } return true; }, trimShareParseNavStack: (stack) => { if (!Array.isArray(stack) || stack.length <= S.navMaxHistory) return; stack.splice(0, stack.length - S.navMaxHistory); }, makeShareParseNavSnapshot: () => { const sessionKey = S.ensureShareParseNavSession(); if (!sessionKey || !S.shareParseMode || !S.shareParseListActive || S.shareParseInsightMode) return null; const path = S.cloneShareParsePath(S.shareParsePath); if (path.length < 2) return null; return { sessionKey, path, parentId: S.shareParseCurParentId || getShareParseRootParentId(), scrollTop: UI.vp ? Math.max(0, Math.round(UI.vp.scrollTop || 0)) : 0 }; }, pushShareParseNavSnapshot: (snap = null, clearForward = true) => { const curSnap = snap || S.makeShareParseNavSnapshot(); if (!curSnap || !curSnap.sessionKey) return false; const stack = S.shareParseNavBackStack || (S.shareParseNavBackStack = []); const last = stack[stack.length - 1]; if (!last || !S.isSameShareParsePath(last.path, curSnap.path)) { stack.push(curSnap); S.trimShareParseNavStack(stack); } if (clearForward) S.shareParseNavForwardStack = []; return true; }, loadShareParseNavSnapshot: async (snap) => { if (!snap || !Array.isArray(snap.path) || snap.path.length < 2) return false; if (snap.sessionKey !== S.getShareParseNavSessionKey()) return false; const target = snap.path[snap.path.length - 1]; S.shareParseNavBusy = true; S.shareParseNavSuspendRecord = true; try { await loadShareParseFolder({ id: snap.parentId || (target && target.id) || getShareParseRootParentId(), name: target && target.name || '', parent_id: target && target.parent_id || '', _sharePath: S.cloneShareParsePath(snap.path) }, { append: false, suppressShareParseNav: true, shareParseRestoreScrollTop: snap.scrollTop }); return true; } finally { S.shareParseNavSuspendRecord = false; S.shareParseNavBusy = false; } }, shareParseSideBack: async () => { if (S.shareParseMode && S.shareParseInsightMode) { handleShareParseInsightBack(); return true; } if (!S.shareParseMode || !S.shareParseListActive || S.shareParseNavBusy) return false; if (!S.ensureShareParseNavSession()) return false; const curPath = S.cloneShareParsePath(S.shareParsePath); if (curPath.length <= 2) return false; const back = S.shareParseNavBackStack || []; if (!back.length) return false; let target = back.pop(); while (target && (!Array.isArray(target.path) || target.path.length < 2 || target.sessionKey !== S.getShareParseNavSessionKey())) { target = back.pop(); } if (!target) return false; const curSnap = S.makeShareParseNavSnapshot(); const ok = await S.loadShareParseNavSnapshot(target); if (!ok) { back.push(target); return false; } if (curSnap) { S.shareParseNavForwardStack.push(curSnap); S.trimShareParseNavStack(S.shareParseNavForwardStack); } return true; }, shareParseSideForward: async () => { if (!S.shareParseMode || !S.shareParseListActive || S.shareParseInsightMode || S.shareParseNavBusy) return false; if (!S.ensureShareParseNavSession()) return false; const forward = S.shareParseNavForwardStack || []; if (!forward.length) return false; let target = forward.pop(); while (target && (!Array.isArray(target.path) || target.path.length < 2 || target.sessionKey !== S.getShareParseNavSessionKey())) { target = forward.pop(); } if (!target) return false; const curSnap = S.makeShareParseNavSnapshot(); const ok = await S.loadShareParseNavSnapshot(target); if (!ok) { forward.push(target); return false; } if (curSnap) { S.shareParseNavBackStack.push(curSnap); S.trimShareParseNavStack(S.shareParseNavBackStack); } return true; }, cloneNavPath: (path = S.path) => (Array.isArray(path) ? path : []).map(n => ({ id: n.id, name: n.name })), getNavPathSig: (path = S.path) => { const arr = Array.isArray(path) ? path : []; return arr.map(n => n && n.id || '').join('>'); }, saveNavScrollTop: (bucketKey = null, path = S.path, scrollTop = null) => { const key = bucketKey || S.getNavBucketKey() || S.navActiveBucket || null; if (!key || !S.navScrollStore[key] || !UI.vp) return; const sig = S.getNavPathSig(path); if (!sig) return; S.navScrollStore[key][sig] = Math.max(0, Math.round(scrollTop == null ? UI.vp.scrollTop : scrollTop)); }, getNavScrollTop: (bucketKey = null, path = S.path) => { const key = bucketKey || S.getNavBucketKey() || S.navActiveBucket || null; if (!key || !S.navScrollStore[key]) return null; const sig = S.getNavPathSig(path); if (!sig) return null; const v = S.navScrollStore[key][sig]; return Number.isFinite(v) ? v : null; }, restoreNavScrollTop: (bucketKey = null, path = S.path) => { if (!gmGet('pk_keep_pos', true) || !UI.vp) return; const savedTop = S.getNavScrollTop(bucketKey, path); if (!Number.isFinite(savedTop)) return; requestAnimationFrame(() => { if (!UI.vp) return; const maxTop = Math.max(0, UI.vp.scrollHeight - UI.vp.clientHeight); UI.vp.scrollTop = Math.max(0, Math.min(savedTop, maxTop)); if (typeof renderVisible === 'function') renderVisible(); }); }, isSameNavPath: (a, b) => { if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if ((a[i] && a[i].id) !== (b[i] && b[i].id)) return false; } return true; }, trimNavStack: (stack) => { if (!Array.isArray(stack) || stack.length <= S.navMaxHistory) return; stack.splice(0, stack.length - S.navMaxHistory); }, getNavContext: () => { if (S.isStrictVirtualNavMode()) return 'virtual'; const key = S.getNavBucketKey(); return key || 'none'; }, recordNavSnapshot: (bucketKey, path = S.path) => { const bucket = bucketKey ? S.navBuckets[bucketKey] : null; if (!bucket) return; const snap = S.cloneNavPath(path); if (!snap.length) return; if (bucket.current && S.isSameNavPath(bucket.current, snap)) return; if (bucket.current && bucket.current.length) { bucket.backStack.push(S.cloneNavPath(bucket.current)); S.trimNavStack(bucket.backStack); } bucket.current = snap; bucket.forwardStack = []; }, restoreFrozenNavBucketPath: () => { const key = S.navFrozenBucket; const bucket = key ? S.navBuckets[key] : null; if (!bucket || !bucket.current || !bucket.current.length) return false; S.navSuspendRecord = true; S.path = S.cloneNavPath(bucket.current); S.navActiveBucket = key; S.navContext = key; S.navSyncSig = ''; queueMicrotask(() => { S.navSuspendRecord = false; }); return true; }, loadNavPath: async (path, bucketKey = null, opts = null) => { const targetPath = S.cloneNavPath(path); if (!targetPath.length) return false; if (S.navTransitionBusy) return false; const key = bucketKey || S.getNavBucketKey() || S.navActiveBucket || null; const skipNavSync = !!(opts && opts.skipNavSync); const prevPath = S.cloneNavPath(S.path); const prevActiveBucket = S.navActiveBucket; const prevContext = S.navContext; const prevSyncSig = S.navSyncSig; const prevSuspendRecord = S.navSuspendRecord; const prevScrollTop = UI.vp ? UI.vp.scrollTop : 0; const prevSelMode = S.selMode; const prevSel = new Set(S.sel); const prevSelEx = new Set(S.selEx); const prevLastSelIdx = S.lastSelIdx; const prevActiveIdForRollback = S.activeId; const targetSig = S.getNavPathSig(targetPath); S.navTransitionBusy = true; S.navSuspendRecord = true; S.saveNavScrollTop(prevActiveBucket || S.getNavBucketKey(), prevPath, prevScrollTop); S.path = targetPath; S.navActiveBucket = key; S.navContext = key || 'none'; S.navSyncSig = ''; try { updateQuotaUI(); const pendingLoad = Promise.resolve(load()); await new Promise(r => requestAnimationFrame(r)); S.clearSelection(); pendingLoad.then(() => { if (S.getNavPathSig() === targetSig) { S.restoreNavScrollTop(key, targetPath); } }).catch(()=>{}); return true; } catch (err) { S.path = prevPath; S.navActiveBucket = prevActiveBucket; S.navContext = prevContext; S.navSyncSig = prevSyncSig || ''; S.selMode = prevSelMode; S.sel = new Set(prevSel); S.selEx = new Set(prevSelEx); S.lastSelIdx = prevLastSelIdx; S.activeId = prevActiveIdForRollback; if (typeof updateStat === 'function') updateStat(); try { updateQuotaUI(); await load(); S.restoreNavScrollTop(prevActiveBucket || S.getNavBucketKey(), prevPath); } catch (restoreErr) {} return false; } finally { S.navSuspendRecord = prevSuspendRecord; S.navTransitionBusy = false; if (!skipNavSync) { S.navSyncSig = ''; S.syncNavContextState(); } } }, navGoBack: async () => { const key = S.getNavBucketKey(); const bucket = key ? S.navBuckets[key] : null; if (!bucket || !bucket.backStack.length || S.navTransitionBusy) return false; const prevPath = S.cloneNavPath(bucket.backStack[bucket.backStack.length - 1]); if (!prevPath.length) return false; const currentPath = bucket.current && bucket.current.length ? S.cloneNavPath(bucket.current) : S.cloneNavPath(S.path); const ok = await S.loadNavPath(prevPath, key, { skipNavSync: true }); if (!ok) return false; bucket.backStack.pop(); if (currentPath.length) { bucket.forwardStack.push(currentPath); S.trimNavStack(bucket.forwardStack); } bucket.current = S.cloneNavPath(prevPath); S.navActiveBucket = key; S.navContext = key; S.navFrozenBucket = null; S.navSyncSig = ''; S.syncNavContextState(); return true; }, navGoForward: async () => { const key = S.getNavBucketKey(); const bucket = key ? S.navBuckets[key] : null; if (!bucket || !bucket.forwardStack.length || S.navTransitionBusy) return false; const nextPath = S.cloneNavPath(bucket.forwardStack[bucket.forwardStack.length - 1]); if (!nextPath.length) return false; const currentPath = bucket.current && bucket.current.length ? S.cloneNavPath(bucket.current) : S.cloneNavPath(S.path); const ok = await S.loadNavPath(nextPath, key, { skipNavSync: true }); if (!ok) return false; bucket.forwardStack.pop(); if (currentPath.length) { bucket.backStack.push(currentPath); S.trimNavStack(bucket.backStack); } bucket.current = S.cloneNavPath(nextPath); S.navActiveBucket = key; S.navContext = key; S.navFrozenBucket = null; S.navSyncSig = ''; S.syncNavContextState(); return true; }, exitVirtualNavMode: async () => { const cur = Array.isArray(S.path) && S.path.length ? S.path[S.path.length - 1] : null; const isAnalyzeGroupedGrid = S.analyzeMode && !!S.analyzeSimGroups && !!cur && cur.id === 'analyze_root' && S.viewMode === 'grid'; const shouldSyncGroupedGridExit = (S.dupMode && S.viewMode === 'grid') || isAnalyzeGroupedGrid; S.scanning = false; S.dupMode = false; S.suppressClearConfirm = false; S.isFlattened = false; S.scanFilter = null; if (S.filterState) S.filterState = { active: false, cat: 'all', ext: 'all' }; if (shouldSyncGroupedGridExit) S._groupedGridExitSyncPending = true; S._sortAppliedForId = null; S._comicApplied = false; if (S.analyzeMode) { S.analyzeMode = false; S.analyzeResultItems = null; S.analyzeSimGroups = null; S.analyzeMap = null; } S.dupRawGroups = []; S.dupBuckets = null; S.dupItemMap = null; S.pinnedDupPath = null; if (UI.selDupFolder) UI.selDupFolder.value = ""; const invertChk = document.getElementById('pk-dup-invert'); if (invertChk) { invertChk.checked = false; invertChk.disabled = true; invertChk.parentNode.style.opacity = '0.5'; } if (UI.chkSearchPath) UI.chkSearchPath.checked = false; isGUISensitive = false; S.sort = 'modified_time'; S.dir = 1; if (UI.crumb) UI.crumb.style.display = ''; const frozenKey = S.navFrozenBucket; const frozenBucket = frozenKey ? S.navBuckets[frozenKey] : null; const fallbackPath = [{ id: '', name: L.btn_nav_home }]; const targetPath = (frozenBucket && frozenBucket.current && frozenBucket.current.length) ? frozenBucket.current : fallbackPath; S.navFrozenBucket = null; const targetLeaf = targetPath[targetPath.length - 1] || { id: '' }; if (typeof applyResolvedViewMode === 'function') { applyResolvedViewMode((targetLeaf && targetLeaf.id) || ''); } return await S.loadNavPath(targetPath, frozenBucket ? frozenKey : null); }, getTopModalOverlay: () => { const list = Array.from(document.querySelectorAll('.pk-modal-ov')).filter(m => { if (!m || !m.isConnected) return false; const st = window.getComputedStyle(m); return st.display !== 'none' && st.visibility !== 'hidden'; }); return list.length ? list[list.length - 1] : null; }, hasFrontOverlayForSideNav: () => { return !!(document.getElementById('pk-player-ov') || document.getElementById('pk-audio-ov') || document.querySelector('.pk-img-ov') || S.getTopModalOverlay()); }, isManagerSideNavScope: () => { const win = document.querySelector('.pk-ov'); return !!(win && win.style.display !== 'none'); }, closePlayerOverlay: () => { const player = document.getElementById('pk-player-ov'); if (!player) return false; S.navVideoBackArmed = false; const pTip = document.getElementById('pk_p_plist_tip_global'); if (pTip) pTip.style.display = 'none'; if (typeof player._pkDestroyPlayer === 'function') { player._pkDestroyPlayer(); return true; } const v = player.querySelector('#pk_video'); if (v) { try { v.pause(); v.removeAttribute('src'); v.load(); } catch (e) {} } player.remove(); return true; }, closeAudioOverlay: () => { const player = document.getElementById('pk-audio-ov'); if (!player) return false; if (typeof player._pkDestroyAudioPlayer === 'function') { player._pkDestroyAudioPlayer(); return true; } const audio = player.querySelector('#pk_audio'); if (audio) { try { audio.pause(); audio.removeAttribute('src'); audio.load(); } catch (e) {} } player.remove(); return true; }, closeImageOverlay: () => { const d = document.querySelector('.pk-img-ov'); if (!d) return false; const pTip = document.getElementById('pk_p_plist_tip_global'); if (pTip) pTip.style.display = 'none'; const cleanup = d._pkResizeHandler; if (cleanup) { window.removeEventListener('resize', cleanup); d._pkResizeHandler = null; } const imgList = d._pkImgList || []; const curIdx = Number.isInteger(d._pkCurIdx) ? d._pkCurIdx : -1; const currentItem = curIdx >= 0 ? imgList[curIdx] : null; if (currentItem) { const targetId = currentItem.id; const targetIdx = S.display.findIndex(x => x.id === targetId); if (targetIdx !== -1) { S.sel.clear(); S.sel.add(targetId); S.activeId = targetId; const rowTop = getItemScrollTopByIndex(targetIdx); const vpHeight = UI.vp.clientHeight; UI.vp.scrollTop = Math.max(0, rowTop - (vpHeight / 2) + (CONF.rowHeight / 2)); renderVisible(); updateStat(); } } d.remove(); return true; }, closeTopModalOverlay: () => { const openModal = S.getTopModalOverlay(); if (!openModal) return false; const closeBtn = openModal.querySelector('.pk-modal-close'); if (closeBtn) closeBtn.click(); else openModal.remove(); return true; }, handleOverlayMouseSideBack: () => { const player = document.getElementById('pk-player-ov'); if (player) { return S.closePlayerOverlay(); } const audioPlayer = document.getElementById('pk-audio-ov'); if (audioPlayer) { return S.closeAudioOverlay(); } const imgPlayer = document.querySelector('.pk-img-ov'); if (imgPlayer) { return S.closeImageOverlay(); } if (S.closeTopModalOverlay()) { return true; } return false; }, canHandleMouseSideBack: () => S.isManagerSideNavScope(), canHandleMouseSideForward: () => S.isManagerSideNavScope(), handleMouseSideBack: async () => { if (!S.isManagerSideNavScope() || S.navTransitionBusy) return false; if (S.handleOverlayMouseSideBack()) return true; if (S.shareParseMode) { await S.shareParseSideBack(); return true; } if (S.isStrictVirtualNavMode()) { await S.exitVirtualNavMode(); return true; } if (S.getNavBucketKey() !== null) { await S.navGoBack(); return true; } return true; }, handleMouseSideForward: async () => { if (!S.isManagerSideNavScope() || S.navTransitionBusy) return false; const player = document.getElementById('pk-player-ov'); if (player) { const v = player.querySelector('#pk_video'); if (v) { if (v.ended) { try { v.currentTime = 0; } catch (e) {} v.play().catch(()=>{}); return true; } if (v.paused) { v.play().catch(()=>{}); return true; } v.pause(); return true; } return true; } if (S.hasFrontOverlayForSideNav()) return true; if (S.shareParseMode) { await S.shareParseSideForward(); return true; } if (S.isStrictVirtualNavMode()) return true; if (S.getNavBucketKey() !== null) { await S.navGoForward(); return true; } return true; }, syncNavContextState: () => { const ctx = S.getNavContext(); const sig = `${ctx}|${(Array.isArray(S.path) ? S.path.map(n => n.id).join('>') : '')}`; if (S.navSyncSig === sig) return; const prevCtx = S.navContext; const prevBucketKey = (prevCtx === 'home' || prevCtx === 'starred' || prevCtx === 'recent') ? prevCtx : null; const curBucketKey = (ctx === 'home' || ctx === 'starred' || ctx === 'recent') ? ctx : null; if (S.navSuspendRecord) { S.navContext = ctx; S.navSyncSig = sig; if (curBucketKey) S.navActiveBucket = curBucketKey; return; } if (ctx === 'virtual') { if (prevBucketKey) S.navFrozenBucket = prevBucketKey; S.navContext = 'virtual'; S.navSyncSig = sig; return; } if (ctx === 'none') { if (prevBucketKey) S.navFrozenBucket = prevBucketKey; S.navContext = 'none'; S.navSyncSig = sig; return; } S.navActiveBucket = curBucketKey; S.recordNavSnapshot(curBucketKey); if (prevCtx === 'virtual' && S.navFrozenBucket === curBucketKey) { S.navFrozenBucket = null; } else if (prevCtx !== 'virtual') { S.navFrozenBucket = null; } S.navContext = curBucketKey; S.navSyncSig = sig; }, canUseMouseSideNav: () => S.canHandleMouseSideBack() || S.canHandleMouseSideForward(), isStrictVirtualNavMode: () => { if (S.isFlattened || S.dupMode || S.analyzeMode) return true; const cur = Array.isArray(S.path) && S.path.length ? S.path[S.path.length - 1] : null; return !!(cur && typeof cur.id === 'string' && (cur.id.startsWith('virtual_') || cur.id === 'analyze_root')); }, getSelectableItems: () => { const out = []; const list = Array.isArray(S.display) ? S.display : []; for (let i = 0; i < list.length; i++) { const item = list[i]; if (!item || item.isHeader) continue; if (S.shareParseMode && (!S.shareParseListActive || !item._isShareItem)) continue; if (S.movingIds && S.movingIds.has(item.id)) continue; out.push(item); } return out; }, getSelectableIds: () => { const out = []; const list = Array.isArray(S.display) ? S.display : []; for (let i = 0; i < list.length; i++) { const item = list[i]; if (!item || item.isHeader) continue; if (S.shareParseMode && (!S.shareParseListActive || !item._isShareItem)) continue; if (S.movingIds && S.movingIds.has(item.id)) continue; out.push(item.id); } return out; }, getSelectableCount: () => { let count = 0; const list = Array.isArray(S.display) ? S.display : []; for (let i = 0; i < list.length; i++) { const item = list[i]; if (!item || item.isHeader) continue; if (S.shareParseMode && (!S.shareParseListActive || !item._isShareItem)) continue; if (S.movingIds && S.movingIds.has(item.id)) continue; count++; } return count; }, isAllSelectionMode: () => S.selMode === 'all', isSelected: (id) => { if (!id) return false; return S.selMode === 'all' ? !S.selEx.has(id) : S.sel.has(id); }, getSelectedCount: () => { return S.getSelectedIds().length; }, getSelectedIds: () => { const isValidShareId = (id) => { if (!S.shareParseMode) return true; const item = (S.shareParseInsightItemMap && S.shareParseInsightItemMap.get(id)) || (S.shareParseItemMap && S.shareParseItemMap.get(id)) || (S.itemMap && S.itemMap.get(id)); return !!(S.shareParseListActive && item && item._isShareItem); }; if (S.selMode !== 'all') return Array.from(S.sel).filter(isValidShareId); const ids = []; const list = S.getSelectableIds(); for (let i = 0; i < list.length; i++) { const id = list[i]; if (!S.selEx.has(id) && isValidShareId(id)) ids.push(id); } return ids; }, setSelected: (id, selected) => { if (!id) return; if (S.shareParseMode) { const item = (S.shareParseInsightItemMap && S.shareParseInsightItemMap.get(id)) || (S.shareParseItemMap && S.shareParseItemMap.get(id)) || (S.itemMap && S.itemMap.get(id)); if (!S.shareParseListActive || !item || !item._isShareItem) return; } if (S.selMode === 'all') { if (selected) S.selEx.delete(id); else S.selEx.add(id); return; } if (selected) S.sel.add(id); else S.sel.delete(id); }, toggleSelected: (id) => { if (!id) return false; const next = !S.isSelected(id); S.setSelected(id, next); return next; }, setExplicitSelection: (ids) => { S.selMode = 'explicit'; S.selEx.clear(); let nextIds = Array.isArray(ids) ? ids : []; if (S.shareParseMode) { nextIds = S.shareParseListActive ? nextIds.filter(id => { const item = (S.shareParseInsightItemMap && S.shareParseInsightItemMap.get(id)) || (S.shareParseItemMap && S.shareParseItemMap.get(id)) || (S.itemMap && S.itemMap.get(id)); return item && item._isShareItem; }) : []; } S.sel = new Set(nextIds); }, setAllSelection: (enabled = true) => { if (enabled) { if (S.shareParseMode && !S.shareParseListActive) { S.selMode = 'explicit'; S.sel.clear(); S.selEx.clear(); return; } S.selMode = 'all'; S.sel.clear(); S.selEx.clear(); } else { S.selMode = 'explicit'; S.sel.clear(); S.selEx.clear(); } }, invertSelection: () => { const total = S.getSelectableCount(); if (total <= 0) { S.clearSelection(); return; } if (S.selMode === 'all') { S.setExplicitSelection(Array.from(S.selEx)); } else { const nextExcluded = new Set(S.sel); S.selMode = 'all'; S.sel.clear(); S.selEx = nextExcluded; } S.lastSelIdx = -1; S.activeId = null; }, clearSelection: () => { S.selMode = 'explicit'; S.sel.clear(); S.selEx.clear(); S.lastSelIdx = -1; S.activeId = null; if (typeof updateStat === 'function') updateStat(); }, updateBlCache: () => { const parse = (key) => new Set(gmGet(key, '').toLowerCase().split('\n').map(s => s.trim()).filter(s => s)); S.blSet = parse('pk_blacklist'); S.blFolderSet = parse('pk_blacklist_folders'); }, getRealCacheKey: (id) => { if (id === 'trash_root') return 'root_trashed'; if (id === 'offline_root') return 'offline_root'; if (id === 'recent_root') return 'recent_root'; if (id === 'history_root') return 'history_root'; if (id === 'root' || id === '' || !id) { if (S.shareMode) return 'share_root'; if (S.starredMode) return 'starred_root'; if (S.recentMode) return 'recent_root'; if (S.historyMode) return 'history_root'; if (S.trashMode) return 'root_trashed'; if (S.offlineMode) return 'offline_root'; return 'root'; } return id; } }; pkState = S; if (S.uploadTasks.length > 0) { S.uploadTasks.forEach(task => { if (task.status === 'WAITING') task.message = L.msg_task_waiting; else if (task.status === 'DONE') task.message = L.msg_task_upload_done; else if (task.status === 'PAUSED') task.message = L.msg_task_paused; else if (task.status === 'HASHING') task.message = L.msg_task_hashing; else if (task.status === 'UPLOADING') task.message = L.msg_task_uploading; }); } const FloatBarManager = (() => { const activeBars = []; const BASE_BOTTOM = 30; const GAP = 10; const reposition = () => { let currentBottom = BASE_BOTTOM; activeBars.forEach(item => { item.el.style.bottom = `${currentBottom}px`; currentBottom += (item.el.offsetHeight || 40) + GAP; }); }; const create = (initialText) => { const id = 'pk_fb_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); const el = document.createElement('div'); el.className = 'pk-float-bar-item'; el.style.cssText = ` position: fixed; left: 50%; transform: translateX(-50%); background: var(--pk-toast-bg); color: var(--pk-toast-fg); padding: 10px 24px; border-radius: 30px; font-size: 13px; font-weight: 600; box-shadow: 0 10px 30px var(--pk-tip-sd); z-index: 2147483647 !important; border: 1px solid var(--pk-toast-bd); display: flex; align-items: center; gap: 12px; transition: bottom 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease; opacity: 0; pointer-events: none; white-space: nowrap; backdrop-filter: blur(8px); `; el.innerHTML = `
${esc(initialText)}`; const isDark = document.querySelector('.pk-ov')?.classList.contains('pk-dark'); if (isDark) el.classList.add('pk-dark'); const fsEl = document.fullscreenElement || document.webkitFullscreenElement; if (fsEl) fsEl.appendChild(el); else document.body.appendChild(el); const entry = { id, el }; activeBars.push(entry); requestAnimationFrame(() => { reposition(); el.style.opacity = '1'; }); return { update: (text) => { const span = el.querySelector('.pk-float-txt'); if (span) span.textContent = text; }, destroy: () => { const idx = activeBars.findIndex(x => x.id === id); if (idx !== -1) { activeBars.splice(idx, 1); el.style.opacity = '0'; el.style.transform = 'translateX(-50%) scale(0.9)'; reposition(); setTimeout(() => el.remove(), 300); } } }; }; return { create }; })(); S.broadcast.onmessage = (e) => { const { type, ids, src, dst } = e.data; if (!ids) return; if (type === 'LOCK_ADD') { ids.forEach(id => S.movingIds.add(id)); if (src) S.movingSourceId = src; if (dst) S.movingDestId = dst; } else if (type === 'LOCK_REM') { ids.forEach(id => S.movingIds.delete(id)); } else if (type === 'LOCK_CLR') { S.movingIds.clear(); S.movingSourceId = null; S.movingDestId = null; } if (typeof updateGlobalLockCSS === 'function') updateGlobalLockCSS(); if (typeof updateStat === 'function') updateStat(); }; const updateGlobalLockCSS = () => { let style = document.getElementById('pk-lock-css'); if (!style) { style = document.createElement('style'); style.id = 'pk-lock-css'; document.head.appendChild(style); } if (!S.movingIds || S.movingIds.size === 0 || !S.movingSourceId) { style.textContent = ''; return; } const selector = Array.from(S.movingIds) .map(id => `.pk-win .pk-row[data-id="${id}"]`) .join(','); style.textContent = `${selector} { opacity: 0.4 !important; filter: grayscale(100%) !important; pointer-events: none !important; cursor: wait !important; }`; }; const isPathBusy = (targetFolderId) => { if (!S.movingIds || S.movingIds.size === 0) return false; const sourceId = S.movingSourceId; const destId = S.movingDestId; const tid = targetFolderId === 'root' ? '' : (targetFolderId || ''); if (tid === '') return true; if (tid === sourceId || tid === destId) return true; const checkAncestry = (startId, forbiddenId) => { let curr = startId; let safety = 50; while (curr && curr !== 'root' && safety > 0) { if (curr === forbiddenId) return true; const parent = globalParentIndex.get(curr); curr = parent ? parent.id : null; safety--; } return false; }; if (checkAncestry(sourceId, tid) || checkAncestry(destId, tid)) return true; if (checkAncestry(tid, sourceId) || checkAncestry(tid, destId)) return true; return false; }; const resumeBackgroundDiscovery = () => { if (S.scanning || S.loading) { return; } const isVirtual = S.isFlattened || S.dupMode || S.path.some(p => p.id === 'virtual_search_root'); if (isVirtual) { return; } let addedCount = 0; if (typeof globalCache !== 'undefined') { for (const [parentFolderId, files] of globalCache) { if (!files || !Array.isArray(files)) continue; for (let i = 0; i < files.length; i++) { const f = files[i]; if (f.kind === 'drive#folder' && !scannedFolderIds.has(f.id) && !globalCache.has(f.id)) { backgroundQueue.push({ id: f.id, name: f.name, retryCount: 0 }); scannedFolderIds.add(f.id); addedCount++; } } if (addedCount > 500) break; } } if (addedCount === 0) { const visibleSubFolders = S.items.filter(f => f.kind === 'drive#folder'); for (let i = visibleSubFolders.length - 1; i >= 0; i--) { const sub = visibleSubFolders[i]; if (!scannedFolderIds.has(sub.id) && !globalCache.has(sub.id)) { backgroundQueue.push({ id: sub.id, name: sub.name, retryCount: 0 }); scannedFolderIds.add(sub.id); addedCount++; } } } if (addedCount > 0 || backgroundQueue.length > 0) { runBackgroundCrawler(); } }; S.updateBlCache(); if (S.items && S.items.length > 0) { S.itemMap.clear(); S.items.forEach(i => S.itemMap.set(i.id, i)); } const el = document.createElement('div'); el.className = 'pk-ov'; if (gmGet('pk_hide_button_text', false)) el.classList.add('pk-hide-btn-text'); let siteFont = window.getComputedStyle(document.body).fontFamily || ''; siteFont = siteFont.replace(/,?\s*sans-serif\s*$/i, ''); el.style.fontFamily = siteFont ? `${siteFont}, "Noto Sans", sans-serif` : '"Noto Sans", sans-serif'; el.addEventListener('wheel', (e) => { e.stopPropagation(); const scrollTarget = e.target.closest('.pk-vp, .pk-modal, #pk-rn-vp, .pk-prev-list, textarea, .pk-scroll'); if (!scrollTarget) { e.preventDefault(); return; } const st = scrollTarget.scrollTop; const sh = scrollTarget.scrollHeight; const ch = scrollTarget.clientHeight; const isUp = e.deltaY < 0; const isDown = e.deltaY > 0; const isAtTop = st <= 0.5; const isAtBottom = (st + ch) >= (sh - 1.5); if ((isUp && isAtTop) || (isDown && isAtBottom)) { if (e.cancelable) e.preventDefault(); } }, { passive: false }); const mkLbl = (id, txt, shortTxt, tip) => { const tipAttr = tip ? ` data-pk-tip="${tip}"` : ''; return ``; }; const savedTheme = gmGet('pk_theme', 'auto'); const sysDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; let isDark = savedTheme === 'dark' || (savedTheme === 'auto' && sysDark); if (isDark) el.classList.add('pk-dark'); const themeIcon = isDark ? CONF.icons.sun : CONF.icons.moon; const initialGridShell = S.viewMode === 'grid' && !S.shareMode && !S.shareParseMode && !S.offlineMode && !S.uploadMode && !S.historyMode; const winClass = 'pk-win pk-lang-' + lang + (initialGridShell ? ' pk-grid-view' : ''); const initialHeaderStyle = initialGridShell ? 'visibility:hidden;' : ''; const ctxIcons = { locate: ``, info: ``, open: ``, star: ``, unstar: ``, download: ``, copy: ``, move: ``, rename: ``, renameBulk: ``, blAdd: ``, blRem: ``, share: CONF.icons.share, copyLink: ``, copyName: ``, trash: ``, restore: ``, delForever: `` }; el.innerHTML = `
${L.loading_detail}
${CONF.icons.cloudDownload}${L.btn_cloud_download}
${CONF.icons.home}${L.btn_nav_home}
${CONF.icons.navUpload}${L.btn_nav_upload}
${CONF.icons.offline}${L.btn_nav_offline}
${CONF.icons.recent}${L.btn_nav_recent}
${CONF.icons.history}${L.btn_nav_history}
${CONF.icons.navShare}${L.btn_nav_share}
${L.btn_nav_starred}
${CONF.icons.trash}${L.btn_nav_trash}
${CONF.icons.shareParse}${L.menu_share_parse}
--%
${CONF.icons.help}
${CONF.icons.settings}${L.btn_settings}
${CONF.logoSVG}
${L.title} by digbug82
${themeIcon}
${CONF.icons.maximize}
${CONF.icons.close}
${L.tip_upload_official_fallback}
${mkLbl('pk-chk-hash', L.tag_hash, L.tag_hash_short)} ${mkLbl('pk-chk-sim', L.tag_sim, L.tag_sim_short)} ${mkLbl('pk-chk-name', L.tag_name, L.tag_name_short)}
${CONF.icons.upFile} ${L.btn_up_file}
${CONF.icons.upFolder} ${L.btn_up_folder}
${L.col_name}
${CONF.icons.folderFirst} ${L.lbl_folder_first}
${L.col_size}
${L.col_dur}
${L.col_date}
${L.status_ready.replace('{n}', 0)}
${CONF.icons.share} ${L.ctx_share}
${ctxIcons.open} ${L.ctx_open}
${ctxIcons.star} ${L.ctx_star}
${ctxIcons.info} ${L.ctx_property}
${ctxIcons.download} ${L.ctx_down}
${ctxIcons.copyName} ${L.ctx_copy_name}
${ctxIcons.move} ${L.btn_cut}
${ctxIcons.copy} ${L.ctx_copy}
${ctxIcons.rename} ${L.ctx_rename}
${CONF.icons.prune} ${L.btn_prune}
${ctxIcons.trash} ${L.ctx_del}
${ctxIcons.blAdd} ${L.ctx_add_bl}
`; document.body.appendChild(el); const destroyTooltip = (() => { const tipEl = document.createElement('div'); tipEl.className = 'pk-tooltip'; const style = document.createElement('style'); style.textContent = ` .pk-tooltip { width: max-content; max-width: 280px; min-width: auto; background: var(--pk-tip-bg); color: var(--pk-tip-fg); border: 1px solid var(--pk-tip-bd); padding: 6px; box-sizing: border-box; border-radius: 8px; font-size: 12px; line-height: 1.4; position: fixed; z-index: 2147483647 !important; pointer-events: none; box-shadow: 0 4px 16px var(--pk-tip-sd); backdrop-filter: blur(4px); display: flex; flex-direction: column; gap: 4px; white-space: normal; word-break: break-all; text-align: justify; opacity: 0; transform: translateY(5px) scale(0.95); transition: opacity 0.15s ease, transform 0.15s ease; } .pk-tooltip.pk-dark { --pk-tip-bg: rgba(20, 20, 20, 0.95); --pk-tip-fg: #ffffff; --pk-tip-bd: rgba(255, 255, 255, 0.1); --pk-tip-sd: rgba(0, 0, 0, 0.4); } .pk-tooltip.show { opacity: 1; transform: translateY(0) scale(1); } body.pk-dragging .pk-tooltip { display: none !important; opacity: 0 !important; } .pk-tooltip img { display: none; width: 100%; max-width: 100%; max-height: 320px; height: auto; object-fit: cover; border-radius: 4px; margin-bottom: 0 !important; display: block; } `; document.head.appendChild(style); document.body.appendChild(tipEl); let activeTarget = null; let isMenuOpen = false; let lastMouseX = 0, lastMouseY = 0; let lastTipSig = ''; const hideTip = () => { tipEl.style.display = 'none'; tipEl.classList.remove('show'); tipEl.style.left = '-9999px'; activeTarget = null; lastTipSig = ''; }; const renderTip = (target) => { const isDark = document.querySelector('.pk-ov')?.classList.contains('pk-dark'); tipEl.classList.toggle('pk-dark', !!isDark); const text = target.getAttribute('data-pk-tip'); const thumb = target.getAttribute('data-pk-thumb'); if (!text && !thumb) { hideTip(); return; } const isGridTooltip = !!target.closest('.pk-grid-view, .pk-grid-card, .pk-grid-card-row, .pk-gv-cover'); const tooltipView = isGridTooltip ? 'grid' : 'list'; const isBlur = (typeof isBlurEnabledForView === 'function') ? isBlurEnabledForView(tooltipView) : gmGet('pk_blur_thumb', false); const rowId = target.closest('[data-id]')?.dataset.id || ''; const tipSig = [rowId, text || '', thumb || '', tooltipView, isBlur ? 'blur' : 'clear', isDark ? 'dark' : 'light'].join('|'); activeTarget = target; if (tipEl.parentNode && tipEl.nextSibling) { document.body.appendChild(tipEl); } if (lastTipSig === tipSig && tipEl.style.display === 'block') { tipEl.classList.add('show'); return; } let html = ''; const usableThumb = thumb && thumb !== 'undefined' && thumb !== 'null' && (thumb.startsWith('http') || thumb.startsWith('blob:')) && !isPreviewIconFailed(thumb); if (usableThumb) { const safeThumb = String(thumb).replace(/"/g, '"'); html += ``; } if (text) { html += `
${text}
`; } lastTipSig = tipSig; tipEl.innerHTML = html; const tipImg = tipEl.querySelector('img[data-pk-tooltip-thumb]'); if (tipImg) { tipImg.addEventListener('error', () => { markPreviewIconFailed(tipImg.dataset.pkTooltipThumb || tipImg.getAttribute('src') || ''); tipImg.remove(); lastTipSig = ''; }, { once: true }); } tipEl.style.display = 'block'; requestAnimationFrame(() => tipEl.classList.add('show')); }; document.addEventListener('mouseover', (e) => { lastMouseX = e.clientX; lastMouseY = e.clientY; if (isMenuOpen) return; if (document.body.classList.contains('pk-dragging')) { hideTip(); return; } if (document.querySelector('#pk-ctx')?.style.display === 'block') return; const target = e.target.closest('[data-pk-tip], [data-pk-thumb]'); if (!target) return; if (target === activeTarget) return; const isName = target.classList.contains('pk-name') || target.closest('.pk-name'); const isPath = target.classList.contains('pk-path') || target.closest('.pk-path'); const isThumb = target.hasAttribute('data-pk-thumb'); const isUI = target.closest('.pk-tb, .pk-sidebar, .pk-hd, .pk-ft, .pk-player-box, .pk-modal, .pk-img-box'); const isForceTip = target.classList.contains('pk-force-tip'); const isHeaderActionTip = /^(pk-btn-folder-first|pk-btn-invert|pk-grid-folder-first|pk-grid-invert)$/.test(target.id); if (isHeaderActionTip) { const ov = target.closest('.pk-ov'); if (!ov || (!ov.classList.contains('pk-hide-btn-text') && !ov.classList.contains('pk-auto-hide-btn-text'))) return; } if (!isName && !isPath && !isThumb && !isUI && !isForceTip) { if (target.scrollWidth <= target.clientWidth + 1) return; } renderTip(target); updatePos({ clientX: lastMouseX, clientY: lastMouseY }); }); const updatePos = (e) => { if (!tipEl || isMenuOpen || tipEl.style.display === 'none') return; let left = (e.clientX) + 15; let top = (e.clientY) + 15; const rect = getLogicalRect(tipEl); const w = rect.width; const h = rect.height; const winW = window.innerWidth; const winH = window.innerHeight; if (left + w > winW - 10) left = (e.clientX) - w - 15; if (top + h > winH - 10) top = (e.clientY) - h - 15; tipEl.style.left = `${left}px`; tipEl.style.top = `${top}px`; }; const onGlobalMouseMove = (e) => { lastMouseX = e.clientX; lastMouseY = e.clientY; if (document.body.classList.contains('pk-dragging')) { hideTip(); return; } updatePos(e); if (activeTarget && tipEl.style.display !== 'none') { const actualTarget = e.target.closest('[data-pk-tip],[data-pk-thumb]'); if (actualTarget !== activeTarget) { hideTip(); } } }; document.addEventListener('mousemove', onGlobalMouseMove); const onGlobalMouseOut = (e) => { const target = e.target.closest('[data-pk-tip],[data-pk-thumb]'); if (target && target === activeTarget) { if (target.contains(e.relatedTarget)) return; hideTip(); } }; document.addEventListener('mouseout', onGlobalMouseOut); const onGlobalCtxMenu = () => { isMenuOpen = true; hideTip(); }; const onGlobalClick = () => { isMenuOpen = false; if(activeTarget) hideTip(); }; const onGlobalWheel = () => { isMenuOpen = false; hideTip(); }; window.addEventListener('contextmenu', onGlobalCtxMenu, true); window.addEventListener('click', onGlobalClick, true); window.addEventListener('wheel', onGlobalWheel, { passive: true, capture: true }); window.pkRefreshTooltip = () => { if (isMenuOpen || document.body.classList.contains('pk-dragging')) { hideTip(); return; } const elUnder = document.elementFromPoint(lastMouseX, lastMouseY); if (!elUnder) { hideTip(); return; } const target = elUnder.closest('[data-pk-tip], [data-pk-thumb]'); if (!target) { hideTip(); return; } if (target.closest('.pk-grid-card-row')) { hideTip(); return; } const isName = target.classList.contains('pk-name') || target.closest('.pk-name'); const isPath = target.classList.contains('pk-path') || target.closest('.pk-path'); const isThumb = target.hasAttribute('data-pk-thumb'); const isUI = target.closest('.pk-tb, .pk-sidebar, .pk-hd, .pk-ft, .pk-player-box, .pk-modal, .pk-img-box'); const isForceTip = target.classList.contains('pk-force-tip'); const isHeaderActionTip = /^(pk-btn-folder-first|pk-btn-invert|pk-grid-folder-first|pk-grid-invert)$/.test(target.id); if (isHeaderActionTip) { const ov = target.closest('.pk-ov'); if (!ov || (!ov.classList.contains('pk-hide-btn-text') && !ov.classList.contains('pk-auto-hide-btn-text'))) { hideTip(); return; } } if (!isName && !isPath && !isThumb && !isUI && !isForceTip) { if (target.scrollWidth <= target.clientWidth + 1) { hideTip(); return; } } renderTip(target); updatePos({ clientX: lastMouseX, clientY: lastMouseY }); }; return () => { document.removeEventListener('mousemove', onGlobalMouseMove); document.removeEventListener('mouseout', onGlobalMouseOut); window.removeEventListener('contextmenu', onGlobalCtxMenu, true); window.removeEventListener('click', onGlobalClick, true); window.removeEventListener('wheel', onGlobalWheel, { passive: true, capture: true }); delete window.pkRefreshTooltip; if (tipEl && tipEl.parentNode) tipEl.remove(); if (style && style.parentNode) style.remove(); }; })(); const UI = { win: el.querySelector('.pk-win'), vp: el.querySelector('#pk-vp'), in: el.querySelector('#pk-in'), loader: el.querySelector('#pk-loader'), loadTxt: el.querySelector('#pk-load-txt'), stopBtn: el.querySelector('#pk-stop-load'), crumb: el.querySelector('#pk-crumb'), stat: el.querySelector('#pk-stat'), chkAll: el.querySelector('#pk-all'), scan: el.querySelector('#pk-scan-dup'), dupTools: el.querySelector('#pk-dup-tools'), dupFilters: el.querySelector('#pk-dup-filters'), chkName: el.querySelector('#pk-chk-name'), chkSim: el.querySelector('#pk-chk-sim'), chkHash: el.querySelector('#pk-chk-hash'), selDupFolder: el.querySelector('#pk-dup-folder-sel'), offTools: el.querySelector('#pk-offline-tools'), chkOffRun: el.querySelector('#pk-off-run'), chkOffFail: el.querySelector('#pk-off-fail'), chkOffOk: el.querySelector('#pk-off-ok'), upTools: el.querySelector('#pk-upload-tools'), upTip: el.querySelector('#pk-upload-tip'), chkUpRun: el.querySelector('#pk-chk-up-run'), chkUpPause: el.querySelector('#pk-chk-up-pause'), chkUpDone: el.querySelector('#pk-chk-up-done'), btnAnalyze: el.querySelector('#pk-analyze'), btnExport: el.querySelector('#pk-export'), btnFolderFirst: el.querySelector('#pk-btn-folder-first'), btnNavHome: el.querySelector('#pk-nav-home'), btnNavOffline: el.querySelector('#pk-nav-offline'), btnNavUpload: el.querySelector('#pk-nav-upload'), btnNavRecent: el.querySelector('#pk-nav-recent'), btnNavHistory: el.querySelector('#pk-nav-history'), btnNavShare: el.querySelector('#pk-nav-share'), btnNavStarred: el.querySelector('#pk-nav-starred'), btnNavTrash: el.querySelector('#pk-nav-trash'), btnNavShareParse: el.querySelector('#pk-nav-share-parse'), trashBar: el.querySelector('#pk-trash-bar'), btnTrashRefresh: el.querySelector('#pk-trash-refresh'), bottomGrp: el.querySelector('.pk-ft .pk-grp'), actionBar: el.querySelector('#pk-actionbar'), btnRestore: el.querySelector('#pk-restore'), btnDelForever: el.querySelector('#pk-del-forever'), btnEmptyTrash: el.querySelector('#pk-empty-trash'), btnTrashBlacklistManager: el.querySelector('#pk-trash-blacklist-manager'), btnDupSmart: el.querySelector('#pk-dup-smart-btn'), btnDupSort: el.querySelector('#pk-dup-sort-btn'), btnExit: el.querySelector('#pk-btn-exit'), btnCopy: el.querySelector('#pk-copy'), btnCut: el.querySelector('#pk-cut'), btnDel: el.querySelector('#pk-del'), btnClearHistoryAll: el.querySelector('#pk-clear-history-all'), btnDeselect: el.querySelector('#pk-deselect'), btnRename: el.querySelector('#pk-rename'), btnBulkRename: el.querySelector('#pk-bulkrename'), btnPrune: el.querySelector('#pk-prune'), btnUnzip: el.querySelector('#pk-unzip'), btnMigrate: el.querySelector('#pk-migrate'), btnExportM3U: el.querySelector('#pk-export-m3u'), btnBlacklistManager: el.querySelector('#pk-blacklist-manager'), uploadWrap: el.querySelector('#pk-upload-wrap'), btnUpload: el.querySelector('#pk-btn-upload'), actUpFile: el.querySelector('#pk-act-upload-file'), actUpFolder: el.querySelector('#pk-act-upload-folder'), inpFile: el.querySelector('#pk-file-selector'), inpFolder: el.querySelector('#pk-folder-selector'), btnCancelShare: el.querySelector('#pk-cancel-share'), btnRetryTask: el.querySelector('#pk-retry-task'), btnCopyLinkOffline: el.querySelector('#pk-off-copy-link'), btnShareParseSave: el.querySelector('#pk-share-parse-save'), btnShareParseInsight: el.querySelector('#pk-share-parse-insight'), btnShareParseStopScan: el.querySelector('#pk-share-parse-stop-scan'), btnShareParseBackList: el.querySelector('#pk-share-parse-back-list'), btnUpPause: el.querySelector('#pk-up-pause'), btnUpStart: el.querySelector('#pk-up-start'), btnUpDel: el.querySelector('#pk-up-del'), btnUpClearAll: el.querySelector('#pk-up-clear-all'), btnPaste: el.querySelector('#pk-paste'), btnRefresh: el.querySelector('#pk-refresh'), btnNewFolder: el.querySelector('#pk-newfolder'), btnSettings: el.querySelector('#pk-settings'), btnClose: el.querySelector('#pk-close'), btnTransferQuotaDetail: el.querySelector('#pk-transfer-detail-btn'), btnTheme: el.querySelector('#pk-theme'), btnExt: el.querySelector('#pk-ext'), btnImgSearch: el.querySelector('#pk-img-search'), btnAria2: el.querySelector('#pk-aria2'), btnDown: el.querySelector('#pk-down'), pop: el.querySelector('#pk-pop'), ctx: el.querySelector('#pk-ctx'), cols: el.querySelectorAll('.pk-col'), searchInput: el.querySelector('#pk-search-input'), chkGlobal: el.querySelector('#pk-chk-global'), lblGlobal: el.querySelector('#pk-lbl-global'), chkSearchPath: el.querySelector('#pk-chk-search-path'), lblSearchPath: el.querySelector('#pk-search-path-con'), btnAnaSelect: (function() { const parent = el.querySelector('#pk-search-path-con'); const b = document.createElement('button'); b.className = 'pk-ana-select-btn'; b.id = 'pk-ana-select-btn'; b.style.marginRight = '4px'; b.innerHTML = ` ${L.btn_ana_select}`; if (parent) parent.parentNode.insertBefore(b, parent); return b; })(), btnAnaSort: (function() { const parent = el.querySelector('#pk-search-path-con'); const b = document.createElement('button'); b.className = 'pk-ana-select-btn'; b.id = 'pk-ana-sort-btn'; b.innerHTML = ` ${L.btn_in_group_sort}`; if (parent) parent.parentNode.insertBefore(b, parent); return b; })(), topBar: el.querySelector('#pk-top-bar'), searchClear: el.querySelector('#pk-search-clear'), searchBtn: el.querySelector('#pk-search-btn'), searchHist: el.querySelector('#pk-search-hist'), filterBar: el.querySelector('#pk-filter-bar'), filterBtn: el.querySelector('#pk-filter-btn'), filterActiveUI: el.querySelector('#pk-filter-active-ui'), filterCatLabel: el.querySelector('#pk-filter-cat-label'), filterExtsWrap: el.querySelector('#pk-filter-exts-wrap'), filterExtsMain: el.querySelector('#pk-filter-exts-main'), filterExtsMoreBtn: el.querySelector('#pk-filter-exts-more-btn'), filterExitBtn: el.querySelector('#pk-filter-exit-btn') }; (() => { const searchWrap = el.querySelector('.pk-search'); const afterSearch = searchWrap ? searchWrap.nextSibling : null; const topBar = searchWrap && searchWrap.parentNode; if (topBar && UI.btnShareParseInsight) topBar.insertBefore(UI.btnShareParseInsight, afterSearch); if (topBar && UI.btnShareParseBackList) topBar.insertBefore(UI.btnShareParseBackList, afterSearch); })(); const isRealHomeUploadView = () => { const path = Array.isArray(S.path) ? S.path : []; const cur = path[path.length - 1] || { id: '' }; const pathStartsAtHome = path.length > 0 && path[0] && path[0].id === ''; const hasVirtualNode = path.some(p => { const id = String((p && p.id) || ''); return id === 'virtual_search_root' || id === 'analyze_root' || id.startsWith('virtual_'); }); return pathStartsAtHome && !S.trashMode && !S.shareMode && !S.shareParseMode && !S.offlineMode && !S.uploadMode && !S.historyMode && !S.recentMode && !S.starredMode && !S.isFlattened && !S.dupMode && !S.analyzeMode && !S.scanning && !S.search && !hasVirtualNode && cur.id !== 'virtual_search_root' && cur.id !== 'analyze_root'; }; const closeLocalUploadMenu = () => { if (typeof window.__pkCloseUploadMenu === 'function') { try { window.__pkCloseUploadMenu(); } catch(e) {} } if (UI.uploadWrap) UI.uploadWrap.classList.remove('active'); const menus = []; if (UI.uploadWrap) { const localMenu = UI.uploadWrap.querySelector('.pk-dropdown-menu'); if (localMenu) menus.push(localMenu); } document.querySelectorAll('.pk-dropdown-menu[data-pk-portal="1"]').forEach(m => menus.push(m)); menus.forEach(menu => { if (!menu) return; if (menu._pkPlaceRaf1) cancelAnimationFrame(menu._pkPlaceRaf1); if (menu._pkPlaceRaf2) cancelAnimationFrame(menu._pkPlaceRaf2); menu._pkPlaceRaf1 = 0; menu._pkPlaceRaf2 = 0; menu.style.display = 'none'; if (menu._pkOriginParent && menu.parentNode !== menu._pkOriginParent) { menu._pkOriginParent.appendChild(menu); } menu.style.position = ''; menu.style.top = ''; menu.style.left = ''; menu.style.right = ''; menu.style.bottom = ''; menu.style.marginTop = ''; menu.style.zIndex = ''; menu.style.minWidth = ''; menu.style.visibility = ''; menu.style.pointerEvents = ''; menu.style.zoom = ''; menu.style.transformOrigin = ''; delete menu.dataset.pkPortal; }); }; const syncLocalUploadVisibility = () => { if (!UI.uploadWrap) return; const shouldShow = isRealHomeUploadView(); UI.uploadWrap.style.display = shouldShow ? 'inline-flex' : 'none'; if (!shouldShow) closeLocalUploadMenu(); }; const getBlacklistCleanKey = (str) => String(str || '').replace(/[\r\n\v\f\u2028\u2029]+/g, ' ').trim().toLowerCase(); const getBlacklistItemName = (item) => { if (!item) return ''; return String(item.name || item.title || '').replace(/[\r\n\v\f\u2028\u2029]+/g, ' ').trim(); }; const getSelectedBlacklistItems = () => { const selectedIds = S.getSelectedIds(); const items = []; for (const id of selectedIds) { const item = S.itemMap.get(id) || (Array.isArray(S.items) ? S.items.find(x => x && x.id === id) : null) || (Array.isArray(S.display) ? S.display.find(x => x && x.id === id) : null); if (item && !item.isHeader && getBlacklistItemName(item)) items.push(item); } return items; }; const isBlacklistFolderItem = (item) => item && item.kind === 'drive#folder'; const isItemInBlacklist = (item) => { const key = getBlacklistCleanKey(getBlacklistItemName(item)); if (!key) return false; return isBlacklistFolderItem(item) ? S.blFolderSet.has(key) : S.blSet.has(key); }; const getBlacklistContextAction = () => { if (S.updateBlCache) S.updateBlCache(); const items = getSelectedBlacklistItems(); if (items.length === 0) return 'add'; return items.every(isItemInBlacklist) ? 'remove' : 'add'; }; const setBlacklistContextItemState = (el) => { if (!el) return; const action = getBlacklistContextAction(); el.innerHTML = action === 'remove' ? `${ctxIcons.blRem} ${L.ctx_remove_bl}` : `${ctxIcons.blAdd} ${L.ctx_add_bl}`; el.setAttribute('data-action', action); }; const isAnalyzeGroupedRoot = () => { const cur = S.path && S.path.length ? S.path[S.path.length - 1] : null; return !!(S.analyzeMode && cur && cur.id === 'analyze_root' && S.analyzeSimGroups); }; const isGroupedResultMode = () => !!(S.dupMode || isAnalyzeGroupedRoot()); const canUseGridView = () => !S.shareMode && (!S.shareParseMode || S.shareParseListActive) && !S.offlineMode && !S.uploadMode; const isGridView = () => S.viewMode === 'grid' && canUseGridView(); const getListRowHeight = () => UI.win && UI.win.classList.contains('pk-maximized') ? 60 : 40; const getGridCardHeight = () => UI.win && UI.win.classList.contains('pk-maximized') ? 324 : 286; const getGridCardRadius = () => UI.win && UI.win.classList.contains('pk-maximized') ? 22 : 18; const getGridLayout = () => { const vpRect = getLogicalRect(UI.vp || UI.in); const vpWidth = Math.max(0, Math.floor((vpRect && vpRect.width) || UI.vp?.clientWidth || 0)); const isMax = UI.win && UI.win.classList.contains('pk-maximized'); const padX = isMax ? 20 : 16; const gapX = isMax ? 18 : 16; const gapY = isMax ? 20 : 16; const minCardWidth = isMax ? 236 : 208; const innerWidth = Math.max(minCardWidth, vpWidth - padX * 2); const cols = Math.max(1, Math.floor((innerWidth + gapX) / (minCardWidth + gapX))); const cardWidth = Math.max(188, Math.floor((innerWidth - gapX * (cols - 1)) / cols)); const cardHeight = Math.max(220, CONF.rowHeight - gapY); return { padX, gapX, gapY, cols, cardWidth, cardHeight }; }; const isGroupedGridView = () => !!(isGroupedResultMode() && isGridView()); const getGroupedGridSectionMetrics = () => { const isMax = !!(UI.win && UI.win.classList.contains('pk-maximized')); return { headerHeight: isMax ? CONF.dupGridHeaderHeight.max : CONF.dupGridHeaderHeight.normal, sectionGap: isMax ? CONF.dupGridSectionGap.max : CONF.dupGridSectionGap.normal, bodyGapY: isMax ? CONF.dupGridBodyGapY.max : CONF.dupGridBodyGapY.normal }; }; const getGroupedGridRowStride = (gridLayout) => { const cardHeight = Number(gridLayout && gridLayout.cardHeight) || 0; const gapY = Math.max(0, Number(gridLayout && gridLayout.gapY) || 0); return Math.max(1, cardHeight + gapY); }; const getSectionRowStride = (dupGridMeta, section) => Math.max(1, Number(section && (section.rowStride || section.rowHeight)) || Number(dupGridMeta && dupGridMeta.rowStride) || getGroupedGridRowStride((dupGridMeta && dupGridMeta.gridLayout) || getGridLayout()) ); const getDupGridSectionWindow = (sections, rangeTop, rangeBottom) => { const len = Array.isArray(sections) ? sections.length : 0; if (!len) return { start: 0, end: 0 }; const num = (section, key, fallback) => { const value = Number(section && section[key]); return Number.isFinite(value) ? value : fallback; }; let lo = 0; let hi = len; while (lo < hi) { const mid = (lo + hi) >> 1; if (num(sections[mid], 'bottom', Infinity) < rangeTop) lo = mid + 1; else hi = mid; } const start = lo; lo = 0; hi = len; while (lo < hi) { const mid = (lo + hi) >> 1; if (num(sections[mid], 'top', Infinity) > rangeBottom) hi = mid; else lo = mid + 1; } const end = lo; if (start < end) return { start: Math.max(0, start - 1), end: Math.min(len, end + 1) }; lo = 0; hi = len; while (lo < hi) { const mid = (lo + hi) >> 1; if (num(sections[mid], 'top', Infinity) < rangeTop) lo = mid + 1; else hi = mid; } const center = Math.max(0, Math.min(len - 1, lo)); return { start: Math.max(0, center - 2), end: Math.min(len, center + 3) }; }; const releaseGroupedGridMeta = (meta) => { if (!meta) return; if (meta.indexLayout && typeof meta.indexLayout.clear === 'function') meta.indexLayout.clear(); if (meta.indexToSection && typeof meta.indexToSection.clear === 'function') meta.indexToSection.clear(); if (meta.repeatedIds && typeof meta.repeatedIds.clear === 'function') meta.repeatedIds.clear(); if (Array.isArray(meta.sections)) { for (let i = 0; i < meta.sections.length; i++) { const section = meta.sections[i]; if (!section) continue; if (Array.isArray(section.itemIndices)) section.itemIndices.length = 0; if (Array.isArray(section.itemIds)) section.itemIds.length = 0; } meta.sections.length = 0; } }; const clearGroupedGridMetaCache = () => { releaseGroupedGridMeta(S.dupGridMeta); S.dupGridMeta = null; S.dupGridMetaKey = ''; S.dupGridMetaLayoutKey = ''; S.dupGridMetaDisplayRef = null; S.dupGridMetaDirty = true; }; const buildGroupedGridMetaKey = (baseKey = getGridLayoutKey()) => { const display = Array.isArray(S.display) ? S.display : []; const displayLen = display.length; if (!displayLen) return `${baseKey}::grouped-grid::empty`; let firstId = ''; let lastId = ''; let runSig = ''; let prevRunKey = null; let runCount = 0; for (let i = 0; i < displayLen; i++) { const item = display[i]; if (!item) continue; const itemId = item.id || ''; if (!firstId) firstId = itemId; lastId = itemId || lastId; const runKey = item.isHeader ? `h:${itemId || i}` : `i:${S.dupGroups.has(itemId) ? S.dupGroups.get(itemId) : -1}`; if (runKey !== prevRunKey) { runSig += `${prevRunKey === null ? '' : '|'}${runKey}`; prevRunKey = runKey; runCount++; } } return `${baseKey}::grouped-grid::${displayLen}::${runCount}::${firstId}::${lastId}::${runSig}`; }; const getDupGridShortStableIdPart = (id) => { const raw = String(id || ''); if (raw.length <= 14) return raw; return `${raw.slice(0, 6)}~${raw.slice(-4)}`; }; const getDupGridShortMemberDigest = (ids) => { const arr = Array.isArray(ids) ? ids.filter(Boolean).map(String) : []; if (!arr.length) return ''; let hash = 2166136261; for (let i = 0; i < arr.length; i++) { const s = arr[i]; for (let j = 0; j < s.length; j++) { hash ^= s.charCodeAt(j); hash = Math.imul(hash, 16777619) >>> 0; } hash ^= 124; hash = Math.imul(hash, 16777619) >>> 0; } const first = getDupGridShortStableIdPart(arr[0]); const second = arr.length > 2 ? getDupGridShortStableIdPart(arr[1]) : ''; const last = arr.length > 1 ? getDupGridShortStableIdPart(arr[arr.length - 1]) : ''; return [arr.length, hash.toString(36), first, second, last].join(':'); }; const getDupGridShortTextDigest = (text) => { const raw = String(text || ''); if (!raw) return ''; let hash = 2166136261; for (let i = 0; i < raw.length; i++) { hash ^= raw.charCodeAt(i); hash = Math.imul(hash, 16777619) >>> 0; } return [raw.length, hash.toString(36), raw.slice(0, 24)].join(':'); }; const getGroupedGridMeta = (force = false) => { if (!isGroupedGridView()) { clearGroupedGridMetaCache(); return null; } const display = Array.isArray(S.display) ? S.display : []; const rawLayoutKey = getGridLayoutKey(); const dupCfg = S.dupConfig || {}; const invertChk = typeof document !== 'undefined' ? document.getElementById('pk-dup-invert') : null; const blurScope = typeof getBlurScope === 'function' ? getBlurScope() : ''; const layoutKey = [ rawLayoutKey, getLang(), S.viewMode || '', S.dupMode ? 'dup' : '', isAnalyzeGroupedRoot() ? 'analyzeGrouped' : '', S.sortId || 0, S.scanId || 0, S.dupMode && Array.isArray(S.dupRawGroups) ? S.dupRawGroups.length : 0, S.analyzeMode && Array.isArray(S.analyzeSimGroups) ? S.analyzeSimGroups.length : 0, S.groupSortOp || '', S.groupSortDir || '', S.search || '', UI.chkSearchPath && UI.chkSearchPath.checked ? 'path1' : 'path0', S.pinnedDupPath || '', UI.selDupFolder ? UI.selDupFolder.value || '' : '', UI.chkHash && UI.chkHash.checked ? 'hash1' : 'hash0', UI.chkSim && UI.chkSim.checked ? 'sim1' : 'sim0', UI.chkName && UI.chkName.checked ? 'name1' : 'name0', dupCfg.video ? 'video1' : 'video0', dupCfg.image ? 'image1' : 'image0', dupCfg.other ? 'other1' : 'other0', gmGet('pk_dup_strictness', 'strict'), gmGet('pk_analyze_last_algo', ''), gmGet('pk_analyze_last_sim', ''), invertChk && invertChk.checked ? 'inv1' : 'inv0', blurScope === 'both' || blurScope === 'grid' ? 'blur' : 'clear', blurScope ].join('::'); if (!force && !S.dupGridMetaDirty && S.dupGridMeta && S.dupGridMetaLayoutKey === layoutKey && S.dupGridMetaDisplayRef === display) return S.dupGridMeta; const metaKey = buildGroupedGridMetaKey(layoutKey); const gridLayout = getGridLayout(); const sectionMetrics = getGroupedGridSectionMetrics(); const cols = Math.max(1, gridLayout.cols); const sectionGapY = Math.max(0, gridLayout.gapY || 0); const rowStride = getGroupedGridRowStride(gridLayout); const vpRect = getLogicalRect(UI.vp || UI.in); const fullWidth = Math.max(0, Math.floor((vpRect && vpRect.width) || UI.vp?.clientWidth || 0)); const sections = []; const indexLayout = new Map(); const indexToSection = new Map(); let current = null; let totalHeight = 0; const pushSection = () => { if (!current) return; const itemCount = current.itemIndices.length; const rows = itemCount > 0 ? Math.ceil(itemCount / cols) : 0; const headerTop = totalHeight; const bodyTop = headerTop + sectionMetrics.headerHeight + (itemCount > 0 ? sectionGapY : 0); const bodyHeight = rows * rowStride; const sectionHeight = sectionMetrics.headerHeight + (itemCount > 0 ? (sectionGapY + bodyHeight) : 0); const section = { key: current.key, groupIndex: current.groupIndex, reason: current.reason, title: current.title, headerIndex: current.headerIndex, itemIndices: [...current.itemIndices], itemIds: [...current.itemIds], itemCount, rows, cols, top: headerTop, headerHeight: sectionMetrics.headerHeight, bodyTop, bodyHeight, rowHeight: rowStride, rowStride, totalHeight: sectionHeight, bottom: headerTop + sectionHeight }; if (current.headerIndex !== null) { indexLayout.set(current.headerIndex, { kind: 'header', sectionKey: current.key, top: headerTop, left: 0, width: fullWidth, height: sectionMetrics.headerHeight }); indexToSection.set(current.headerIndex, section); } for (let localIdx = 0; localIdx < current.itemIndices.length; localIdx++) { const displayIndex = current.itemIndices[localIdx]; const rowIdx = Math.floor(localIdx / cols); const colIdx = localIdx % cols; indexLayout.set(displayIndex, { kind: 'item', sectionKey: current.key, localIndex: localIdx, rowIdx, colIdx, top: bodyTop + rowIdx * rowStride, left: gridLayout.padX + colIdx * (gridLayout.cardWidth + gridLayout.gapX), width: gridLayout.cardWidth, height: gridLayout.cardHeight }); indexToSection.set(displayIndex, section); } sections.push(section); totalHeight += sectionHeight; current = null; }; for (let i = 0; i < display.length; i++) { const d = display[i]; if (!d) continue; if (d.isHeader) { pushSection(); const parsedGroupIndex = /^grp_\d+$/.test(d.id || '') ? parseInt(String(d.id).replace('grp_', ''), 10) : -1; current = { key: d.id || `grp_auto_${i}`, headerIndex: i, groupIndex: parsedGroupIndex, reason: d.type || '', title: d.name || '', itemIndices: [], itemIds: [] }; continue; } if (!current) { current = { key: `loose_${i}`, headerIndex: null, groupIndex: S.dupGroups.has(d.id) ? S.dupGroups.get(d.id) : -1, reason: S.dupReasons.get(d.id) || '', title: '', itemIndices: [], itemIds: [] }; } current.itemIndices.push(i); if (d.id) current.itemIds.push(d.id); } pushSection(); if (sections.length > 0) totalHeight = Math.max(0, totalHeight); const repeatedIds = new Set(); const seenIds = new Set(); const firstSectionById = new Map(); for (const section of sections) { section.memberDigest = getDupGridShortMemberDigest(section.itemIds); section.groupReasonDigest = getDupGridShortTextDigest(section.reason || ''); section.groupTitleDigest = getDupGridShortTextDigest(section.title || ''); for (let localIdx = 0; localIdx < section.itemIds.length; localIdx++) { const itemId = String(section.itemIds[localIdx] || ''); if (!itemId) continue; if (seenIds.has(itemId)) repeatedIds.add(itemId); seenIds.add(itemId); if (firstSectionById.has(itemId) && firstSectionById.get(itemId) !== section.key) repeatedIds.add(itemId); else firstSectionById.set(itemId, section.key); } } const stableScope = S.dupMode ? 'dup-file' : (isAnalyzeGroupedRoot() ? 'dup-folder' : ''); if (stableScope) { for (const section of sections) { if (!section || section.headerIndex === null || !Array.isArray(section.itemIds) || !section.itemIds.length || !section.memberDigest) continue; const header = display[section.headerIndex] || null; const groupSource = stableScope === 'dup-folder' && Array.isArray(S.analyzeSimGroups) ? S.analyzeSimGroups[section.groupIndex] : null; const rawGroupReason = String(section.reason || (header && header.type) || ''); const rawGroupTitle = String(section.title || (header && header.name) || ''); const groupReasonDigest = section.groupReasonDigest || getDupGridShortTextDigest(rawGroupReason); const groupTitleDigest = section.groupTitleDigest || getDupGridShortTextDigest(rawGroupTitle); const groupSig = [ `dupSection:${section.key || ''}`, `dupGroup:${section.groupIndex}`, `dupType:${groupReasonDigest}`, `dupTitle:${groupTitleDigest}`, `dupGroupSim:${groupSource && groupSource._sim !== undefined ? String(groupSource._sim) : ''}`, `dupMembers:${section.memberDigest}` ].join('|'); section.dupGroupSig = groupSig; } } const nextMeta = { key: metaKey, cols, gridLayout, sectionMetrics, fullWidth, rowHeight: rowStride, rowStride, totalHeight: Math.max(0, totalHeight), sections, indexLayout, indexToSection, repeatedIds }; releaseGroupedGridMeta(S.dupGridMeta); S.dupGridMetaKey = metaKey; S.dupGridMetaLayoutKey = layoutKey; S.dupGridMetaDisplayRef = display; S.dupGridMetaDirty = false; S.dupGridMeta = nextMeta; return S.dupGridMeta; }; const findDupGridHitIndex = (logicalOffsetX, logicalOffsetY) => { if (!isGroupedGridView()) return -1; const dupGridMeta = getGroupedGridMeta(); if (!dupGridMeta || !dupGridMeta.sections || !dupGridMeta.sections.length) return -1; const gridLayout = dupGridMeta.gridLayout || getGridLayout(); const cols = Math.max(1, dupGridMeta.cols || gridLayout.cols || 1); if (logicalOffsetX < gridLayout.padX || logicalOffsetY < 0) return -1; const colStride = gridLayout.cardWidth + gridLayout.gapX; const relX = logicalOffsetX - gridLayout.padX; const colIdx = Math.floor(relX / Math.max(1, colStride)); const withinColX = relX - colIdx * colStride; if (colIdx < 0 || colIdx >= cols || withinColX < 0 || withinColX > gridLayout.cardWidth) return -1; for (const section of dupGridMeta.sections) { if (logicalOffsetY < section.top) break; if (logicalOffsetY >= section.bottom) continue; if (logicalOffsetY < section.bodyTop || logicalOffsetY >= section.bodyTop + section.bodyHeight) return -1; const rowStride = getSectionRowStride(dupGridMeta, section); const relY = logicalOffsetY - section.bodyTop; const rowIdx = Math.floor(relY / rowStride); const withinRowY = relY - rowIdx * rowStride; if (rowIdx < 0 || rowIdx >= section.rows || withinRowY < 0 || withinRowY > gridLayout.cardHeight) return -1; const localIdx = rowIdx * Math.max(1, section.cols || cols) + colIdx; if (localIdx < 0 || localIdx >= section.itemIndices.length) return -1; return section.itemIndices[localIdx]; } return -1; }; const getDupGridAnchorIndex = (logicalCenterX, logicalCenterY) => { if (!isGroupedGridView()) return -1; const dupGridMeta = getGroupedGridMeta(); if (!dupGridMeta) return -1; const hitIdx = findDupGridHitIndex(logicalCenterX, logicalCenterY); if (hitIdx >= 0) return hitIdx; const sections = dupGridMeta.sections || []; if (!sections.length) return -1; const findSectionIndex = () => { let lo = 0; let hi = sections.length - 1; while (lo <= hi) { const mid = (lo + hi) >> 1; const section = sections[mid]; if (logicalCenterY < section.top) { hi = mid - 1; } else if (logicalCenterY >= section.bottom) { lo = mid + 1; } else { return mid; } } const rightIdx = Math.max(0, Math.min(sections.length - 1, lo)); const leftIdx = Math.max(0, rightIdx - 1); const left = sections[leftIdx]; const right = sections[rightIdx]; const leftDist = left ? Math.min(Math.abs(logicalCenterY - left.top), Math.abs(logicalCenterY - left.bottom)) : Infinity; const rightDist = right ? Math.min(Math.abs(logicalCenterY - right.top), Math.abs(logicalCenterY - right.bottom)) : Infinity; return rightDist < leftDist ? rightIdx : leftIdx; }; const section = sections[findSectionIndex()]; const itemIndices = section && Array.isArray(section.itemIndices) ? section.itemIndices : []; if (!section || !itemIndices.length) return -1; const gridLayout = dupGridMeta.gridLayout || getGridLayout(); const cols = Math.max(1, section.cols || dupGridMeta.cols || gridLayout.cols || 1); const rowStride = getSectionRowStride(dupGridMeta, section); const rows = Math.max(1, section.rows || Math.ceil(itemIndices.length / cols)); const colStride = Math.max(1, gridLayout.cardWidth + gridLayout.gapX); const approxCol = Math.max(0, Math.min(cols - 1, Math.round((logicalCenterX - gridLayout.padX - gridLayout.cardWidth / 2) / colStride))); const bodyTop = Number(section.bodyTop) || section.top || 0; const bodyBottom = bodyTop + Math.max(0, section.bodyHeight || rows * rowStride); let approxRow = 0; if (logicalCenterY >= bodyBottom) { approxRow = rows - 1; } else if (logicalCenterY > bodyTop) { approxRow = Math.floor((logicalCenterY - bodyTop) / rowStride); } approxRow = Math.max(0, Math.min(rows - 1, approxRow)); let bestIdx = -1; let bestScore = Infinity; const rowStart = Math.max(0, approxRow - 2); const rowEnd = Math.min(rows - 1, approxRow + 2); const colStart = Math.max(0, approxCol - 2); const colEnd = Math.min(cols - 1, approxCol + 2); for (let row = rowStart; row <= rowEnd; row++) { for (let col = colStart; col <= colEnd; col++) { const localIdx = row * cols + col; if (localIdx < 0 || localIdx >= itemIndices.length) continue; const idx = itemIndices[localIdx]; const layout = dupGridMeta.indexLayout ? dupGridMeta.indexLayout.get(idx) : null; const left = layout ? layout.left : gridLayout.padX + col * colStride; const top = layout ? layout.top : bodyTop + row * rowStride; const width = layout ? layout.width : gridLayout.cardWidth; const height = layout ? layout.height : gridLayout.cardHeight; const cx = left + (width / 2); const cy = top + (height / 2); const score = Math.abs(cy - logicalCenterY) * 4 + Math.abs(cx - logicalCenterX); if (score < bestScore) { bestScore = score; bestIdx = idx; } } } if (bestIdx >= 0) return bestIdx; if (logicalCenterY <= bodyTop) return itemIndices[0] || -1; if (logicalCenterY >= bodyBottom) return itemIndices[itemIndices.length - 1] || -1; const boundaryLocalIdx = Math.max(0, Math.min(itemIndices.length - 1, approxRow * cols + approxCol)); return itemIndices[boundaryLocalIdx] || -1; }; const getItemScrollTopByIndex = (targetIdx) => { if (targetIdx < 0) return 0; if (isGridView()) { if (isGroupedGridView()) { const dupGridMeta = getGroupedGridMeta(); const layout = dupGridMeta && dupGridMeta.indexLayout ? dupGridMeta.indexLayout.get(targetIdx) : null; if (layout) return Math.max(0, layout.top); const section = dupGridMeta && dupGridMeta.indexToSection ? dupGridMeta.indexToSection.get(targetIdx) : null; if (section) { if (section.headerIndex === targetIdx) return Math.max(0, section.top || 0); const localIdx = Array.isArray(section.itemIndices) ? section.itemIndices.indexOf(targetIdx) : -1; if (localIdx >= 0) { const cols = Math.max(1, section.cols || dupGridMeta.cols || 1); const rowIdx = Math.floor(localIdx / cols); return Math.max(0, (section.bodyTop || 0) + rowIdx * getSectionRowStride(dupGridMeta, section)); } } } const gridLayout = getGridLayout(); const visualRow = Math.floor(targetIdx / Math.max(1, gridLayout.cols)); return gridLayout.gapY + visualRow * getGroupedGridRowStride(gridLayout); } return targetIdx * CONF.rowHeight; }; const getViewportAnchorId = (preferSelection = false) => { const selectedIds = S.getSelectedIds(); const fallbackId = S.activeId || (selectedIds.length ? selectedIds[selectedIds.length - 1] : null); if (preferSelection && fallbackId && S.display.some(x => x.id === fallbackId)) return fallbackId; if (!UI.vp || !S.display.length) return fallbackId; const centerY = UI.vp.scrollTop + (UI.vp.clientHeight / 2); if (isGridView()) { const gridLayout = getGridLayout(); if (isGroupedGridView()) { const usableWidth = Math.max(1, UI.vp.clientWidth - gridLayout.padX * 2); const logicalCenterX = gridLayout.padX + (usableWidth / 2); const targetIdx = getDupGridAnchorIndex(logicalCenterX, centerY); if (targetIdx >= 0 && targetIdx < S.display.length) { return S.display[targetIdx]?.id || fallbackId; } return fallbackId; } const cols = Math.max(1, gridLayout.cols); const totalRows = Math.max(1, Math.ceil(S.display.length / cols)); const adjustedCenterY = Math.max(0, centerY - gridLayout.gapY); const visualRow = Math.max(0, Math.min(totalRows - 1, Math.floor(adjustedCenterY / Math.max(1, CONF.rowHeight)))); const usableWidth = Math.max(1, UI.vp.clientWidth - gridLayout.padX * 2); const centerX = usableWidth / 2; const visualCol = Math.max(0, Math.min(cols - 1, Math.floor(centerX / Math.max(1, gridLayout.cardWidth + gridLayout.gapX)))); const targetIdx = Math.min(S.display.length - 1, visualRow * cols + visualCol); return S.display[targetIdx]?.id || fallbackId; } const targetIdx = Math.max(0, Math.min(S.display.length - 1, Math.floor(centerY / Math.max(1, CONF.rowHeight)))); return S.display[targetIdx]?.id || fallbackId; }; const syncLayoutMetrics = () => { CONF.rowHeight = isGridView() ? getGridCardHeight() : getListRowHeight(); }; const getGridLayoutKey = () => { const vpRect = getLogicalRect(UI.vp || UI.in); const vpWidth = Math.max(0, Math.floor((vpRect && vpRect.width) || UI.vp?.clientWidth || 0)); const vpHeight = Math.max(0, Math.floor(UI.vp?.clientHeight || 0)); const gridLayout = getGridLayout(); const isMax = UI.win && UI.win.classList.contains('pk-maximized') ? 1 : 0; return [vpWidth, vpHeight, gridLayout.cols, gridLayout.cardWidth, gridLayout.cardHeight, isMax].join(':'); }; const clearGridStableRows = (container) => { if (!container) return; if (container._pkGridRowMap) container._pkGridRowMap.clear(); container._pkGridRowMapKey = ''; container._pkGridStableRowMode = false; S._dupGridRepeatedIdKey = ''; S._dupGridRepeatedIds = null; clearGroupedGridMetaCache(); }; const resetViewportRenderState = () => { if (S._gridRelayoutRaf1) { cancelAnimationFrame(S._gridRelayoutRaf1); S._gridRelayoutRaf1 = 0; } if (S._gridRelayoutRaf2) { cancelAnimationFrame(S._gridRelayoutRaf2); S._gridRelayoutRaf2 = 0; } if (S.dupGridScrollRaf) { cancelAnimationFrame(S.dupGridScrollRaf); S.dupGridScrollRaf = 0; } if (S.dupGridScrollEndTimer) { clearTimeout(S.dupGridScrollEndTimer); S.dupGridScrollEndTimer = 0; } S.dupGridScrollDirty = false; S.dupGridLastScrollTop = 0; S.dupGridLastRenderedTop = -1; if (!UI.in) return; clearGridStableRows(UI.in); const pool = UI.in._pkVisibleRowPool || []; for (let i = 0; i < pool.length; i++) { const row = pool[i]; if (!row) continue; if (typeof resetPooledRow === 'function') resetPooledRow(row); if (row.parentNode === UI.in) UI.in.removeChild(row); } if (typeof cleanupNonPooledChildren === 'function') { cleanupNonPooledChildren(UI.in); } }; const beginFolderViewSync = () => { if (S._folderViewSyncing) return; S._folderViewSyncing = true; resetViewportRenderState(); if (UI.vp) { UI.vp.style.visibility = 'hidden'; UI.vp.style.pointerEvents = 'none'; } const hd = UI.win && UI.win.querySelector('.pk-grid-hd'); if (hd) hd.style.visibility = 'hidden'; }; const endFolderViewSync = (force = false) => { if (!S._folderViewSyncing) return; if (S._folderViewSyncHold && !force) return; requestAnimationFrame(() => { if (S._folderViewSyncHold && !force) return; if (UI.vp) { UI.vp.style.visibility = ''; UI.vp.style.pointerEvents = ''; } const hd = UI.win && UI.win.querySelector('.pk-grid-hd'); if (hd) hd.style.visibility = ''; S._folderViewSyncing = false; }); }; const scheduleGridRelayout = (forceFull = false) => { if (!isGridView()) { endFolderViewSync(); return; } if (S._gridRelayoutRaf1) cancelAnimationFrame(S._gridRelayoutRaf1); if (S._gridRelayoutRaf2) cancelAnimationFrame(S._gridRelayoutRaf2); const prevKey = S._gridLayoutKey || ''; S._gridRelayoutRaf1 = requestAnimationFrame(() => { S._gridRelayoutRaf2 = requestAnimationFrame(() => { if (!UI.vp || !UI.in || !isGridView()) { endFolderViewSync(); return; } const nextKey = getGridLayoutKey(); if (forceFull || nextKey !== prevKey) { S._gridLayoutKey = nextKey; renderList(); } else if (typeof renderVisible === 'function') { renderVisible(); endFolderViewSync(); } else { endFolderViewSync(); } }); }); }; const ensureGridMediaStore = () => { if (!window.pkGlobalThumbCache) window.pkGlobalThumbCache = new Set(); if (!window.pkThumbTriState) window.pkThumbTriState = { folder: Object.create(null), file: Object.create(null) }; if (!window.pkThumbTriState.folder) window.pkThumbTriState.folder = Object.create(null); if (!window.pkThumbTriState.file) window.pkThumbTriState.file = Object.create(null); if (!window.pkGridMediaStore) window.pkGridMediaStore = { folder: Object.create(null), file: Object.create(null) }; if (!window.pkGridMediaStore.folder) window.pkGridMediaStore.folder = Object.create(null); if (!window.pkGridMediaStore.file) window.pkGridMediaStore.file = Object.create(null); return window.pkGridMediaStore; }; const getStableFolderMediaState = (item) => { ensureGridMediaStore(); const tri = window.pkThumbTriState.folder; const store = window.pkGridMediaStore.folder; const id = item && item.id; if (!id) return { state: 'none', src: '' }; const state = tri[id] || 'unknown'; const cached = store[id] || ''; if (state === 'ok' && cached) return { state: 'ok', src: cached }; if (state === 'ok' && item.thumbnail_link) { store[id] = item.thumbnail_link; return { state: 'ok', src: item.thumbnail_link }; } if ((state === 'unknown' || state === 'probing') && cached) return { state: 'ok', src: cached }; return { state, src: '' }; }; const markStableFolderMediaState = (id, state, src = '') => { ensureGridMediaStore(); if (!id) return; window.pkThumbTriState.folder[id] = state; if (state === 'ok' && src) { window.pkGridMediaStore.folder[id] = src; window.pkGlobalThumbCache.add(id); } else if (state === 'fail' && window.pkGridMediaStore.folder[id]) { delete window.pkGridMediaStore.folder[id]; } }; const getStableFileMediaState = (item) => { ensureGridMediaStore(); const tri = window.pkThumbTriState.file; const store = window.pkGridMediaStore.file; const id = item && item.id; if (!id) return { state: 'none', src: '' }; const state = tri[id] || 'unknown'; const cached = store[id] || ''; if (state === 'ok' && cached) return { state: 'ok', src: cached }; if (state === 'ok' && item.thumbnail_link) { store[id] = item.thumbnail_link; return { state: 'ok', src: item.thumbnail_link }; } if ((state === 'unknown' || state === 'probing') && cached) return { state: 'ok', src: cached }; return { state, src: '' }; }; const markStableFileMediaState = (id, state, src = '') => { ensureGridMediaStore(); if (!id) return; window.pkThumbTriState.file[id] = state; if (state === 'ok' && src) { window.pkGridMediaStore.file[id] = src; window.pkGlobalThumbCache.add(id); } else if (state === 'fail' && window.pkGridMediaStore.file[id]) { delete window.pkGridMediaStore.file[id]; } }; window.ensureGridMediaStore = ensureGridMediaStore; window.getStableFolderMediaState = getStableFolderMediaState; window.markStableFolderMediaState = markStableFolderMediaState; window.getStableFileMediaState = getStableFileMediaState; window.markStableFileMediaState = markStableFileMediaState; const ensureGridMediaDomCache = () => { if (!window.pkGridMediaDomCache) window.pkGridMediaDomCache = { folder: Object.create(null), file: Object.create(null) }; return window.pkGridMediaDomCache; }; const isTrustedChildRealThumb = (child) => { if (!child || !child.thumbnail_link || child.thumbnail_link === child.icon_link) return false; const mime = (child.mime_type || '').toLowerCase(); const duration = Number((child.params && child.params.duration) || (child.medias && child.medias[0] && child.medias[0].duration) || 0); const hasMediaMeta = Array.isArray(child.medias) && child.medias.length > 0; const stableState = child.id && typeof getStableFileMediaState === 'function' ? getStableFileMediaState(child).state : 'unknown'; return stableState === 'ok' || mime.startsWith('video/') || mime.startsWith('image/') || duration > 0 || hasMediaMeta; }; const getFolderEmbedCacheStamp = (item) => { if (!item || item.kind !== 'drive#folder') return ''; if (typeof globalCache === 'undefined') return 'nogc'; const normalize = (data) => (data && !Array.isArray(data) && data.items) ? data.items : data; const raw = globalCache.get(item.id); if (!raw) return 'miss'; if (!Array.isArray(raw) && raw.nextToken) { const pending = normalize(raw); const len = Array.isArray(pending) ? pending.filter(Boolean).length : 0; return `loading:${len}`; } const children = normalize(raw); if (!Array.isArray(children)) return 'invalid'; const visibleChildren = children.filter(Boolean); if (!visibleChildren.length) return 'empty'; const nonFolders = visibleChildren.filter(child => child.kind !== 'drive#folder'); if (!nonFolders.length) { return `folders:${visibleChildren.length}:${visibleChildren.slice(0, 3).map(child => child.id || '').join(',')}`; } const hasRealChildThumb = (child) => isTrustedChildRealThumb(child); const isFallbackPlaceholderChild = (child) => { const mime = (child.mime_type || '').toLowerCase(); const duration = (child.params && child.params.duration) || 0; return !hasRealChildThumb(child) && (mime.startsWith('video/') || mime.startsWith('image/') || duration > 0); }; const realThumbChild = nonFolders.find(hasRealChildThumb); if (realThumbChild) { const stableChildMedia = typeof getStableFileMediaState === 'function' ? getStableFileMediaState(realThumbChild) : { state: 'unknown', src: '' }; const realThumbSrc = stableChildMedia.src || realThumbChild.thumbnail_link || ''; return ['real', realThumbChild.id || '', realThumbSrc || '', realThumbChild.icon_link || '', realThumbChild.mime_type || '', realThumbChild.name || ''].join(':'); } if (nonFolders.every(isFallbackPlaceholderChild)) { const target = nonFolders[0] || {}; return ['fallback', target.id || '', target.icon_link || '', target.thumbnail_link || '', target.mime_type || '', target.name || ''].join(':'); } const ordinaryPlaceholderChild = nonFolders.find(child => !hasRealChildThumb(child) && !isFallbackPlaceholderChild(child)); if (ordinaryPlaceholderChild) { return ['ordinary', ordinaryPlaceholderChild.id || '', ordinaryPlaceholderChild.icon_link || '', ordinaryPlaceholderChild.thumbnail_link || '', ordinaryPlaceholderChild.mime_type || '', ordinaryPlaceholderChild.name || ''].join(':'); } return `blank:${visibleChildren.length}:${nonFolders.length}`; }; const makeGridMediaCacheKey = (item) => [item?.id || '', item?.kind || '', item?.thumbnail_link || '', item?.icon_link || '', getFolderEmbedCacheStamp(item)].join('::'); const stashFrozenGridMediaNode = (itemId, mediaKey, node, kind = '') => { if (!itemId || !mediaKey || !node) return; const cache = ensureGridMediaDomCache(); const bucket = kind === 'drive#folder' ? cache.folder : cache.file; bucket[itemId] = { key: mediaKey, node }; }; const takeFrozenGridMediaNode = (item) => { if (!item || !item.id) return null; const cache = ensureGridMediaDomCache(); const bucket = item.kind === 'drive#folder' ? cache.folder : cache.file; const hit = bucket[item.id]; const mediaKey = makeGridMediaCacheKey(item); if (!hit || hit.key !== mediaKey || !hit.node) return null; delete bucket[item.id]; return hit.node; }; window.ensureGridMediaDomCache = ensureGridMediaDomCache; window.stashFrozenGridMediaNode = stashFrozenGridMediaNode; window.takeFrozenGridMediaNode = takeFrozenGridMediaNode; const buildGridFolderPreview = (folderItem, folderIconHtml) => { const hasThumb = !!(folderItem.thumbnail_link && folderItem.thumbnail_link !== folderItem.icon_link); const stableFolderMedia = hasThumb ? getStableFolderMediaState(folderItem) : { state: 'none', src: '' }; const folderThumbState = stableFolderMedia.state; const folderResolvedSrc = stableFolderMedia.src || folderItem.thumbnail_link || ''; const folderHasCoverThumb = hasThumb && folderThumbState === 'ok' && !!folderResolvedSrc; if (hasThumb && folderThumbState === 'unknown' && !folderResolvedSrc && folderItem.id && window.pkThumbTriState && window.pkThumbTriState.folder) { window.pkThumbTriState.folder[folderItem.id] = 'probing'; } const makePreviewBaseHtml = (display = 'flex', opacity = '1', iconSrc = folderItem.icon_link, svgHtml = folderIconHtml) => iconSrc ? `` : `${svgHtml}`; const makeItemPlaceholderHtml = (child) => { const childIconHtml = getIcon(child).replace(/width="\d+"/g, 'width="108"').replace(/height="\d+"/g, 'height="108"'); return makePreviewBaseHtml('flex', '1', child.icon_link, childIconHtml); }; const resolveCachedPreview = () => { const normalize = (data) => (data && !Array.isArray(data) && data.items) ? data.items : data; if (typeof globalCache === 'undefined') return { decided: false, hasThumb: false, hidePreview: false, html: '' }; const raw = globalCache.get(folderItem.id); if (!raw) return { decided: false, hasThumb: false, hidePreview: false, html: '' }; if (!Array.isArray(raw) && raw.nextToken) { return { decided: false, hasThumb: false, hidePreview: false, html: '' }; } const children = normalize(raw); if (!Array.isArray(children)) return { decided: false, hasThumb: false, hidePreview: false, html: '' }; if (children.length === 0) return { decided: true, hasThumb: false, hidePreview: true, html: '' }; const visibleChildren = children.filter(Boolean); if (!visibleChildren.length) return { decided: true, hasThumb: false, hidePreview: true, html: '' }; const nonFolders = visibleChildren.filter(child => child.kind !== 'drive#folder'); if (!nonFolders.length) { return { decided: true, hasThumb: false, hidePreview: false, html: makePreviewBaseHtml('flex', '1') }; } const hasRealChildThumb = (child) => isTrustedChildRealThumb(child); const isFallbackPlaceholderChild = (child) => { const mime = (child.mime_type || '').toLowerCase(); const duration = (child.params && child.params.duration) || 0; return !hasRealChildThumb(child) && (mime.startsWith('video/') || mime.startsWith('image/') || duration > 0); }; const makeChildRealThumbHtml = (child) => { const stableChildMedia = typeof getStableFileMediaState === 'function' ? getStableFileMediaState(child) : { state: 'unknown', src: '' }; const childThumbState = stableChildMedia.state; const childResolvedSrc = stableChildMedia.src || child.thumbnail_link || ''; if (!childResolvedSrc || childThumbState === 'fail') return ''; if (childThumbState === 'unknown' && child.id && window.pkThumbTriState && window.pkThumbTriState.file) { window.pkThumbTriState.file[child.id] = 'probing'; } const imgOpacity = childThumbState === 'ok' ? '1' : '0'; const baseOpacity = childThumbState === 'ok' ? '0' : '1'; return `
${makePreviewBaseHtml('flex', baseOpacity, folderItem.icon_link, folderIconHtml)}
`; }; const realThumbChild = nonFolders.find(hasRealChildThumb); if (realThumbChild) { const realThumbHtml = makeChildRealThumbHtml(realThumbChild); if (realThumbHtml) { return { decided: true, hasThumb: true, hidePreview: false, html: realThumbHtml }; } } if (nonFolders.every(isFallbackPlaceholderChild)) { return { decided: true, hasThumb: false, hidePreview: false, html: makeItemPlaceholderHtml(nonFolders[0]) }; } const ordinaryPlaceholderChild = nonFolders.find(child => !hasRealChildThumb(child) && !isFallbackPlaceholderChild(child)); if (ordinaryPlaceholderChild) { return { decided: true, hasThumb: false, hidePreview: false, html: makeItemPlaceholderHtml(ordinaryPlaceholderChild) }; } return { decided: true, hasThumb: false, hidePreview: false, html: '' }; }; if (folderHasCoverThumb) { return { hasThumb: true, hidePreview: false, html: `${makePreviewBaseHtml('none', '1')}` }; } const cachedPreview = resolveCachedPreview(); const probeBasePreview = cachedPreview.decided ? cachedPreview : { hasThumb: false, hidePreview: false, html: '' }; if (hasThumb && folderThumbState !== 'fail') { return { hasThumb: false, hidePreview: false, html: `${probeBasePreview.html}` }; } if (cachedPreview.decided) { return { hasThumb: cachedPreview.hasThumb, hidePreview: cachedPreview.hidePreview, html: cachedPreview.html }; } return { hasThumb: false, hidePreview: false, html: '' }; }; const renderFrozenGridMedia = (item, iconFallback, gridFileFallbackHtml) => { const isFolder = item.kind === 'drive#folder'; const hasThumb = !!(item.thumbnail_link && item.thumbnail_link !== item.icon_link); if (isFolder) { const folderEmbed = buildGridFolderPreview(item, iconFallback); return `
`; } const makeFileFallbackHtml = (opacity = '1') => item.icon_link ? `
${iconFallback}
` : `
${iconFallback}
`; if (hasThumb) { const stableFileMedia = getStableFileMediaState(item); const fileThumbState = stableFileMedia.state; const fileResolvedSrc = stableFileMedia.src || item.thumbnail_link || ''; if (fileThumbState === 'fail' || !fileResolvedSrc) { return makeFileFallbackHtml(); } if (fileThumbState === 'unknown') { window.pkThumbTriState.file[item.id] = 'probing'; } const imgOpacity = fileThumbState === 'ok' ? '1' : '0'; const baseOpacity = fileThumbState === 'ok' ? '0' : '1'; return `
${makeFileFallbackHtml(baseOpacity)}
`; } return makeFileFallbackHtml(); }; const isGridVideoItem = (item) => { const mime = ((item && item.mime_type) || '').toLowerCase(); const duration = Number((item && item.params && item.params.duration) || (item && item.medias && item.medias[0] && item.medias[0].duration) || 0); return mime.startsWith('video/') || duration > 0; }; const syncGridVideoPlayState = (scope, item = null) => { const root = scope && scope.nodeType === 1 ? (scope.matches('.pk-grid-card-row, .pk-row') ? scope : (scope.closest('.pk-grid-card-row, .pk-row') || scope)) : null; if (!root) return; const mount = root.querySelector('.pk-gv-media-mount'); const play = root.querySelector('.pk-gv-play'); if (!play) return; const isVideo = item ? isGridVideoItem(item) : !!(mount && mount.dataset && mount.dataset.pkIsVideo === '1'); const hasThumb = item ? !!(item.thumbnail_link && item.thumbnail_link !== item.icon_link) : !!(mount && mount.dataset && mount.dataset.pkHasThumb === '1'); if (!mount || !isVideo || !hasThumb) { play.style.display = 'none'; return; } const wrap = mount.firstElementChild; const thumb = mount.querySelector('img.pk-max-thumb'); const wrapperReady = !!(wrap && wrap.dataset && wrap.dataset.pkThumbReady === '1'); const imgReady = !!(thumb && thumb.complete && (thumb.naturalWidth > 1 || thumb.naturalHeight > 1)); play.style.display = (wrapperReady || imgReady) ? 'flex' : 'none'; }; window.syncGridVideoPlayState = syncGridVideoPlayState; const patchFrozenGridMedia = (row, item, prevMediaNode = null, prevMediaKey = '') => { const mount = row.querySelector('.pk-gv-media-mount'); if (!mount || !item) return; const mediaKey = makeGridMediaCacheKey(item); const iconFallback = mount.dataset.pkIconFallback || ''; const gridFileFallbackHtml = mount.dataset.pkFileFallback || ''; mount.dataset.pkIsVideo = isGridVideoItem(item) ? '1' : '0'; mount.dataset.pkHasThumb = (item.thumbnail_link && item.thumbnail_link !== item.icon_link) ? '1' : '0'; const syncGridVideoPlay = () => { if (typeof syncGridVideoPlayState === 'function') { syncGridVideoPlayState(row, item); } }; const queueSyncGridVideoPlay = () => requestAnimationFrame(syncGridVideoPlay); const cachedMediaNode = typeof takeFrozenGridMediaNode === 'function' ? takeFrozenGridMediaNode(item) : null; if (cachedMediaNode) { mount.innerHTML = ''; mount.appendChild(cachedMediaNode); mount.dataset.pkMediaKey = mediaKey; queueSyncGridVideoPlay(); return; } if (prevMediaNode && prevMediaKey === mediaKey) { mount.innerHTML = ''; mount.appendChild(prevMediaNode); mount.dataset.pkMediaKey = mediaKey; queueSyncGridVideoPlay(); return; } if (mount.dataset.pkMediaKey === mediaKey && mount.firstElementChild) { queueSyncGridVideoPlay(); return; } mount.dataset.pkMediaKey = mediaKey; mount.innerHTML = renderFrozenGridMedia(item, iconFallback, gridFileFallbackHtml); queueSyncGridVideoPlay(); }; window.renderFrozenGridMedia = renderFrozenGridMedia; window.patchFrozenGridMedia = patchFrozenGridMedia; const getRowClassName = (isSelected, isFocused, isMoving, selectedCount = -1) => { let cls = 'pk-row'; if (isGridView()) cls += ' pk-grid-card-row'; if (isSelected) cls += ' sel'; if (isSelected && isGridView() && (selectedCount === 1 || (selectedCount < 0 && S.getSelectedCount() === 1))) cls += ' pk-sel-single'; if (isFocused) cls += ' pk-focused'; if (isMoving) cls += ' pk-moving'; return cls; }; const ensureVisibleRowPool = (container, need) => { if (!container._pkVisibleRowPool) container._pkVisibleRowPool = []; const pool = container._pkVisibleRowPool; while (pool.length < need) { const row = document.createElement('div'); row._pkPooledRow = true; row.style.position = 'absolute'; pool.push(row); } return pool; }; const resetPooledRow = (row) => { row.className = ''; row.innerHTML = ''; row.onclick = null; row.ondblclick = null; row.oncontextmenu = null; row.onmouseover = null; row.onmouseout = null; row.onmouseenter = null; row.onmouseleave = null; row.onmousedown = null; row.onmouseup = null; row.style.cssText = 'position:absolute;'; row.removeAttribute('data-id'); row.removeAttribute('data-pk-media-key'); row.removeAttribute('data-pk-thumb'); row.removeAttribute('data-pk-bound-id'); row.removeAttribute('data-pk-bound-kind'); row.removeAttribute('data-pk-history-stable'); row.removeAttribute('data-pk-history-sig'); row.removeAttribute('data-pk-grid-stable'); row.removeAttribute('data-pk-grid-sig'); delete row.dataset.pkBoundId; delete row.dataset.pkBoundKind; delete row.dataset.pkHistoryStable; delete row.dataset.pkHistorySig; delete row.dataset.pkGridStable; delete row.dataset.pkGridSig; return row; }; const softResetPooledRow = (row) => { row.onclick = null; row.ondblclick = null; row.oncontextmenu = null; row.onmouseover = null; row.onmouseout = null; row.onmouseenter = null; row.onmouseleave = null; row.onmousedown = null; row.onmouseup = null; return row; }; const cleanupNonPooledChildren = (container) => { Array.from(container.children).forEach(node => { if (!node._pkPooledRow) container.removeChild(node); }); }; let pkStatRaf = 0; const scheduleStatUpdate = () => { if (pkStatRaf) return; pkStatRaf = requestAnimationFrame(() => { pkStatRaf = 0; if (typeof updateStat === 'function') updateStat(); }); }; const flushVisibleRowPool = (container, usedCount) => { const pool = container._pkVisibleRowPool || []; if (container._pkHistoryStableRowMode) { for (const row of pool) { if (!row) continue; if (row._pkUsedThisPass) { if (row.parentNode !== container) container.appendChild(row); } else if (row.parentNode === container) { container.removeChild(row); } row._pkUsedThisPass = false; } return; } if (container._pkGridStableRowMode) { for (const row of pool) { if (!row) continue; if (row._pkGridUsedThisPass) { if (row.parentNode !== container) container.appendChild(row); } else if (row.parentNode === container) { container.removeChild(row); } row._pkGridUsedThisPass = false; } const map = container._pkGridRowMap; if (map) { for (const [id, row] of map) { if (!row || !row._pkPooledRow || !pool.includes(row) || (row.dataset.pkBoundId || '') !== id) { map.delete(id); } } const maxStableRows = Math.max(usedCount + 24, usedCount * 2); if (map.size > maxStableRows) { for (const [id, row] of map) { if (map.size <= maxStableRows) break; if (row && row.parentNode === container) continue; map.delete(id); } } } return; } for (let i = 0; i < pool.length; i++) { const row = pool[i]; if (i < usedCount) { if (row.parentNode !== container) container.appendChild(row); } else if (row.parentNode === container) { container.removeChild(row); } } }; const refreshVisibleSelectionState = () => { const pool = (UI.in && UI.in._pkVisibleRowPool) || []; const selectedCountForSelectionState = isGridView() ? S.getSelectedCount() : -1; const singleGridSel = selectedCountForSelectionState === 1; for (const row of pool) { if (!row || row.parentNode !== UI.in) continue; if (row.classList.contains('pk-group-hd')) { const grpChk = row.querySelector('.pk-grp-chk'); if (!grpChk) continue; const gIds = Array.isArray(row._pkGroupIds) ? row._pkGroupIds : []; let selCount = 0; gIds.forEach(id => { if (S.isSelected(id)) selCount++; }); const isAll = gIds.length > 0 && selCount === gIds.length; const isInd = selCount > 0 && selCount < gIds.length; if (grpChk.checked !== isAll) grpChk.checked = isAll; if (grpChk.indeterminate !== isInd) grpChk.indeterminate = isInd; continue; } const boundId = row.dataset.pkBoundId || ''; if (!boundId) continue; const isSelected = S.isSelected(boundId); const isFocused = S.activeId === boundId; const isMoving = S.movingIds.has(boundId); row.className = getRowClassName(isSelected, isFocused, isMoving, selectedCountForSelectionState); const chk = row.querySelector('input[type="checkbox"]'); if (chk && chk.checked !== isSelected) chk.checked = isSelected; if (singleGridSel && isSelected) row.classList.add('pk-sel-single'); else row.classList.remove('pk-sel-single'); if (isFocused && !isSelected) { row.style.backgroundColor = 'var(--pk-sel-bg)'; row.style.border = '1px solid var(--pk-pri)'; row.style.borderRadius = isGridView() ? `${getGridCardRadius()}px` : '4px'; } else { row.style.backgroundColor = ''; row.style.border = ''; row.style.borderRadius = ''; } } }; const getVirtualModeViewKey = (folderId = null) => { const hasExplicitId = folderId !== null; const safeId = hasExplicitId ? String(folderId || '') : ''; if (safeId === 'file_insight' || safeId === 'file_dup' || safeId === 'folder_insight' || safeId === 'folder_dup' || safeId === 'global_search') return safeId; if (safeId === 'virtual_search_root') return 'global_search'; if (hasExplicitId) return ''; const path = Array.isArray(S.path) ? S.path : []; const cur = path.length ? path[path.length - 1] : null; const curId = String((cur && cur.id) || ''); if (curId === 'virtual_search_root' || path.some(p => p && p.id === 'virtual_search_root')) return 'global_search'; if (S.analyzeMode && S.analyzeSimGroups) return 'folder_dup'; if (S.analyzeMode) return 'folder_insight'; if (S.dupMode) return 'file_dup'; if (S.isFlattened) return 'file_insight'; return ''; }; const resolvePreferredViewMode = (folderId = null) => { const curNode = folderId !== null ? { id: folderId } : (S.path[S.path.length - 1] || { id: 'root' }); const curId = String(curNode.id || 'root'); const safeId = folderId !== null ? (folderId || 'root') : curId; const globalMode = gmGet('pk_file_view_mode', 'grid') === 'list' ? 'list' : 'grid'; const independentView = gmGet('pk_view_independent', false); const readModePref = (key, fallback = globalMode) => { try { const prefStore = JSON.parse(gmGet('pk_folder_view_prefs', '{}')); const saved = prefStore[key]; const mode = typeof saved === 'string' ? saved : (saved && saved.viewMode); return mode === 'list' ? 'list' : (mode === 'grid' ? 'grid' : fallback); } catch(e) { return fallback; } }; const virtualKey = getVirtualModeViewKey(safeId); const specialKey = safeId === 'share_parse_insight' || (S.shareParseMode && S.shareParseInsightMode) ? 'share_parse_insight' : (safeId === 'share_parse_list' || (S.shareParseMode && S.shareParseListActive) ? 'share_parse_list' : (virtualKey || (safeId === 'trash_root' || S.trashMode ? 'trash_root' : (safeId === 'starred_root' || S.starredMode ? 'starred_root' : (safeId === 'recent_root' || S.recentMode ? 'recent_root' : (safeId === 'history_root' || S.historyMode ? 'history_root' : '')))))); if (specialKey) { if (!independentView) return globalMode; return readModePref(specialKey, 'grid'); } const isStandard = !S.trashMode && !S.shareMode && !S.offlineMode && !S.starredMode && !S.recentMode && !S.historyMode && !S.isFlattened && !S.dupMode && !S.analyzeMode && (!curId.startsWith('virtual_') || curId === 'virtual_search_root'); if (!isStandard) return globalMode; if (gmGet('pk_view_independent', false)) { return readModePref(safeId, 'grid'); } return globalMode; }; const persistViewPreference = () => { const nextMode = S.viewMode === 'grid' ? 'grid' : 'list'; const curNode = S.path[S.path.length - 1]; const curId = String((curNode && curNode.id) || 'root'); const independentView = gmGet('pk_view_independent', false); const virtualKey = getVirtualModeViewKey(); const specialKey = S.shareParseMode && S.shareParseInsightMode ? 'share_parse_insight' : (S.shareParseMode && S.shareParseListActive ? 'share_parse_list' : (virtualKey || (S.trashMode ? 'trash_root' : (S.starredMode ? 'starred_root' : (S.recentMode ? 'recent_root' : (S.historyMode ? 'history_root' : '')))))); if (specialKey) { if (!independentView) { gmSet('pk_file_view_mode', nextMode); return; } try { const prefStore = JSON.parse(gmGet('pk_folder_view_prefs', '{}')); prefStore[specialKey] = nextMode; gmSet('pk_folder_view_prefs', JSON.stringify(prefStore)); } catch(e) {} return; } gmSet('pk_file_view_mode', nextMode); const isStandard = !S.trashMode && !S.shareMode && !S.offlineMode && !S.starredMode && !S.recentMode && !S.historyMode && !S.isFlattened && !S.dupMode && !S.analyzeMode && (!curId.startsWith('virtual_') || curId === 'virtual_search_root'); if (!isStandard) return; if (gmGet('pk_view_independent', false)) { const folderId = curId; try { const prefStore = JSON.parse(gmGet('pk_folder_view_prefs', '{}')); prefStore[folderId] = nextMode; gmSet('pk_folder_view_prefs', JSON.stringify(prefStore)); } catch(e) {} } }; const readJsonPref = (key, fallback) => { try { const raw = gmGet(key, JSON.stringify(fallback)); const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw; return parsed && typeof parsed === 'object' ? parsed : fallback; } catch(e) { return fallback; } }; const normalizeSortDir = (dir) => Number(dir) === -1 ? -1 : 1; const getCurrentPathNode = () => (Array.isArray(S.path) && S.path.length ? S.path[S.path.length - 1] : { id: '' }); const getSortPrefKey = (folderId = null) => { const hasExplicitId = folderId !== null; const safeId = hasExplicitId ? String(folderId || '') : ''; if (hasExplicitId) { if (safeId === 'file_dup') return ''; if (safeId === 'virtual_search_root') return 'global_search'; if (['starred_root', 'recent_root', 'history_root', 'trash_root', 'file_insight', 'folder_insight', 'folder_dup', 'global_search', 'share_parse_list', 'share_parse_insight'].includes(safeId)) return safeId; } const path = Array.isArray(S.path) ? S.path : []; const cur = hasExplicitId ? { id: safeId } : getCurrentPathNode(); const curId = String((cur && cur.id) || ''); const pathHasGlobalSearch = path.some(p => p && p.id === 'virtual_search_root'); if (S.shareParseMode) { if (S.shareParseInsightMode) return 'share_parse_insight'; if (S.shareParseListActive) return 'share_parse_list'; return ''; } if (S.shareMode || S.offlineMode || S.uploadMode) return ''; if (S.dupMode) return ''; if (S.analyzeMode) { const isAnalyzeRoot = curId === 'analyze_root'; if (S.analyzeSimGroups) return isAnalyzeRoot ? '' : 'folder_dup'; return 'folder_insight'; } if (S.isFlattened) return 'file_insight'; if (pathHasGlobalSearch || curId === 'virtual_search_root') return 'global_search'; if (S.historyMode) return 'history_root'; if (S.trashMode) return 'trash_root'; if (S.starredMode) return 'starred_root'; if (S.recentMode) return 'recent_root'; if (curId.startsWith('virtual_') && curId !== 'virtual_search_root') return ''; return curId || 'root'; }; const isPathSortSupportedForContext = (key) => { const cur = getCurrentPathNode(); const curId = String((cur && cur.id) || ''); if (key === 'file_insight' || key === 'share_parse_insight') return true; if (key === 'folder_insight') return !!(S.analyzeMode && curId === 'analyze_root' && !S.analyzeSimGroups); if (key === 'global_search') return curId === 'virtual_search_root'; return false; }; const getDefaultSortForContext = (key) => { if (key === 'history_root') return { sort: 'play_time', dir: 1 }; return { sort: 'modified_time', dir: 1 }; }; const getLegalSortsForContext = (key) => { if (!key) return []; if (key === 'history_root') return ['starred', 'name', 'size', 'duration', 'progress', 'play_time']; const legal = ['name', 'size', 'modified_time', 'duration']; if (key !== 'share_parse_list' && key !== 'share_parse_insight') legal.unshift('starred'); if (isPathSortSupportedForContext(key)) legal.splice(legal.indexOf('size'), 0, 'path'); if (S.analyzeMode && getCurrentPathNode().id === 'analyze_root') { const idx = legal.indexOf('duration'); if (idx !== -1) legal.splice(idx, 1); } return legal; }; const sanitizeSortForContext = (sortState, key) => { const fallback = getDefaultSortForContext(key); const savedSort = sortState && typeof sortState.sort === 'string' ? sortState.sort : ''; const savedDir = normalizeSortDir(sortState && sortState.dir); const legal = getLegalSortsForContext(key); if (savedSort && legal.includes(savedSort)) { return { sort: savedSort, dir: savedDir, fallbackOnly: false }; } return { sort: fallback.sort, dir: fallback.dir, fallbackOnly: true }; }; const getSortContextSignature = (folderId = null, key = null) => { const prefKey = key !== null ? key : getSortPrefKey(folderId); const pathIds = (Array.isArray(S.path) ? S.path : []).map(p => String((p && p.id) || '')).join('/'); const sharePathIds = (Array.isArray(S.shareParsePath) ? S.shareParsePath : []).map(p => String((p && p.id) || '')).join('/'); return [ prefKey || '', pathIds, S.shareParseMode ? 'share' : '', S.shareParseInsightMode ? 'share_insight' : '', S.shareParseCurParentId || '', sharePathIds, S.analyzeMode ? 'analyze' : '', S.analyzeSimGroups ? 'folder_dup' : '', S.dupMode ? 'file_dup' : '', S.isFlattened ? 'flat' : '' ].join('|'); }; const getCurrentSortSourceItems = () => { if (S.shareParseMode && S.shareParseInsightMode) return Array.isArray(S.shareParseInsightItems) ? S.shareParseInsightItems : []; if (S.shareParseMode && S.shareParseListActive) return Array.isArray(S.shareParseItems) ? S.shareParseItems : []; return Array.isArray(S.items) ? S.items : []; }; const isPureMediaSortContext = () => { const items = getCurrentSortSourceItems(); const files = items.filter(item => item && !item.isHeader && item.kind !== 'drive#folder'); if (!files.length) return false; return files.every(isImageLikeItem) || files.every(isVideoLikeItem); }; const canMediaSortIntervene = (key) => { if (!key || !gmGet('pk_comic_mode', true)) return false; const path = Array.isArray(S.path) ? S.path : []; const cur = getCurrentPathNode(); const curId = String((cur && cur.id) || ''); if (key === 'starred_root') return !!(S.starredMode && path.length > 1); if (key === 'recent_root') return !!(S.recentMode && path.length > 1); if (key === 'folder_insight') return !!(S.analyzeMode && curId !== 'analyze_root' && !S.analyzeSimGroups); if (key === 'folder_dup') return !!(S.analyzeMode && curId !== 'analyze_root' && S.analyzeSimGroups); if (key === 'global_search') return path.some(p => p && p.id === 'virtual_search_root') && curId !== 'virtual_search_root'; if (key === 'share_parse_list') return !!(S.shareParseMode && S.shareParseListActive); if (['history_root', 'trash_root', 'file_insight', 'share_parse_insight'].includes(key)) return false; return !S.shareMode && !S.offlineMode && !S.uploadMode && !S.dupMode && !S.isFlattened && !S.historyMode && !S.trashMode; }; const resolvePreferredSortState = (folderId = null, opts = {}) => { const key = getSortPrefKey(folderId); if (!key) return null; const sig = getSortContextSignature(folderId, key); if (S._sortManualSig === sig) { const manual = sanitizeSortForContext({ sort: S.sort, dir: S.dir }, key); return { ...manual, key, signature: sig, manual: true, fallbackOnly: false }; } const independentSort = gmGet('pk_sort_independent', false); let saved = null; if (independentSort) { const prefStore = readJsonPref('pk_folder_sort_prefs', {}); saved = prefStore[key] || null; } else { saved = readJsonPref('pk_global_sort_pref', { sort: 'modified_time', dir: 1 }); } const sanitized = sanitizeSortForContext(saved, key); if (opts.allowMedia !== false && canMediaSortIntervene(key) && isPureMediaSortContext()) { return { sort: 'name', dir: 1, fallbackOnly: true, key, signature: sig, mediaOverride: true }; } return { ...sanitized, key, signature: sig }; }; const applyResolvedSortState = (folderId = null) => { const key = getSortPrefKey(folderId); const sig = getSortContextSignature(folderId, key); if (S._sortContextSig !== sig) { S._sortContextSig = sig; S._sortManualSig = null; S._mediaSortAutoSig = null; } const resolved = resolvePreferredSortState(folderId); if (!resolved) return null; S.sort = resolved.sort; S.dir = normalizeSortDir(resolved.dir); S._mediaSortAutoSig = resolved.mediaOverride ? resolved.signature : null; try { if (typeof updateGridSortUIInstant === 'function') updateGridSortUIInstant(); } catch(e) {} return resolved; }; const persistSortPreference = () => { const key = getSortPrefKey(); if (!key) return false; const state = sanitizeSortForContext({ sort: S.sort, dir: S.dir }, key); if (state.fallbackOnly) return false; if (gmGet('pk_sort_independent', false)) { try { const prefStore = readJsonPref('pk_folder_sort_prefs', {}); const prev = prefStore[key] || {}; prefStore[key] = { ...prev, sort: state.sort, dir: state.dir }; gmSet('pk_folder_sort_prefs', JSON.stringify(prefStore)); return true; } catch(e) { return false; } } try { const globalPref = readJsonPref('pk_global_sort_pref', {}); globalPref.sort = state.sort; globalPref.dir = state.dir; delete globalPref.folderFirst; gmSet('pk_global_sort_pref', JSON.stringify(globalPref)); } catch(e) { gmSet('pk_global_sort_pref', JSON.stringify({ sort: state.sort, dir: state.dir })); } return true; }; const applyManualSortSelection = (sort) => { const key = getSortPrefKey(); const cur = getCurrentPathNode(); if (!key && (S.dupMode || (S.analyzeMode && cur.id === 'analyze_root' && S.analyzeSimGroups))) return; if (key && !getLegalSortsForContext(key).includes(sort)) return; if (S.sort === sort) S.dir *= -1; else { S.sort = sort; S.dir = 1; } if (key) { const sig = getSortContextSignature(null, key); S._sortContextSig = sig; S._sortManualSig = sig; S._mediaSortAutoSig = null; } S.gridSortMenuOpen = true; updateGridSortUIInstant(); persistSortPreference(); refresh(); }; const getGridSortOptions = () => { const cur = S.path[S.path.length - 1] || { id: 'root' }; const isAnalyzeRoot = S.analyzeMode && cur.id === 'analyze_root'; const key = getSortPrefKey(); const legalSorts = getLegalSortsForContext(key); if (key && !legalSorts.includes(S.sort)) { const safe = sanitizeSortForContext({ sort: S.sort, dir: S.dir }, key); S.sort = safe.sort; S.dir = safe.dir; } const isHistoryGridSort = !!S.historyMode; const isTrashGridSort = !!S.trashMode; const supportsPathSort = !isHistoryGridSort && isPathSortSupportedForContext(key); const supportsDurationTypeSort = !isAnalyzeRoot && !isHistoryGridSort; const opts = [ { sort: 'starred', ascLabel: L.picker_sort_star_new, descLabel: L.picker_sort_star_old, ascIcon: CONF.crumbIcons.sortStarNew, descIcon: CONF.crumbIcons.sortStarOld }, { sort: 'name', ascLabel: 'A-Z', descLabel: 'Z-A', ascIcon: CONF.crumbIcons.sortAZ, descIcon: CONF.crumbIcons.sortZA }, { sort: 'size', ascLabel: L.picker_sort_large, descLabel: L.picker_sort_small, ascIcon: CONF.crumbIcons.sortLarge, descIcon: CONF.crumbIcons.sortSmall }, { sort: 'modified_time', ascLabel: isTrashGridSort ? L.trash_sort_more_to_less : L.picker_sort_new, descLabel: isTrashGridSort ? L.trash_sort_less_to_more : L.picker_sort_old, ascIcon: CONF.crumbIcons.sortNew, descIcon: CONF.crumbIcons.sortOld } ]; if (isHistoryGridSort) { opts.splice(opts.length - 1, 1, { sort: 'duration', ascLabel: L.picker_sort_duration_asc, descLabel: L.picker_sort_duration_desc, ascIcon: CONF.crumbIcons.sortTypeDurAsc, descIcon: CONF.crumbIcons.sortTypeDurDesc }, { sort: 'progress', ascLabel: L.picker_sort_progress_more, descLabel: L.picker_sort_progress_less, ascIcon: CONF.crumbIcons.sortProgressAsc, descIcon: CONF.crumbIcons.sortProgressDesc }, { sort: 'play_time', ascLabel: L.picker_sort_new, descLabel: L.picker_sort_old, ascIcon: CONF.crumbIcons.sortNew, descIcon: CONF.crumbIcons.sortOld } ); return opts; } if (supportsPathSort) { opts.splice(2, 0, { sort: 'path', ascLabel: L.picker_sort_path_asc, descLabel: L.picker_sort_path_desc, ascIcon: CONF.crumbIcons.sortPathAsc, descIcon: CONF.crumbIcons.sortPathDesc } ); } if (supportsDurationTypeSort) { opts.splice(opts.length - 1, 0, { sort: 'duration', ascLabel: L.picker_sort_type_dur_asc, descLabel: L.picker_sort_type_dur_desc, ascIcon: CONF.crumbIcons.sortTypeDurAsc, descIcon: CONF.crumbIcons.sortTypeDurDesc } ); } if (S.shareParseMode) opts.shift(); return key ? opts.filter(opt => legalSorts.includes(opt.sort)) : opts; }; const resolveGridSortOptionState = (opt) => { const isActive = opt.sort === S.sort; const dir = isActive ? S.dir : 1; return { ...opt, dir, active: isActive, label: dir === -1 ? opt.descLabel : opt.ascLabel, icon: dir === -1 ? opt.descIcon : opt.ascIcon }; }; const getGridSortMeta = () => { const opts = getGridSortOptions(); const fallbackOpt = { sort: 'modified_time', ascLabel: L.picker_sort_new, descLabel: L.picker_sort_old, ascIcon: CONF.crumbIcons.sortNew, descIcon: CONF.crumbIcons.sortOld }; const target = opts.find(opt => opt.sort === S.sort) || opts[0] || fallbackOpt; return resolveGridSortOptionState(target); }; const bindGridSortOptions = (root) => { if (!root) return; root.querySelectorAll('.pk-grid-sort-opt').forEach(opt => { opt.onclick = (e) => { e.stopPropagation(); applySortSelection(opt.dataset.sort); }; }); }; const updateGridSortUIInstant = () => { const wrap = UI.gridSortWrap || document.querySelector('.pk-grid-sort-wrap'); if (!wrap) return; const meta = getGridSortMeta(); const trigger = wrap.querySelector('#pk-grid-sort-trigger'); if (trigger) trigger.innerHTML = `${meta.icon}${meta.label}`; const menu = wrap.querySelector('.pk-grid-sort-menu'); if (menu) { menu.innerHTML = getGridSortOptions().map(opt => { const state = resolveGridSortOptionState(opt); return `
${state.icon}${state.label}
`; }).join(''); bindGridSortOptions(menu); } UI.gridSortWrap = wrap; wrap.classList.toggle('open', !!S.gridSortMenuOpen); }; const closeGridSortMenu = (evt) => { if (!UI.gridSortWrap) return; if (!evt) { UI.gridSortWrap.classList.remove('open'); S.gridSortMenuOpen = false; return; } const target = evt.target; const isNodeTarget = !!target && typeof target === 'object' && typeof target.nodeType === 'number'; if (typeof evt.composedPath === 'function') { const path = evt.composedPath(); if (Array.isArray(path) && path.includes(UI.gridSortWrap)) return; } else if (isNodeTarget && UI.gridSortWrap.contains(target)) { return; } UI.gridSortWrap.classList.remove('open'); S.gridSortMenuOpen = false; }; const applySortSelection = (sort) => { applyManualSortSelection(sort); }; const syncFolderFirstFromGlobal = () => { const saved = gmGet('pk_folder_first', false); S.folderFirst = saved === true || saved === 'true'; if (S.renderFolderFirst) S.renderFolderFirst(); }; const setFolderFirstGlobal = (enabled) => { S.folderFirst = enabled === true; gmSet('pk_folder_first', S.folderFirst); if (S.renderFolderFirst) S.renderFolderFirst(); }; const rememberFolderFirstBeforeStrictMode = () => { if (S.strictFolderFirstSnapshot === null) { S.strictFolderFirstSnapshot = S.folderFirst === true; } }; const restoreFolderFirstAfterStrictMode = () => { if (S.strictFolderFirstSnapshot === null) return; S.strictFolderFirstSnapshot = null; syncFolderFirstFromGlobal(); }; const toggleFolderFirst = () => { setFolderFirstGlobal(!S.folderFirst); closeGridSortMenu(); refresh(); }; const ensureViewSwitch = () => { const host = (S.trashMode && UI.trashBar) ? UI.trashBar : UI.actionBar; if (!host) return; let wrap = UI.viewSwitch || el.querySelector('#pk-view-switch'); if (!wrap) { wrap = document.createElement('div'); wrap.className = 'pk-view-switch'; wrap.id = 'pk-view-switch'; wrap.innerHTML = ``; UI.viewSwitch = wrap; UI.btnViewList = wrap.querySelector('#pk-view-list'); UI.btnViewGrid = wrap.querySelector('#pk-view-grid'); UI.btnViewList.onclick = (e) => { e.stopPropagation(); if (S.viewMode === 'list') return; const anchorId = getViewportAnchorId(true); const finishViewSwitch = () => requestAnimationFrame(() => UI.win.classList.remove('pk-view-switching')); UI.win.classList.add('pk-view-switching'); S.viewMode = 'list'; persistViewPreference(); UI.win.classList.remove('pk-grid-view'); syncLayoutMetrics(); renderViewSwitch(); renderList(); if (anchorId) { requestAnimationFrame(() => { const targetIdx = S.display.findIndex(x => x.id === anchorId); if (targetIdx !== -1) { const rowTop = getItemScrollTopByIndex(targetIdx); const vpHeight = UI.vp.clientHeight; UI.vp.scrollTop = Math.max(0, rowTop - (vpHeight / 2) + (CONF.rowHeight / 2)); } finishViewSwitch(); }); } else { finishViewSwitch(); } }; UI.btnViewGrid.onclick = (e) => { e.stopPropagation(); if (!canUseGridView() || S.viewMode === 'grid') return; const anchorId = getViewportAnchorId(true); const finishViewSwitch = () => requestAnimationFrame(() => UI.win.classList.remove('pk-view-switching')); UI.win.classList.add('pk-view-switching'); S.viewMode = 'grid'; persistViewPreference(); UI.win.classList.add('pk-grid-view'); syncLayoutMetrics(); renderViewSwitch(); renderList(); if (anchorId) { requestAnimationFrame(() => { const targetIdx = S.display.findIndex(x => x.id === anchorId); if (targetIdx !== -1) { const rowTop = getItemScrollTopByIndex(targetIdx); const vpHeight = UI.vp.clientHeight; UI.vp.scrollTop = Math.max(0, rowTop - (vpHeight / 2) + (CONF.rowHeight / 2)); } finishViewSwitch(); }); } else { finishViewSwitch(); } }; } if (!UI.viewSwitch) UI.viewSwitch = wrap; if (!UI.btnViewList) UI.btnViewList = wrap.querySelector('#pk-view-list'); if (!UI.btnViewGrid) UI.btnViewGrid = wrap.querySelector('#pk-view-grid'); if (wrap.parentNode !== host) { if (S.trashMode && UI.btnEmptyTrash && UI.btnEmptyTrash.parentNode === host) UI.btnEmptyTrash.insertAdjacentElement('afterend', wrap); else if (S.trashMode && UI.btnTrashRefresh && UI.btnTrashRefresh.parentNode === host) UI.btnTrashRefresh.insertAdjacentElement('afterend', wrap); else if (UI.btnCancelShare && UI.btnCancelShare.parentNode === host) UI.btnCancelShare.insertAdjacentElement('afterend', wrap); else host.appendChild(wrap); } }; const renderViewSwitch = () => { const usable = canUseGridView(); if (!usable) { if (UI.viewSwitch) { UI.viewSwitch.style.display = 'none'; UI.viewSwitch.style.visibility = 'hidden'; UI.viewSwitch.style.pointerEvents = 'none'; } return; } ensureViewSwitch(); if (!UI.viewSwitch) return; UI.viewSwitch.style.display = 'inline-flex'; UI.viewSwitch.style.visibility = ''; UI.viewSwitch.style.pointerEvents = ''; if (UI.btnViewList) UI.btnViewList.classList.toggle('active', !isGridView()); if (UI.btnViewGrid) UI.btnViewGrid.classList.toggle('active', isGridView()); }; const applyResolvedViewMode = (prefKey = null) => { const nextViewMode = (typeof resolvePreferredViewMode === 'function') ? resolvePreferredViewMode(prefKey) : (gmGet('pk_file_view_mode', 'grid') === 'list' ? 'list' : 'grid'); const modeChanged = S.viewMode !== nextViewMode; if (modeChanged) { beginFolderViewSync(); if (UI.win) UI.win.classList.add('pk-view-switching', 'pk-mode-view-syncing'); } S.viewMode = nextViewMode; S._gridLayoutKey = ''; clearGroupedGridMetaCache(); if (UI.win) UI.win.classList.toggle('pk-grid-view', isGridView()); syncLayoutMetrics(); renderViewSwitch(); if (modeChanged && UI.win) requestAnimationFrame(() => UI.win.classList.remove('pk-mode-view-syncing')); return nextViewMode; }; const isSystemItem = (item) => { if (!item) return false; if (item.kind !== 'drive#folder') return false; if (item._isSystemRoot) return true; const isRootLocation = S.path.length === 1 && S.path[0].id === ''; if (isRootLocation && item.name === CONF.SYSTEM_FOLDER_NAME) return true; if (!item.parent_id && item.name === CONF.SYSTEM_FOLDER_NAME) return true; return false; }; const updateCrawlerUI = () => { if (!UI.btnNavHome) return; if (typeof isBackgroundRunning !== 'undefined' && isBackgroundRunning) { UI.btnNavHome.classList.add('pk-status-dot'); } else { UI.btnNavHome.classList.remove('pk-status-dot'); } }; window.pkUpdateCrawlerUI = updateCrawlerUI; let isForcedHidden = false; let autoHideBtnTextRaf = 0; let autoHideBtnTextBound = false; let autoHideBtnTextLockWidth = 0; const isAutoHideVisibleEl = (node) => { if (!node || !node.isConnected) return false; const st = getComputedStyle(node); if (st.display === 'none' || st.visibility === 'hidden') return false; const r = node.getBoundingClientRect(); return r.width > 0 && r.height > 0; }; const detectAutoHideButtonTextOverflow = () => { if (!el || !UI || !UI.win || el.style.display === 'none' || !isAutoHideVisibleEl(UI.win)) return false; const tol = 4; const bars = [el.querySelector('#pk-top-bar'), UI.actionBar, UI.trashBar].filter(isAutoHideVisibleEl); for (const bar of bars) { const br = bar.getBoundingClientRect(); const children = Array.from(bar.children).filter(isAutoHideVisibleEl); for (const child of children) { const cr = child.getBoundingClientRect(); if (cr.right > br.right + tol) return true; } } return false; }; const updateAutoHideButtonText = () => { autoHideBtnTextRaf = 0; if (!el || !UI || !UI.win) return; const syncQuotaText = () => { try { if (typeof refreshQuotaText === 'function') refreshQuotaText(); } catch(e) {} }; if (el.style.display === 'none') { el.classList.remove('pk-auto-hide-btn-text'); autoHideBtnTextLockWidth = 0; syncQuotaText(); return; } const width = window.innerWidth || 0; const unlockGap = 96; const wasAuto = el.classList.contains('pk-auto-hide-btn-text'); if (wasAuto) { if (!autoHideBtnTextLockWidth) autoHideBtnTextLockWidth = width; if (width < autoHideBtnTextLockWidth + unlockGap) return; el.classList.remove('pk-auto-hide-btn-text'); if (detectAutoHideButtonTextOverflow()) { el.classList.add('pk-auto-hide-btn-text'); autoHideBtnTextLockWidth = width; } else { autoHideBtnTextLockWidth = 0; } syncQuotaText(); } else if (detectAutoHideButtonTextOverflow()) { el.classList.add('pk-auto-hide-btn-text'); autoHideBtnTextLockWidth = width; syncQuotaText(); } else { autoHideBtnTextLockWidth = 0; } }; const requestAutoHideButtonTextCheck = () => { if (autoHideBtnTextRaf) return; autoHideBtnTextRaf = requestAnimationFrame(updateAutoHideButtonText); }; const bindAutoHideButtonTextObservers = () => { if (autoHideBtnTextBound) return; autoHideBtnTextBound = true; const targets = [UI.win, el.querySelector('#pk-top-bar'), UI.actionBar, UI.trashBar, UI.crumb].filter(Boolean); if (typeof ResizeObserver !== 'undefined') { const ro = new ResizeObserver(requestAutoHideButtonTextCheck); targets.forEach(t => ro.observe(t)); } if (typeof MutationObserver !== 'undefined') { const mo = new MutationObserver(requestAutoHideButtonTextCheck); targets.slice(1, 4).forEach(t => mo.observe(t, { attributes: true, childList: true, subtree: true, attributeFilter: ['style', 'class'] })); } requestAutoHideButtonTextCheck(); }; const checkGuiResponsiveness = () => { const width = window.innerWidth; document.body.classList.remove('pk-hide-all-ui'); if (isForcedHidden) { el.style.display = 'flex'; if (el.focus) el.focus(); isForcedHidden = false; } const isCompact = width <= 1200; if (UI.searchInput) { UI.searchInput.placeholder = isCompact ? L.placeholder_search_short : L.placeholder_search; } if (typeof syncShareParseInsightSearchPathLabel === 'function') syncShareParseInsightSearchPathLabel(); const folderSelPlaceholder = document.querySelector('#pk-dup-folder-sel option[value=""]'); if (folderSelPlaceholder) { folderSelPlaceholder.textContent = isCompact ? L.lbl_dup_select_folder_short : L.lbl_dup_select_folder; } requestAutoHideButtonTextCheck(); }; const syncDupFolderButtonText = () => { const sel = UI.selDupFolder; const btn = document.getElementById('pk-dup-folder-btn'); const txt = document.getElementById('pk-dup-folder-btn-txt'); if (!sel || !btn || !txt) return; const placeholder = window.innerWidth <= 1200 ? L.lbl_dup_select_folder_short : L.lbl_dup_select_folder; let label = placeholder; const opt = sel.selectedIndex >= 0 ? sel.options[sel.selectedIndex] : null; if (opt && opt.value && opt.value !== "__RESET__") label = (opt.textContent || placeholder).trim(); txt.textContent = label; btn.removeAttribute('title'); btn.removeAttribute('data-pk-tip'); }; (() => { const sel = UI.selDupFolder; const btn = document.getElementById('pk-dup-folder-btn'); const wrap = document.getElementById('pk-dup-folder-sel-wrap'); if (!sel || !btn || !wrap) return; let pop = null; const closePop = () => { if (pop && pop.parentNode) pop.remove(); pop = null; btn.classList.remove('act'); }; const openPop = () => { closePop(); const opts = Array.from(sel.options).filter(o => !(o.disabled && !o.value)); opts.sort((a, b) => (b.value === "__RESET__") - (a.value === "__RESET__")); if (!opts.length) return; pop = document.createElement('div'); pop.className = 'pk-dup-folder-pop'; pop.classList.toggle('pk-dark', !!document.querySelector('.pk-ov')?.classList.contains('pk-dark')); pop.innerHTML = '
'; const head = pop.firstElementChild; const list = pop.lastElementChild; opts.forEach(o => { const item = document.createElement('button'); item.type = 'button'; item.className = `pk-dup-folder-item${o.value === sel.value ? ' act' : ''}${o.value === "__RESET__" ? ' pk-reset' : ''}`; const itemText = (o.textContent || '').trim(); item.dataset.value = o.value; item.textContent = itemText; item.removeAttribute('title'); if (itemText && o.value !== "__RESET__") item.setAttribute('data-pk-tip', itemText); if (o.value === "__RESET__") head.appendChild(item); else list.appendChild(item); }); document.body.appendChild(pop); pop.addEventListener('mousedown', (e) => e.stopPropagation(), true); pop.addEventListener('click', (e) => e.stopPropagation(), true); pop.addEventListener('wheel', (e) => e.stopPropagation(), { passive: true }); const rect = typeof getLogicalRect === 'function' ? getLogicalRect(btn) : btn.getBoundingClientRect(); const popRect = pop.getBoundingClientRect(); const winW = window.innerWidth; const winH = window.innerHeight; let left = rect.left; let top = rect.bottom + 4; if (left + popRect.width > winW - 8) left = Math.max(8, winW - popRect.width - 8); if (top + popRect.height > winH - 8) top = Math.max(8, rect.top - popRect.height - 4); pop.style.left = `${left}px`; pop.style.top = `${top}px`; btn.classList.add('act'); const activeItem = list.querySelector('.pk-dup-folder-item.act'); if (activeItem) activeItem.scrollIntoView({ block: 'nearest' }); }; btn.addEventListener('click', (e) => { e.stopPropagation(); syncDupFolderButtonText(); if (pop) closePop(); else openPop(); }); document.addEventListener('click', (e) => { if (!pop) return; const t = e.target; if (pop.contains(t)) { const item = t.closest('.pk-dup-folder-item'); if (!item) return; const pickedValue = item.dataset.value || ''; S._dupFolderPickedScrollTop = !!(pickedValue && pickedValue !== "__RESET__"); sel.value = pickedValue; syncDupFolderButtonText(); closePop(); sel.dispatchEvent(new Event('change', { bubbles: true })); return; } if (!wrap.contains(t)) closePop(); }, true); const handleDupFolderViewportChange = (e) => { if (!pop) return; const t = e && e.target; if (t && pop.contains(t)) return; closePop(); }; window.addEventListener('resize', handleDupFolderViewportChange, true); window.addEventListener('scroll', handleDupFolderViewportChange, true); syncDupFolderButtonText(); })(); window.addEventListener('resize', () => { checkGuiResponsiveness(); syncDupFolderButtonText(); if (el.style.display === 'none') return; const isAuthManagerReloading = !!( typeof window.pkIsAuthManagerReloading === 'function' && window.pkIsAuthManagerReloading() ); if (isAuthManagerReloading) { if (typeof window.pkDeferAuthManagerRelayout === 'function') { window.pkDeferAuthManagerRelayout(); } return; } const isDupAnalyzeMaskBusy = !!( S.loading && S.dupMode && S.dupRunning && UI.loader && UI.loader.style.display !== 'none' ); if (isDupAnalyzeMaskBusy) return; if (isGridView() && typeof scheduleGridRelayout === 'function') { scheduleGridRelayout(true); } else if (typeof renderList === 'function') { requestAnimationFrame(() => { if (typeof syncLayoutMetrics === 'function') syncLayoutMetrics(); renderList(); }); } else if (typeof renderVisible === 'function') { requestAnimationFrame(() => { if (typeof syncLayoutMetrics === 'function') syncLayoutMetrics(); if (UI.in) UI.in.style.height = `${S.display.length * CONF.rowHeight}px`; renderVisible(); }); } }); checkGuiResponsiveness(); bindAutoHideButtonTextObservers(); if (UI.btnClose) { UI.btnClose.addEventListener('click', () => { isForcedHidden = false; requestAnimationFrame(checkGuiResponsiveness); }); } if (UI.btnTheme) { UI.btnTheme.onclick = (e) => { if (e) e.stopPropagation(); el.classList.add('pk-no-transition'); void el.offsetHeight; const wasDark = el.classList.contains('pk-dark'); const newTheme = wasDark ? 'light' : 'dark'; const newIcon = wasDark ? CONF.icons.moon : CONF.icons.sun; UI.btnTheme.innerHTML = newIcon; if (wasDark) { el.classList.remove('pk-dark'); } else { el.classList.add('pk-dark'); } const uploadMenu = (UI.uploadWrap && UI.uploadWrap.querySelector('.pk-dropdown-menu')) || document.querySelector('.pk-dropdown-menu[data-pk-portal="1"]'); if (uploadMenu) uploadMenu.classList.toggle('pk-dark', !wasDark); const crumbPop = document.getElementById('pk-main-crumb-pop'); if (crumbPop) crumbPop.classList.toggle('pk-dark', !wasDark); document.querySelectorAll('#pk-toast-container,.pk-msg-toast,.pk-float-bar-item,#pk-script-update-toast,.pk-script-update-toast,#pk-audio-mini,#pk-audio-ov').forEach(n => n.classList.toggle('pk-dark', !wasDark)); const audioMiniNow = document.getElementById('pk-audio-mini'); const audioFullNow = document.getElementById('pk-audio-ov'); if (audioMiniNow && typeof syncAudioMiniTheme === 'function') requestAnimationFrame(() => syncAudioMiniTheme(audioMiniNow)); if (audioFullNow && typeof syncAudioFullPlayerTheme === 'function') requestAnimationFrame(() => syncAudioFullPlayerTheme(audioFullNow)); gmSet('pk_theme', newTheme); void el.offsetHeight; requestAnimationFrame(() => { el.classList.remove('pk-no-transition'); }); }; } const btnMax = el.querySelector('#pk-maximize'); const isTurbo = gmGet('pk_turbo_mode', false); let isWinMaximized = (globalSavedState && typeof globalSavedState.isMaximized !== 'undefined') ? globalSavedState.isMaximized : isTurbo; let pkSyncHideMenuLock = false; const closeTransientDropdowns = (includeEscape = false, blurSearch = false, dispatchNative = false) => { if (pkSyncHideMenuLock) return; pkSyncHideMenuLock = true; try { closeLocalUploadMenu(); if (blurSearch && UI.searchInput && document.activeElement === UI.searchInput) UI.searchInput.blur(); document.querySelectorAll('#pk-main-crumb-pop,.pk-crumb-pop').forEach(n => n.remove()); if (UI.crumb) { UI.crumb.querySelectorAll('.pk-crumb-sep.pk-active').forEach(btn => { btn.classList.remove('pk-active'); btn.innerHTML = CONF.crumbIcons.right; }); } if (UI.searchHist) { UI.searchHist.style.display = 'none'; UI.searchHist.innerHTML = ''; } if (UI.pop) UI.pop.style.display = 'none'; if (UI.ctx) UI.ctx.style.display = 'none'; document.querySelectorAll('.pk-select-menu,.pk-hist-pop').forEach(menu => { menu.style.display = 'none'; }); document.querySelectorAll('.pk-dropdown-menu[data-pk-portal="1"]').forEach(n => n.remove()); if (dispatchNative) { if (includeEscape) { const keyInit = { key: 'Escape', code: 'Escape', keyCode: 27, which: 27, bubbles: true, cancelable: true }; document.dispatchEvent(new KeyboardEvent('keydown', keyInit)); document.dispatchEvent(new KeyboardEvent('keyup', keyInit)); } document.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, clientX: 0, clientY: 0, button: 0 })); document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true, clientX: 0, clientY: 0, button: 0 })); document.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, clientX: 0, clientY: 0, button: 0 })); } } catch (e) {} setTimeout(() => { pkSyncHideMenuLock = false; }, 0); }; const syncHideNativeExitDropdown = () => closeTransientDropdowns(false); const pkWinHideObserver = new MutationObserver(() => { if (!el.isConnected) return; const cs = getComputedStyle(el); if (el.hidden || cs.display === 'none' || cs.visibility === 'hidden') { requestAnimationFrame(syncHideNativeExitDropdown); } }); pkWinHideObserver.observe(el, { attributes: true, attributeFilter: ['style', 'class', 'hidden'] }); if (isTurbo) { if (btnMax) btnMax.style.display = 'none'; if (UI.btnClose) UI.btnClose.style.display = 'none'; } const win = el.querySelector('.pk-win'); if (isWinMaximized) { if (win) win.classList.add('pk-maximized'); document.body.classList.add('pk-body-max'); CONF.rowHeight = 60; if (btnMax) { btnMax.innerHTML = CONF.icons.minimize; btnMax.setAttribute('data-pk-tip', L.tip_minimize); } } else { if (win) win.classList.remove('pk-maximized'); document.body.classList.remove('pk-body-max'); CONF.rowHeight = 40; if (btnMax) { btnMax.innerHTML = CONF.icons.maximize; btnMax.setAttribute('data-pk-tip', L.tip_maximize); } } if (btnMax) { btnMax.onclick = (e) => { if (e) e.stopPropagation(); el.classList.add('pk-no-transition'); const vp = UI.vp; const oldRowHeight = CONF.rowHeight; const centerIndex = (vp.scrollTop + vp.clientHeight / 2) / oldRowHeight; isWinMaximized = !isWinMaximized; btnMax.innerHTML = isWinMaximized ? CONF.icons.minimize : CONF.icons.maximize; btnMax.setAttribute('data-pk-tip', isWinMaximized ? L.tip_minimize : L.tip_maximize); const win = el.querySelector('.pk-win'); if (isWinMaximized) { win.classList.add('pk-maximized'); document.body.classList.add('pk-body-max'); CONF.rowHeight = 60; } else { win.classList.remove('pk-maximized'); document.body.classList.remove('pk-body-max'); CONF.rowHeight = 40; } if (typeof renderList === 'function') { renderList(); } else if (typeof renderVisible === 'function') { if(UI.in) UI.in.style.height = `${S.display.length * CONF.rowHeight}px`; renderVisible(); } if (typeof refreshQuotaText === 'function') refreshQuotaText(); requestAnimationFrame(() => { void el.offsetHeight; const newRowHeight = CONF.rowHeight; const newVpHeight = vp.clientHeight; const targetScrollTop = (centerIndex * newRowHeight) - (newVpHeight / 2); vp.scrollTop = Math.max(0, targetScrollTop); renderVisible(); el.classList.remove('pk-no-transition'); }); }; } let modalZIndexCounter = 2147483640; function showModal(html) { let container = document.getElementById('pk-toast-container'); if (container) document.body.appendChild(container); const m = document.createElement('div'); m.className = 'pk-modal-ov'; const isPlayerWebFullscreen = !!document.querySelector('.pk-web-fullscreen-ov'); if (isPlayerWebFullscreen) { m.classList.add('pk-modal-over-player-webfullscreen'); m.style.setProperty('z-index', '2147483647', 'important'); } else { m.style.zIndex = (++modalZIndexCounter).toString(); } if (document.querySelector('.pk-ov').classList.contains('pk-dark')) { m.classList.add('pk-dark'); } m.innerHTML = `
${CONF.icons.close}
${html}
`; const actBars = m.querySelectorAll('.pk-modal-act'); actBars.forEach(bar => { const btns = Array.from(bar.children).filter(child => child.classList.contains('pk-btn')); if (!bar.querySelector('.pk-bl-btn') && (btns.length === 1 || btns.length === 2)) { bar.style.setProperty('display', 'grid', 'important'); bar.style.setProperty('grid-template-columns', btns.length === 1 ? '1fr' : '1fr 1fr', 'important'); bar.style.setProperty('gap', '15px', 'important'); bar.style.setProperty('width', '100%', 'important'); bar.style.setProperty('margin', '0', 'important'); bar.style.setProperty('margin-top', '20px', 'important'); btns.forEach(btn => { btn.style.setProperty('height', '46px', 'important'); btn.style.setProperty('border-radius', '12px', 'important'); btn.style.setProperty('font-size', '15px', 'important'); btn.style.setProperty('font-weight', '600', 'important'); btn.style.setProperty('justify-content', 'center', 'important'); btn.style.setProperty('padding', '0', 'important'); btn.style.setProperty('margin', '0', 'important'); btn.style.setProperty('min-width', '0', 'important'); if (btn.classList.contains('pri')) { btn.style.setProperty('background', 'var(--pk-pri)', 'important'); btn.style.setProperty('color', '#fff', 'important'); btn.style.setProperty('border', 'none', 'important'); btn.style.setProperty('transition', 'filter 0.2s', 'important'); } else { btn.style.setProperty('background', 'transparent', 'important'); btn.style.setProperty('color', 'var(--pk-fg)', 'important'); btn.style.setProperty('border', '1px solid transparent', 'important'); btn.onmouseover = () => btn.style.setProperty('background', 'var(--pk-hl)', 'important'); btn.onmouseout = () => btn.style.setProperty('background', 'transparent', 'important'); } }); } }); document.body.appendChild(m); m.querySelector('.pk-modal-close').addEventListener('click', () => m.remove()); return m; } function showAlert(msg, title = L.title_alert) { return new Promise((resolve) => { const m = showModal(`

${title}

${msg.replace(/\n/g, '
')}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { Object.assign(modalBox.style, { width: '420px', padding: '30px', boxSizing: 'border-box' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '26px', right: '26px' }); } m.querySelector('#alert_ok').onclick = () => { m.remove(); resolve(); }; m.querySelector('.pk-modal-close').onclick = () => { m.remove(); resolve(); }; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#alert_ok').click(); } }); }); } function showConfirm(msg, title = L.title_confirm) { return new Promise((resolve) => { const m = showModal(`

${title}

${esc(msg).replace(/\n/g, '
')}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { Object.assign(modalBox.style, { width: '420px', height: 'auto', minHeight: 'auto', padding: '30px' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '26px', right: '26px' }); } m.querySelector('#cfm_no').onclick = () => { m.remove(); resolve(false); }; m.querySelector('#cfm_yes').onclick = () => { m.remove(); resolve(true); }; m.querySelector('.pk-modal-close').onclick = () => { m.remove(); resolve(false); }; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#cfm_yes').click(); } }); }); } function showDeleteConfirm(msg, title = L.title_confirm) { return new Promise((resolve) => { const m = showModal(`

${title}

${esc(msg).replace(/\n/g, '
')}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { Object.assign(modalBox.style, { width: '420px', height: 'auto', minHeight: 'auto', padding: '30px' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '26px', right: '26px' }); } m.querySelector('#cfm_no').onclick = () => { m.remove(); resolve({ confirm: false }); }; m.querySelector('#cfm_yes').onclick = () => { const isHard = m.querySelector('#pk_hard_delete_chk').checked; m.remove(); resolve({ confirm: true, hardDelete: isHard }); }; m.querySelector('.pk-modal-close').onclick = () => { m.remove(); resolve({ confirm: false }); }; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#cfm_yes').click(); } }); }); } const confirmSelectionClear = async () => { if (S.suppressClearConfirm) return true; return new Promise((resolve) => { const m = showModal(`

${L.title_confirm}

${esc(L.msg_clear_sel_confirm.replace('{n}', S.getSelectedCount())).replace(/\n/g, '
')}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) Object.assign(modalBox.style, { width: '420px', padding: '30px' }); m.querySelector('#cfm_no').onclick = () => { m.remove(); resolve(false); }; m.querySelector('#cfm_yes').onclick = () => { const isChecked = m.querySelector('#pk_session_suppress').checked; if (isChecked) S.suppressClearConfirm = true; m.remove(); resolve(true); }; m.querySelector('.pk-modal-close').onclick = () => { m.remove(); resolve(false); }; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#cfm_yes').click(); } }); }); }; function showPrompt(msg, val = '', title = L.title_prompt) { return new Promise((resolve) => { const cleanTitle = esc(msg).replace(/[::]$/, ''); const isNewFolder = (title === L.btn_newfolder); const okBtnText = isNewFolder ? L.btn_create : L.btn_ok; const m = showModal(`

${cleanTitle}

${title}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { Object.assign(modalBox.style, { width: '480px', height: 'auto', minHeight: 'auto', padding: '30px' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '26px', right: '26px' }); } const inp = m.querySelector('#prm_input'); const err = m.querySelector('#prm_err'); const okBtn = m.querySelector('#prm_ok'); const validate = () => { const v = inp.value.trim(); const isEmpty = v === ''; const isDup = S.items.some(item => item.name === v && item.name !== val); err.style.visibility = isDup ? 'visible' : 'hidden'; if (isDup || isEmpty) { okBtn.disabled = true; okBtn.style.opacity = '0.4'; okBtn.style.cursor = 'not-allowed'; inp.style.borderColor = isDup ? '#ff4d4f' : 'var(--pk-bd)'; } else if (v === val) { okBtn.disabled = false; okBtn.style.opacity = '1'; okBtn.style.cursor = 'pointer'; inp.style.borderColor = 'var(--pk-bd)'; } else { okBtn.disabled = false; okBtn.style.opacity = '1'; okBtn.style.cursor = 'pointer'; inp.style.borderColor = 'var(--pk-pri)'; } }; inp.focus(); if (val && val.includes('.') && val.lastIndexOf('.') > 0) { inp.setSelectionRange(0, val.lastIndexOf('.')); } else { inp.select(); } inp.addEventListener('input', validate); validate(); inp.onkeydown = (e) => { if (e.key === 'Enter' && !okBtn.disabled) okBtn.click(); if (e.key === 'Escape') { e.stopPropagation(); m.remove(); resolve(null); } }; m.querySelector('#prm_cancel').onclick = () => { m.remove(); resolve(null); }; m.querySelector('#prm_ok').onclick = () => { const v = inp.value.trim(); m.remove(); resolve(v); }; m.querySelector('.pk-modal-close').onclick = () => { m.remove(); resolve(null); }; }); } function getPkToastHost() { return document.fullscreenElement || document.webkitFullscreenElement || document.body; } function ensurePkToastContainer() { let container = document.getElementById('pk-toast-container'); const host = getPkToastHost(); if (!container) { container = document.createElement('div'); container.id = 'pk-toast-container'; container.style.cssText = 'position:fixed; top:80px; left:50%; transform:translateX(-50%); display:flex; flex-direction:column; gap:12px; z-index:2147483647; pointer-events:none; align-items:center'; } if (host && container.parentNode !== host) host.appendChild(container); return container; } function showToast(msg, type = 'success', duration = 0) { let container = ensurePkToastContainer(); const t = document.createElement('div'); t.className = `pk-msg-toast ${type}`; const ov = document.querySelector('.pk-ov'); const savedThemeNow = gmGet('pk_theme', 'auto'); const sysDarkNow = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; const isDark = ov ? ov.classList.contains('pk-dark') : (savedThemeNow === 'dark' || (savedThemeNow === 'auto' && sysDarkNow)); document.querySelectorAll('.pk-msg-toast').forEach(n => n.classList.toggle('pk-dark', isDark)); if (isDark) t.classList.add('pk-dark'); t.style.cssText = 'position:relative; top:auto; left:auto; transform:translateY(-15px) scale(0.95); opacity:0; transition:all 0.3s cubic-bezier(0.23, 1, 0.32, 1); max-width:80vw;'; if (type === 'error' || type === 'warning') { const icon = CONF.icons.warning.replace('style="', 'style="flex-shrink: 0; '); t.innerHTML = `${icon}${msg}`; t.style.backgroundColor = type === 'warning' ? 'rgba(250, 173, 20, 0.95)' : 'rgba(217, 48, 37, 0.95)'; t.style.color = '#ffffff'; if (type === 'warning') t.style.border = '1px solid rgba(250, 173, 20, 0.2)'; } else { t.textContent = msg; } container.prepend(t); requestAnimationFrame(() => { t.style.transform = 'translateY(0) scale(1)'; t.style.opacity = '1'; }); const displayTime = duration > 0 ? duration : (type === 'success' ? 2200 : 3500); setTimeout(() => { t.style.opacity = '0'; t.style.transform = 'translateY(-15px) scale(0.95)'; setTimeout(() => { if(t.parentNode) t.remove(); }, 300); }, displayTime); } function showScriptUpdateToast(info) { const latest = info && info.latestVersion ? String(info.latestVersion) : ''; if (!latest || document.getElementById('pk-script-update-toast') || isScriptUpdateDismissed(latest)) return; let container = ensurePkToastContainer(); const t = document.createElement('div'); t.id = 'pk-script-update-toast'; t.className = 'pk-script-update-toast'; const ov = document.querySelector('.pk-ov'); const savedThemeNow = gmGet('pk_theme', 'auto'); const sysDarkNow = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; const isDark = ov ? ov.classList.contains('pk-dark') : (savedThemeNow === 'dark' || (savedThemeNow === 'auto' && sysDarkNow)); if (isDark) t.classList.add('pk-dark'); const msg = esc(L.msg_script_update_new).replace('{v}', `${esc(latest)}`); t.innerHTML = `${msg}`; const openUrl = () => { const m = {tc:'README(tc).md',en:'README(en).md',ko:'README(ko).md',ja:'README(ja).md',id:'README(id).md',ms:'README(ms).md'}; const f = m[getLang()]; window.open(`${CONF.scriptUpdateProjectUrl}/blob/main/${f ? `i18n/${f}` : 'README.md'}`, '_blank', 'noopener,noreferrer'); }; t.onclick = (e) => { if (e.target && e.target.closest('[data-pk-script-update-close]')) return; openUrl(); }; const openBtn = t.querySelector('[data-pk-script-update-open]'); if (openBtn) openBtn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); openUrl(); }; const closeBtn = t.querySelector('[data-pk-script-update-close]'); if (closeBtn) closeBtn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); markScriptUpdateDismissed(latest); t.style.opacity = '0'; t.style.transform = 'translateY(-15px) scale(0.95)'; setTimeout(() => { if (t.parentNode) t.remove(); }, 300); }; container.prepend(t); requestAnimationFrame(() => { t.style.transform = 'translateY(0) scale(1)'; t.style.opacity = '1'; }); } async function runScriptUpdateCheck() { try { const info = await fetchScriptUpdateInfo(false); if (isScriptUpdateNew(info)) showScriptUpdateToast(info); } catch (e) {} } function showBlacklistModal() { const icons = { paste: ``, delRow: ``, trash: ``, rocket: ``, save: `` }; const toastStyle = ` position: absolute; top: 120px; left: 50%; transform: translateX(-50%); background: var(--pk-toast-bg); backdrop-filter: blur(10px); color: var(--pk-toast-fg); border: 1px solid var(--pk-toast-bd); padding: 8px 24px; border-radius: 99px; font-size: 13px; z-index: 2000; pointer-events: none; opacity: 0; transition: opacity 0.3s, transform 0.3s; box-shadow: 0 8px 20px var(--pk-tip-sd); text-align: center; font-weight: 500; display: flex; align-items: center; gap: 8px; `; const textareaStyle = ` flex: 1; resize: none; border: 2px solid transparent; border-radius: 8px; padding: 15px; background: var(--pk-hl); color: var(--pk-fg); font-size: 13px; font-family: inherit; cursor: auto; outline: none; line-height: 1.6; letter-spacing: 0.3px; transition: background 0.2s, border-color 0.2s, box-shadow 0.2s; width: 100%; box-sizing: border-box; `; const modalInnerStyle = ` `; const m = showModal(` ${modalInnerStyle}

${L.title_blacklist}

${L.tip_bl_desc}
`); const modalEl = m.querySelector('.pk-modal'); Object.assign(modalEl.style, { width: '600px', maxWidth: '90vw', padding: '30px', height: '600px', maxHeight: '85vh' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '26px', right: '26px' }); const areaFolder = m.querySelector('#bl_folder_input'); const areaFile = m.querySelector('#bl_file_input'); const radios = m.querySelectorAll('input[name="bl_mode"]'); const groupFolder = m.querySelector('#group_bl_folder'); const groupFile = m.querySelector('#group_bl_file'); radios.forEach(r => { r.onchange = () => { const mode = r.value; groupFolder.style.display = mode === 'folder' ? 'flex' : 'none'; groupFile.style.display = mode === 'file' ? 'flex' : 'none'; }; }); const toast = m.querySelector('#pk_bl_toast'); const showToast = (msg) => { toast.textContent = msg; toast.style.opacity = '1'; setTimeout(() => { toast.style.opacity = '0'; }, 2000); }; const loadLargeText = async (el, storageKey, originalPlaceholder) => { el.placeholder = L.str_loading_placeholder; await sleep(350); const data = await new Promise(r => setTimeout(() => r(gmGet(storageKey, '')), 0)); if (data) { const prevDisplay = el.style.display; el.style.display = 'none'; el.value = data; el.style.display = prevDisplay; } el.placeholder = originalPlaceholder; el.setSelectionRange(0, 0); el.blur(); }; loadLargeText(areaFolder, 'pk_blacklist_folders', L.ph_bl_file); loadLargeText(areaFile, 'pk_blacklist', L.ph_bl_file); const highlightLine = (el) => { if (el.selectionStart !== el.selectionEnd) return; const val = el.value; if (!val) return; const cursor = el.selectionStart; let start = val.lastIndexOf('\n', cursor - 1); start = start === -1 ? 0 : start + 1; let end = val.indexOf('\n', cursor); if (end === -1) end = val.length; el.setSelectionRange(start, end); }; const enableLineSnap = (el) => { el.addEventListener('click', () => highlightLine(el)); el.addEventListener('keyup', (e) => { if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) highlightLine(el); }); }; enableLineSnap(areaFolder); enableLineSnap(areaFile); const setupSafeControls = (el, btnPaste, btnDel) => { btnPaste.onclick = async () => { try { const text = await navigator.clipboard.readText(); if (!text || !text.trim()) return showToast(L.msg_copy_empty); const cleanText = text.split(/\r?\n/).map(line => line.trim()).filter(line => line).join('\n'); const oldVal = el.value; const prefix = (oldVal && !oldVal.endsWith('\n')) ? '\n' : ''; el.value = oldVal + prefix + cleanText; el.scrollTop = el.scrollHeight; const addedCount = cleanText.split('\n').length; showToast(L.msg_add_success.replace('{n}', addedCount)); } catch (err) { showToast(L.err_clipboard_denied); } }; btnDel.onclick = () => { const val = el.value; if (!val) return; const selStart = el.selectionStart; const selEnd = el.selectionEnd; if (selStart === selEnd) { return showToast(L.msg_del_select); } let lineStart = val.lastIndexOf('\n', selStart - 1); lineStart = (lineStart === -1) ? 0 : lineStart + 1; let lineEnd = val.indexOf('\n', selEnd); if (lineEnd === -1) lineEnd = val.length; else lineEnd += 1; const newVal = val.substring(0, lineStart) + val.substring(lineEnd); el.value = newVal; el.setSelectionRange(lineStart, lineStart); highlightLine(el); el.focus(); showToast(L.msg_del_done); }; }; setupSafeControls(areaFolder, m.querySelector('#btn_paste_folder'), m.querySelector('#btn_del_folder')); setupSafeControls(areaFile, m.querySelector('#btn_paste_file'), m.querySelector('#btn_del_file')); m.querySelector('#bl_save').onclick = async () => { const fDir = areaFolder.value.trim(); const fFile = areaFile.value.trim(); gmSet('pk_blacklist_folders', fDir); gmSet('pk_blacklist', fFile); S.updateBlCache(); m.remove(); renderVisible(); }; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.target.tagName !== 'TEXTAREA') { e.preventDefault(); e.stopPropagation(); m.querySelector('#bl_save').click(); } }); m.querySelector('#bl_clear').onclick = async () => { if (areaFolder.value.trim() === '' && areaFile.value.trim() === '') return; if (await showConfirm(L.msg_bl_clear_confirm, L.title_confirm)) { areaFolder.value = ""; areaFile.value = ""; gmSet('pk_blacklist_folders', ""); gmSet('pk_blacklist', ""); S.updateBlCache(); renderVisible(); showToast(L.str_cleanup_done); } }; m.querySelector('#bl_run').onclick = async () => { if (S.movingIds && S.movingIds.size > 0) { showAlert(L.msg_op_blocked_moving); return; } const fDir = areaFolder.value.trim(); const fFile = areaFile.value.trim(); gmSet('pk_blacklist_folders', fDir); gmSet('pk_blacklist', fFile); S.updateBlCache(); const isGlobalSearch = S.path.some(p => p.id === 'virtual_search_root'); if (S.trashMode || S.shareMode || S.offlineMode || S.starredMode || S.recentMode || S.historyMode || S.isFlattened || S.dupMode || S.analyzeMode || isGlobalSearch) { m.remove(); showAlert(L.msg_bl_run_limit); return; } m.remove(); setLoad(true); S.scanning = true; S.scanId = (S.scanId || 0) + 1; const myScanId = S.scanId; if (S.scanAbortController) S.scanAbortController.abort(); S.scanAbortController = new AbortController(); const signal = S.scanAbortController.signal; const parseBigTextAsync = async (text, typeLabel) => { const set = new Set(); if (!text) return set; const len = text.length; let start = 0; let end = 0; let count = 0; let lastYieldTime = performance.now(); updateLoadTxt(`${L.str_analyzing}\n${typeLabel}... 0%`); await sleep(16); while (start < len) { if (!S.scanning || signal.aborted || myScanId !== S.scanId) return set; end = text.indexOf('\n', start); if (end === -1) end = len; const line = text.substring(start, end).trim().toLowerCase(); if (line) set.add(line); start = end + 1; count++; if (count % 2000 === 0) { const now = performance.now(); if (now - lastYieldTime > 16) { const progress = Math.min(100, Math.round((start / len) * 100)); updateLoadTxt(`${L.str_analyzing}\n${typeLabel}... ${progress}% (${set.size})`); await sleep(0); lastYieldTime = performance.now(); } } } return set; }; UI.stopBtn.onclick = () => { S.scanning = false; if (S.scanAbortController) S.scanAbortController.abort(); updateLoadTxt(L.str_stopping); }; try { const folderText = areaFolder.value || ''; const fileText = areaFile.value || ''; if (folderText) S.blFolderSet = await parseBigTextAsync(folderText, L.lbl_type_folder); else S.blFolderSet = new Set(); if (S.scanning && !signal.aborted && myScanId === S.scanId) { if (fileText) S.blSet = await parseBigTextAsync(fileText, L.lbl_type_file); else S.blSet = new Set(); } if (!S.scanning || signal.aborted || myScanId !== S.scanId) { if (myScanId === S.scanId) setLoad(false); return; } if (S.blSet.size === 0 && S.blFolderSet.size === 0) { setLoad(false); showAlert(L.msg_bl_empty); return; } updateLoadTxt(L.str_init_scan); await sleep(50); const foundMatches = []; const rootNodes = [{ id: '', name: 'Root', lineage: [], retryCount: 0 }]; await coreRecursiveEngine(rootNodes, { signal: signal, preferFresh: true, onFile: (f, parent) => { const cleanName = f.name.replace(/[\r\n\v\f\u2028\u2029]+/g, ' ').trim().toLowerCase(); if (S.blSet.has(cleanName)) { f._lineage = parent.lineage || []; foundMatches.push({ item: f, type: 'FILE' }); } }, onFolder: (folder, filesInFolder, nextSubFolders) => { const cleanName = folder.name.replace(/[\r\n\v\f\u2028\u2029]+/g, ' ').trim().toLowerCase(); const isSystemRootFolder = (folder.parent_id === '' || folder.parent_id === 'root') && folder.name === CONF.SYSTEM_FOLDER_NAME; if (S.blFolderSet.has(cleanName) && !isSystemRootFolder) { folder._lineage = (folder.lineage || []).slice(0, -1); foundMatches.push({ item: folder, type: 'FOLDER' }); nextSubFolders.length = 0; } }, onProgress: (st) => { const folderText = `${L.str_scanning} ${st.folders} ${L.unit_folders}`; const statusInfo = ` | ${L.str_hits}: ${foundMatches.length} | ${L.str_speed}: ${st.currentConcurrency}`; updateLoadTxt(folderText + statusInfo); } }); if (!S.scanning || signal.aborted || myScanId !== S.scanId) { if (myScanId === S.scanId) setLoad(false); return; } setLoad(false); if (foundMatches.length === 0) { showAlert(L.msg_blacklist_run_none); return; } showPreviewModal(foundMatches); } catch (e) { if (e.name !== 'AbortError' && myScanId === S.scanId) { setLoad(false); showAlert(`${L.str_scan_error}: ${e.message}`); } } finally { if (myScanId === S.scanId) { setLoad(false); S.scanning = false; S.scanAbortController = null; if (typeof DurationProber !== 'undefined') DurationProber.checkAndRun(); } } }; } function showPreviewModal(matches) { const modalHtml = `

${L.modal_bl_preview.replace('{n}', matches.length)}

${L.col_type}
${L.col_name}
${L.col_path}
`; const m = showModal(modalHtml); const modalEl = m.querySelector('.pk-modal'); Object.assign(modalEl.style, { width: "800px", maxWidth: "90vw", height: "80vh", padding: "0", display: "flex", flexDirection: "column" }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: "24px", right: "24px" }); const listDiv = m.querySelector('#bl_prev_list'); const listIn = m.querySelector('#bl_prev_in'); const chkAll = m.querySelector('#bl_prev_all'); const btnDel = m.querySelector('#bl_prev_del'); const topStat = m.querySelector('#bl_top_stat'); const ROW_HEIGHT = 44; listIn.style.height = `${matches.length * ROW_HEIGHT}px`; const selectedIds = new Set(matches.map(m => m.item.id)); let lastIdx = -1; const updateCount = () => { const c = selectedIds.size; if (topStat) { topStat.innerHTML = L.str_bl_stat .replace('{n}', matches.length) .replace('{m}', `${c}`); } btnDel.disabled = c === 0; btnDel.style.opacity = c === 0 ? 0.5 : 1; btnDel.style.cursor = c === 0 ? 'not-allowed' : 'pointer'; chkAll.checked = c > 0 && c === matches.length; chkAll.indeterminate = c > 0 && c < matches.length; }; updateCount(); let isRenderScheduled = false; const renderPreviewList = () => { const top = listDiv.scrollTop; const h = listDiv.clientHeight || 500; const buffer = 15; const start = Math.max(0, Math.floor(top / ROW_HEIGHT) - buffer); const end = Math.min(matches.length, Math.ceil((top + h) / ROW_HEIGHT) + buffer); listIn.innerHTML = ''; const fragment = document.createDocumentFragment(); for (let i = start; i < end; i++) { const mMatch = matches[i]; const row = document.createElement('div'); row.dataset.id = mMatch.item.id; row.style.cssText = `position:absolute; top:${i * ROW_HEIGHT}px; width:100%; display:grid; grid-template-columns: 40px 50px 1fr 1fr; padding:0; border-bottom:1px dashed var(--pk-bd); font-size:13px; align-items:center; height:${ROW_HEIGHT}px; transition:background 0.1s; cursor:pointer;`; row.onmouseover = () => row.style.backgroundColor = 'var(--pk-hl)'; row.onmouseout = () => row.style.backgroundColor = 'transparent'; const isFolder = mMatch.type === 'FOLDER'; const it = mMatch.item; const mime = (it.mime_type || '').toLowerCase(); const isMedia = mime.startsWith('video/') || mime.startsWith('image/'); if (isFolder && (!it.thumbnail_link || it.thumbnail_link === it.icon_link) && typeof globalCache !== 'undefined') { const scanDeepCover = (targetId, depth) => { if (depth > 5) return null; const raw = globalCache.get(targetId); if (!raw) return null; const files = (raw && !Array.isArray(raw) && raw.items) ? raw.items : raw; if (!files || files.length === 0) return null; const vid = files.find(f => f.mime_type?.startsWith('video/') && f.thumbnail_link); if (vid) return vid.thumbnail_link; const img = files.find(f => f.mime_type?.startsWith('image/') && f.thumbnail_link); if (img) return img.thumbnail_link; const subFolders = files.filter(f => f.kind === 'drive#folder'); for (const sub of subFolders) { if (globalCache.has(sub.id)) { const childThumb = scanDeepCover(sub.id, depth + 1); if (childThumb) return childThumb; continue; } if (sub.thumbnail_link && sub.thumbnail_link !== sub.icon_link && !sub._coverResolved) { return sub.thumbnail_link; } const childThumb = scanDeepCover(sub.id, depth + 1); if (childThumb) return childThumb; } return null; }; const foundThumb = scanDeepCover(it.id, 0); if (foundThumb) { it.thumbnail_link = foundThumb; } } const hasCover = it.thumbnail_link && it.thumbnail_link !== it.icon_link; const usableCover = hasCover && !isPreviewIconFailed(it.thumbnail_link); const fallbackSvg = getIcon(it).replace(/width="\d+"/, 'width="24"').replace(/height="\d+"/, 'height="24"'); const makePreviewImg = (src, fit, radius = '0') => { const safeSrc = String(src || '').replace(/"/g, '"'); return ``; }; let iconHtml = ''; if (!isFolder && isMedia && usableCover) { iconHtml = makePreviewImg(it.thumbnail_link, 'cover', '4px'); const secondFallback = it.icon_link && !isPreviewIconFailed(it.icon_link) ? `${makePreviewImg(it.icon_link, 'contain')}${fallbackSvg}` : fallbackSvg; iconHtml += `${secondFallback}`; } else { const iconSrc = it.icon_link; iconHtml = iconSrc && !isPreviewIconFailed(iconSrc) ? `${makePreviewImg(iconSrc, 'contain')}${fallbackSvg}` : fallbackSvg; } const lineage = mMatch.item._lineage ||[]; const relativePath = lineage.map(x => x.name).join('/'); const homeIcon = ``; row.ondragstart = (e) => { e.preventDefault(); return false; }; let pathHtml = `
${homeIcon}${esc(L.btn_nav_home)}`; if (relativePath) { pathHtml += `/${esc(relativePath)}`; } pathHtml += `
`; const homeGroupTip = `${homeIcon}${esc(L.btn_nav_home)}`; const fullPathTip = `
${homeGroupTip}${relativePath ? '/' + esc(relativePath) : ''}
`; const thumbAttr = (typeof usableCover !== 'undefined' && usableCover) ? `data-pk-thumb="${it.thumbnail_link}"` : ''; const isChecked = selectedIds.has(mMatch.item.id); row.innerHTML = `
${iconHtml}
${esc(mMatch.item.name)}
${pathHtml}
`; bindPreviewIconFallback(row); const chk = row.querySelector('input'); row.onclick = (e) => { const curIdx = i; const id = mMatch.item.id; if (e.target === chk && !e.shiftKey && !e.ctrlKey && !e.metaKey) return; if (e.shiftKey && lastIdx !== -1) { const s = Math.min(lastIdx, curIdx); const eIdx = Math.max(lastIdx, curIdx); if (!e.ctrlKey && !e.metaKey) selectedIds.clear(); for (let k = s; k <= eIdx; k++) selectedIds.add(matches[k].item.id); } else if (e.ctrlKey || e.metaKey) { if (selectedIds.has(id)) selectedIds.delete(id); else selectedIds.add(id); } else { selectedIds.clear(); selectedIds.add(id); } lastIdx = curIdx; updateCount(); renderPreviewList(); }; chk.onchange = (e) => { if(e.target.checked) { selectedIds.add(mMatch.item.id); lastIdx = i; } else selectedIds.delete(mMatch.item.id); updateCount(); renderPreviewList(); }; fragment.appendChild(row); } listIn.appendChild(fragment); }; listDiv.onscroll = () => { if (!isRenderScheduled) { requestAnimationFrame(() => { renderPreviewList(); isRenderScheduled = false; }); isRenderScheduled = true; } }; renderPreviewList(); m.addEventListener('click', (e) => { if (m._blockClick) return; if (e.target.closest('[data-id]') || e.target.closest('button') || e.target.closest('input') || e.target.closest('label')) return; if (selectedIds.size > 0) { selectedIds.clear(); lastIdx = -1; updateCount(); renderPreviewList(); } }); chkAll.onchange = (e) => { const checked = e.target.checked; if(checked) { matches.forEach(m => selectedIds.add(m.item.id)); } else { selectedIds.clear(); } updateCount(); renderPreviewList(); }; const mqBox = document.createElement('div'); mqBox.className = 'pk-bl-mq'; listDiv.appendChild(mqBox); let isDragging = false, isMarquee = false, startX = 0, startY = 0; let blScrollSpeed = 0, blScrollRaf = null, blLastX = 0, blLastY = 0, blLastCtrl = false; listDiv.onmousedown = (e) => { if (e.button !== 0 || e.target.closest('input')) return; const rect = getLogicalRect(listDiv); isDragging = true; isMarquee = false; startX = e.clientX - rect.left; startY = e.clientY - rect.top + listDiv.scrollTop; const rawStartX = e.clientX, rawStartY = e.clientY; const updateMarqueeBox = () => { const curRect = getLogicalRect(listDiv); const curX = blLastX - curRect.left; const maxScrollHeight = listIn.offsetHeight; const curY = Math.max(0, Math.min(maxScrollHeight, blLastY - curRect.top + listDiv.scrollTop)); const top = Math.min(startY, curY); const left = Math.min(startX, curX); const width = Math.abs(curX - startX); const height = Math.abs(curY - startY); mqBox.style.display = 'block'; mqBox.style.top = top + 'px'; mqBox.style.left = left + 'px'; mqBox.style.width = width + 'px'; mqBox.style.height = height + 'px'; const sIdx = Math.floor(top / ROW_HEIGHT); const eIdx = Math.floor((top + height) / ROW_HEIGHT); for (let i = 0; i < matches.length; i++) { const isInside = i >= sIdx && i <= eIdx; if (isInside) { selectedIds.add(matches[i].item.id); } else if (!blLastCtrl) { selectedIds.delete(matches[i].item.id); } } updateCount(); if (!isRenderScheduled) { requestAnimationFrame(() => { renderPreviewList(); isRenderScheduled = false; }); isRenderScheduled = true; } }; const runBlScroll = () => { if (!isMarquee || blScrollSpeed === 0) { blScrollRaf = null; return; } listDiv.scrollTop += blScrollSpeed; updateMarqueeBox(); blScrollRaf = requestAnimationFrame(runBlScroll); }; const onMouseMove = (me) => { if (!isDragging) return; if (!isMarquee) { if (Math.abs(me.clientX - rawStartX) > 5 || Math.abs(me.clientY - rawStartY) > 5) { isMarquee = true; if (!me.ctrlKey && !me.metaKey && !me.shiftKey) { selectedIds.clear(); lastIdx = -1; updateCount(); renderPreviewList(); } } else return; } blLastX = me.clientX; blLastY = me.clientY; blLastCtrl = me.ctrlKey || me.metaKey; const curRect = listDiv.getBoundingClientRect(); if (blLastY > curRect.bottom - 5) blScrollSpeed = Math.min(45, 2 + Math.pow((blLastY - curRect.bottom + 5) / 5, 1.3)); else if (blLastY < curRect.top + 5) blScrollSpeed = -Math.min(45, 2 + Math.pow((curRect.top + 5 - blLastY) / 5, 1.3)); else blScrollSpeed = 0; if (blScrollSpeed !== 0 && !blScrollRaf) blScrollRaf = requestAnimationFrame(runBlScroll); updateMarqueeBox(); }; const onMouseUp = () => { if (isDragging && mqBox.style.display === 'block') { m._blockClick = true; setTimeout(() => m._blockClick = false, 100); } isDragging = false; blScrollSpeed = 0; if (blScrollRaf) { cancelAnimationFrame(blScrollRaf); blScrollRaf = null; } mqBox.style.display = 'none'; window.removeEventListener('mousemove', onMouseMove); window.removeEventListener('mouseup', onMouseUp); }; window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); }; m.querySelector('#bl_prev_cancel').onclick = () => m.remove(); m.tabIndex = 0; setTimeout(() => m.focus(), 50); m.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && (e.key === 'a' || e.key === 'A')) { e.preventDefault(); e.stopPropagation(); matches.forEach(m => selectedIds.add(m.item.id)); updateCount(); renderPreviewList(); } }, true); btnDel.onclick = async () => { if (selectedIds.size === 0) return; const confirmed = await showConfirm(L.warn_del.replace('{n}', selectedIds.size)); if (!confirmed) return; const isHardDelete = m.querySelector('#bl_prev_hard_delete').checked; m.remove(); const allIds = Array.from(selectedIds).filter(id => { const match = matches.find(m => m.item.id === id); if (match && isSystemItem(match.item)) { console.warn(`[Security] Prevented deletion of system folder: ${match.item.name}`); return false; } return true; }); const itemsToDelete = matches.filter(m => allIds.includes(m.item.id)).map(m => m.item); await executeBatchDelete(allIds, { silent: false, explicitItems: itemsToDelete, hardDelete: isHardDelete }); }; } function updateLoadAction(isExitMode = false) { if (!UI.stopBtn) return; UI.stopBtn.removeAttribute('data-pk-tip'); const txtEl = UI.stopBtn.querySelector('span'); if (txtEl) txtEl.textContent = isExitMode ? L.btn_exit_script : L.btn_stop; } function setLoad(b, isInline = false) { S.loading = b; if (b) { if (isInline) { UI.loader.style.display = 'none'; } else { UI.loader.style.display = 'flex'; if (UI.loadTxt) UI.loadTxt.textContent = L.loading_detail; updateLoadAction(false); } } else { UI.loader.style.display = 'none'; updateLoadAction(false); } } function updateLoadTxt(txt) { if (UI.loadTxt) UI.loadTxt.innerText = txt; updateLoadAction(txt === L.str_waiting_token); } let activeLoadId = 0; const computeDuplicateGroups = async (candidates, cfg, isRunningFn) => { const groups =[]; const assigned = new Set(); const strictness = gmGet('pk_dup_strictness', 'strict'); const sizeRatioLimit = (strictness === 'loose') ? 0.10 : 0.05; const activeStages = ['hash', ...(cfg.video ? ['video'] : []), 'name']; const stageSpan = 100 / activeStages.length; const setDupProgress = (pct) => { const safePct = Math.max(0, Math.min(100, Math.round(pct))); updateLoadTxt(L.loading_dup.replace('{p}', safePct)); }; let lastDupPct = 0; const setStageProgress = (stageKey, processed, total, stageStartRatio = 0, stageEndRatio = 1) => { const stageIndex = activeStages.indexOf(stageKey); if (stageIndex < 0) return; const start = stageIndex * stageSpan; const end = (stageIndex + 1) * stageSpan; const safeStartRatio = Math.max(0, Math.min(1, stageStartRatio)); const safeEndRatio = Math.max(safeStartRatio, Math.min(1, stageEndRatio)); let innerRatio = safeEndRatio; if (total > 0) { const raw = Math.max(0, Math.min(1, processed / total)); innerRatio = safeStartRatio + ((safeEndRatio - safeStartRatio) * raw); } const nextPct = start + ((end - start) * innerRatio); if (nextPct >= lastDupPct) { lastDupPct = nextPct; setDupProgress(nextPct); } }; setDupProgress(0); if (isRunningFn()) { const hashMap = new Map(); let hashProcessed = 0; const hashBuildTotal = candidates.length; for (const item of candidates) { const hash = item.gcid || item.md5_checksum || item.hash; const key = hash ? `${hash}|${item.size}` : null; if (key) { if (!hashMap.has(key)) hashMap.set(key,[]); hashMap.get(key).push(item); } hashProcessed++; if (hashProcessed % 500 === 0) { if (!isRunningFn()) break; setStageProgress('hash', hashProcessed, hashBuildTotal, 0, 0.7); await sleep(0); } } if (!isRunningFn()) return groups; const hashEntries = Array.from(hashMap.entries()); for (let i = 0; i < hashEntries.length; i++) { const [key, items] = hashEntries[i]; if (items.length > 1) { const ids = items.map(i => i.id); ids.forEach(id => { assigned.add(id); S.dupReasons.set(id, L.tag_hash); }); groups.push({ ids: ids, type: L.tag_hash }); } if ((i + 1) % 200 === 0) { if (!isRunningFn()) break; setStageProgress('hash', i + 1, hashEntries.length, 0.7, 1); await sleep(0); } } if (!isRunningFn()) return groups; setStageProgress('hash', 1, 1, 1, 1); } if (isRunningFn() && cfg.video) { const simCandidates = candidates.filter(i => i.mime_type.startsWith('video') && !assigned.has(i.id)); const validVideos = simCandidates.filter(item => (parseFloat(item.params?.duration || 0) > 0)); validVideos.sort((a, b) => parseFloat(a.params?.duration || 0) - parseFloat(b.params?.duration || 0)); let processedCount = 0; const totalCount = validVideos.length; if (totalCount <= 0) { setStageProgress('video', 0, 0); } for (let i = 0; i < totalCount; i++) { processedCount++; if (processedCount % 500 === 0) { if (!isRunningFn()) break; setStageProgress('video', processedCount, totalCount); await sleep(0); } if (assigned.has(validVideos[i].id)) continue; const root = validVideos[i]; const rootDur = parseFloat(root.params?.duration || 0); const rootSize = parseInt(root.size || 0); const groupItems = [root]; for (let j = i + 1; j < totalCount; j++) { const target = validVideos[j]; if (assigned.has(target.id)) continue; const durThreshold = (strictness === 'loose') ? 3.0 : 2.0; const targetDur = parseFloat(target.params?.duration || 0); const durDiff = Math.abs(targetDur - rootDur); if (durDiff > durThreshold) break; const targetSize = parseInt(target.size || 0); if (rootSize > 0 && targetSize > 0) { const sizeDiff = Math.abs(targetSize - rootSize); const maxBase = Math.max(targetSize, rootSize); const ratio = sizeDiff / maxBase; if (strictness === 'loose' || ratio <= 0.10) { groupItems.push(target); } } } if (groupItems.length > 1) { const ids = groupItems.map(x => x.id); ids.forEach(id => { assigned.add(id); S.dupReasons.set(id, L.tag_sim); }); groups.push({ ids: ids, type: L.tag_sim }); } } if (!isRunningFn()) return groups; setStageProgress('video', totalCount, totalCount); } if (isRunningFn()) { const cleanNameAd = (oldName) => { let cleanName = oldName; cleanName = cleanName.replace(/^【[^】]+】 *[-_.]? */, ''); cleanName = cleanName.replace(/^[a-z0-9-]+[.](?:com|net|org|cc|xyz|vip|top|la) +/i, ''); const adKw = "(?:[.]com|[.]net|[.]org|[.]cc|[.]xyz|[.]vip|[.]top|[.]la|2048|www[.])"; const atRegex = new RegExp('^.*?' + adKw + '.*?(?:@|--+|_\\\\s)', 'i'); cleanName = cleanName.replace(atRegex, ''); const hyphenRegex = new RegExp('^[a-z0-9.-]+' + adKw + '-', 'i'); cleanName = cleanName.replace(hyphenRegex, ''); cleanName = cleanName.replace(/^(?:精品加群|福利合集)[0-9]+[-_]+ */, ''); cleanName = cleanName.replace(/^[-_. ,,::;;\p{Extended_Pictographic}]+/u, ''); const pairs = [['【','】'], ['[',']'], ['《','》'],['<','>'], ['(',')'],['(',')'], ['{','}']]; pairs.forEach(([L_char, R_char]) => { const idxR_Fix = cleanName.indexOf(R_char); const idxL_Check = cleanName.indexOf(L_char); if (idxR_Fix > 0 && idxR_Fix <= 10 && (idxL_Check === -1 || idxL_Check > idxR_Fix)) { cleanName = L_char + cleanName; } const chars = cleanName.split(''); const stack =[]; const toRemove = new Set(); for (let i = 0; i < chars.length; i++) { const c = chars[i]; if (c === L_char) { stack.push(i); } else if (c === R_char) { if (stack.length > 0) stack.pop(); else toRemove.add(i); } } stack.forEach(i => toRemove.add(i)); if (toRemove.size > 0) { cleanName = chars.filter((_, i) => !toRemove.has(i)).join(''); } }); const quoteCount = (cleanName.match(/'/g) || []).length; if (quoteCount % 2 !== 0) cleanName = cleanName.replace(/'/, ''); let result = cleanName.trim(); const lastDot = result.lastIndexOf('.'); if (lastDot > 0) result = result.substring(0, lastDot); result = result .replace(/\s*[-_ ..。]*\s*(?:\(\s*\d+\s*\)|(\s*\d+\s*)|\[\s*\d+\s*\]|【\s*\d+\s*】)\s*$/u, '') .replace(/\s*(?:[-_ ..。]*\s*)?(?:副本|复制|拷贝|拷貝|コピー|複製|복사본|사본|복사)\s*(?:\d+|\(\s*\d+\s*\)|(\s*\d+\s*)|\[\s*\d+\s*\]|【\s*\d+\s*】)?\s*$/u, '') .replace(/\s*[-_ ..。]+\s*(?:copy|duplicate|dup|salinan|salin|duplikat)\s*(?:\d+|\(\s*\d+\s*\)|(\s*\d+\s*)|\[\s*\d+\s*\]|【\s*\d+\s*】)?\s*$/i, '') .trim(); let finalResult = result ? result.toLowerCase() : oldName.replace(/\.[^/.]+$/, "").toLowerCase().trim(); if (strictness === 'loose') { finalResult = finalResult.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, ''); } return finalResult; }; const remainingItems = candidates.filter(i => !assigned.has(i.id)); const getTypeGroup = (mime) => { if (!mime) return 'other'; if (mime.startsWith('video')) return 'video'; if (mime.startsWith('image')) return 'image'; return 'other'; }; const typeNameMap = new Map(); let nameBuildProcessed = 0; const nameBuildTotal = remainingItems.length; for (const item of remainingItems) { const tGroup = getTypeGroup(item.mime_type); const cleaned = cleanNameAd(item.name); if (cleaned) { const key = tGroup + '|' + cleaned; if (!typeNameMap.has(key)) typeNameMap.set(key,[]); typeNameMap.get(key).push(item); } nameBuildProcessed++; if (nameBuildProcessed % 500 === 0) { if (!isRunningFn()) break; setStageProgress('name', nameBuildProcessed, nameBuildTotal, 0, 0.7); await sleep(0); } } if (!isRunningFn()) return groups; const typeEntries = Array.from(typeNameMap.entries()); for (let i = 0; i < typeEntries.length; i++) { const [key, items] = typeEntries[i]; if (items.length > 1) { const sortedForAlgo = [...items].sort((a, b) => parseInt(a.size || 0) - parseInt(b.size || 0)); let currentGroup = [sortedForAlgo[0]]; const tempGroups =[]; for (let j = 1; j < sortedForAlgo.length; j++) { const target = sortedForAlgo[j]; const root = currentGroup[0]; const rootSize = parseInt(root.size || 0); const targetSize = parseInt(target.size || 0); let isMatch = false; if (rootSize === 0 && targetSize === 0) { isMatch = true; } else if (rootSize > 0 && targetSize > 0) { const sizeDiff = Math.abs(targetSize - rootSize); const maxBase = Math.max(targetSize, rootSize); if ((sizeDiff / maxBase) <= sizeRatioLimit) { isMatch = true; } } if (isMatch) currentGroup.push(target); else { if (currentGroup.length > 1) tempGroups.push(currentGroup); currentGroup = [target]; } } if (currentGroup.length > 1) tempGroups.push(currentGroup); tempGroups.forEach(grp => { const ids = grp.map(i => i.id); ids.forEach(id => { assigned.add(id); S.dupReasons.set(id, L.tag_name); }); groups.push({ ids: ids, type: L.tag_name }); }); } if ((i + 1) % 200 === 0) { if (!isRunningFn()) break; setStageProgress('name', i + 1, typeEntries.length, 0.7, 1); await sleep(0); } } if (!isRunningFn()) return groups; setStageProgress('name', 1, 1, 1, 1); } setDupProgress(100); return groups; }; async function load(isHistoryNav = false, forceUpdate = false) { if (S.scanning) return; if (S.shareParseMode) { setLoad(false); if (UI.btnExport) UI.btnExport.style.display = 'none'; renderCrumb(); if (typeof applyResolvedSortState === 'function') applyResolvedSortState(); if (S.shareParseListActive) { syncShareParseMirror(); renderList(); } else { renderShareParsePanel(); } syncShareParseInsightFilterBar(); updateShareParseInsightButtons(); updateStat(); return; } const snapshot = { trash: S.trashMode, share: S.shareMode, starred: S.starredMode, recent: S.recentMode, pathId: S.path[S.path.length - 1]?.id || '' }; const cur = S.path[S.path.length - 1]; const folderId = cur.id || 'root'; if (S.clipType === 'move' && S.movingIds.size > 0 && folderId === S.movingSourceId) { forceUpdate = true; } let realCacheKey = S.getRealCacheKey(folderId); const isAnalyzeSub = S.path.some(p => p.id === 'analyze_root'); if (isAnalyzeSub) { S.analyzeMode = true; if (S.strictFolderFirstSnapshot !== null) { syncFolderFirstFromGlobal(); } if (typeof applyResolvedViewMode === 'function') applyResolvedViewMode(S.analyzeSimGroups ? 'folder_dup' : 'folder_insight'); } if (folderId === 'analyze_root') { renderCrumb(); if (UI.scan) UI.scan.style.display = 'none'; S.items = [...(S.analyzeResultItems || [])]; S.itemMap.clear(); S.items.forEach(i => S.itemMap.set(i.id, i)); setLoad(false); refresh(); updateStat(); return; } const recentRootNextPageIntent = !!(S.recentLoadNextPageOnly && S.recentMode && S.path.length === 1 && cur && cur.id === 'recent_root' && realCacheKey === 'recent_root'); if (!recentRootNextPageIntent && (globalDirtyFolders.has(folderId) || (folderId === 'root' && globalDirtyFolders.has('')))) { forceUpdate = true; globalDirtyFolders.delete(folderId); globalDirtyFolders.delete(''); globalDirtyFolders.delete('root'); } if (!recentRootNextPageIntent) S.clearSelection(); if (typeof UI !== 'undefined' && UI.vp && !forceUpdate && !recentRootNextPageIntent) { UI.vp.scrollTop = 0; } if (UI.win) UI.win.setAttribute('data-cur-pid', folderId); activeLoadId++; const currentId = activeLoadId; let nextToken = null; const fetchedIds = new Set(); let isResuming = false; S.pagingLoading = false; if (S.abortController) { S.abortController.abort(); } S.abortController = new AbortController(); const signal = S.abortController.signal; const isInVirtualSearch = S.path.some(p => p.id === 'virtual_search_root'); if (!isInVirtualSearch) { S.search = ''; if (UI.searchInput) { UI.searchInput.value = ''; if (UI.searchClear) UI.searchClear.style.display = 'none'; } } if (cur.id === 'virtual_search_root') { if (typeof applyResolvedViewMode === 'function') applyResolvedViewMode('global_search'); renderCrumb(); if (UI.scan) UI.scan.style.display = 'none'; setLoad(false); refresh(); return; } const isInVirtualStack = S.path.some(p => p.id === 'virtual_search_root'); if (isInVirtualStack) { if (typeof applyResolvedViewMode === 'function') applyResolvedViewMode('global_search'); if (UI.chkGlobal) UI.chkGlobal.checked = true; } if (UI.viewSwitch) { const usableViewSwitch = canUseGridView(); UI.viewSwitch.style.display = usableViewSwitch ? 'inline-flex' : 'none'; UI.viewSwitch.style.visibility = usableViewSwitch ? '' : 'hidden'; UI.viewSwitch.style.pointerEvents = usableViewSwitch ? '' : 'none'; } UI.scan.style.display = (S.trashMode || S.shareMode || S.offlineMode || S.historyMode) ? 'none' : 'flex'; UI.btnExit.style.display = 'none'; if (UI.dupTools) UI.dupTools.style.display = 'none'; if (UI.dupFilters) UI.dupFilters.style.display = 'none'; if (UI.offTools) UI.offTools.style.display = S.offlineMode ? 'flex' : 'none'; if (UI.upTools) UI.upTools.style.display = S.uploadMode ? 'flex' : 'none'; if (UI.upTip) UI.upTip.style.display = S.uploadMode ? 'flex' : 'none'; if (UI.crumb) UI.crumb.style.display = ''; if (UI.searchInput && UI.searchInput.parentNode) { UI.searchInput.parentNode.style.display = S.trashMode ? 'none' : 'flex'; } if (UI.cntFolderFirst) { UI.cntFolderFirst.style.display = (S.trashMode || S.dupMode) ? 'none' : 'flex'; } if (UI.btnAnalyze) { UI.btnAnalyze.style.display = (S.trashMode || S.shareMode || S.offlineMode || S.historyMode) ? 'none' : 'flex'; } if (UI.lblGlobal) { UI.lblGlobal.style.display = (S.trashMode || S.shareMode || S.starredMode || S.recentMode || S.historyMode || S.offlineMode || S.uploadMode || S.isFlattened || S.dupMode || S.analyzeMode) ? 'none' : 'flex'; if (S.trashMode && UI.chkGlobal) { UI.chkGlobal.checked = false; } if (isInVirtualSearch) { UI.chkGlobal.checked = true; } } S.dupMode = false; S.lastSelIdx = -1; if (!isAnalyzeSub) { if (S.analyzeMode && UI.chkSearchPath) UI.chkSearchPath.checked = false; S.analyzeMode = false; } if (UI.btnNewFolder) { UI.btnNewFolder.style.display = (S.trashMode || S.shareMode || S.offlineMode || S.uploadMode || S.historyMode || S.starredMode || S.recentMode || S.isFlattened || S.dupMode || S.analyzeMode) ? 'none' : 'flex'; } S.isFlattened = false; renderCrumb(); if (folderId === 'root' && !S.preloaded) { if (typeof globalCache !== 'undefined' && globalCache.has('root')) { S.cache.set('root', globalCache.get('root')); S.preloaded = true; } else if (S.preLoadPromise) { let preLoadSuccess = false; try { const TIMEOUT_LIMIT = isTurbo ? 1000 : 200; const raceResult = await Promise.race([ S.preLoadPromise, new Promise(r => setTimeout(() => r('TIMEOUT'), TIMEOUT_LIMIT)) ]); if (globalCache.has('root') || raceResult !== 'TIMEOUT') { S.cache.set('root', globalCache.get('root')); S.preloaded = true; preLoadSuccess = true; } } catch(e) {} if (!preLoadSuccess) { forceUpdate = true; } } else { forceUpdate = true; } } const syncStars = () => { for (let k = 0; k < S.items.length; k++) { const it = S.items[k]; if (it.starred || (it.tags && it.tags.some(t => t.name === 'STAR'))) { S.starredSet.add(it.id); } } }; if (!forceUpdate) { let cachedData = null; if (S.cache.has(realCacheKey)) { cachedData = S.cache.get(realCacheKey); } else if (typeof globalCache !== 'undefined') { let lookupKey = realCacheKey; if (realCacheKey === 'root' && globalCache.has('')) lookupKey = ''; if (globalCache.has(lookupKey)) { cachedData = globalCache.get(lookupKey); S.cache.set(realCacheKey, cachedData); } } if (cachedData) { if (cachedData && !Array.isArray(cachedData) && cachedData.items) { S.items = [...cachedData.items]; nextToken = cachedData.nextToken; isResuming = !!nextToken; if (isResuming) S.items.forEach(it => fetchedIds.add(it.id)); } else { S.items = Array.isArray(cachedData) ? [...cachedData] : []; nextToken = null; } if (S.analyzeMode && S.analyzeMap) { S.items.forEach(item => { if (item.kind === 'drive#folder' && S.analyzeMap.has(item.id)) { item.size = S.analyzeMap.get(item.id).size.toString(); } }); } S.itemMap.clear(); for (let k = 0; k < S.items.length; k++) { S.itemMap.set(S.items[k].id, S.items[k]); } syncStars(); S.pagingLoading = !!nextToken; refresh(); updateStat(); if (S.offlineMode) { const session = globalCache.get('offline_session'); if (session && session.nextToken && !session.completed) { isResuming = true; S.items.forEach(it => fetchedIds.add(it.id)); } else { setLoad(false); return; } } else if (S.historyMode) { const session = globalCache.get('history_session'); if (session && Array.isArray(session.targetIds) && !session.completed && (session.cursor || 0) < session.targetIds.length) { isResuming = true; nextToken = `history:${session.cursor || 0}`; S.items.forEach(it => fetchedIds.add(it.id)); } else if (!nextToken) { setLoad(false); return; } } else if (S.recentMode && realCacheKey === 'recent_root') { const session = globalCache.get('recent_session'); const sessionToken = session && session.nextToken && !session.completed ? session.nextToken : null; if (!nextToken && sessionToken) nextToken = sessionToken; if (nextToken) { isResuming = true; S.items.forEach(it => fetchedIds.add(it.id)); globalCache.set('recent_session', { phase: 'active', nextToken: nextToken || null, completed: false, count: S.items.length }); } else { setLoad(false); const visibleSubFolders = S.items.filter(f => f.kind === 'drive#folder'); for (let i = visibleSubFolders.length - 1; i >= 0; i--) { const sub = visibleSubFolders[i]; if (!scannedFolderIds.has(sub.id) && !globalCache.has(sub.id)) { backgroundQueue.push({ id: sub.id, name: sub.name, retryCount: 0 }); scannedFolderIds.add(sub.id); } } runBackgroundCrawler(); return; } } else if (!nextToken) { setLoad(false); if (window.pkSmartRefreshTrigger && !(S.recentMode && realCacheKey === 'recent_root')) { window.pkSmartRefreshTrigger(true); } const visibleSubFolders = S.items.filter(f => f.kind === 'drive#folder'); for (let i = visibleSubFolders.length - 1; i >= 0; i--) { const sub = visibleSubFolders[i]; if (!scannedFolderIds.has(sub.id) && !globalCache.has(sub.id)) { backgroundQueue.push({ id: sub.id, name: sub.name, retryCount: 0 }); scannedFolderIds.add(sub.id); } } runBackgroundCrawler(); return; } else { if (S.recentMode && realCacheKey === 'recent_root') { globalCache.set('recent_session', { phase: 'active', nextToken: nextToken || null, completed: false, count: S.items.length }); } } } } if (!nextToken && !isResuming) { if (S.recentMode && realCacheKey === 'recent_root') { globalCache.set('recent_session', { phase: 'active', nextToken: null, completed: false, count: 0 }); } const keepHistoryVisual = !!(S.historyMode && Array.isArray(S.items) && S.items.length > 0); if (!keepHistoryVisual) { S.items = []; S.display = []; S.itemMap.clear(); refresh(); } setLoad(true, true); updateLoadTxt(L.loading_detail); } isGUISensitive = false; const currentHeaders = getHeaders(); if (!currentHeaders.Authorization || currentHeaders.Authorization.length < 10) { if (!isResuming) { setLoad(true); updateLoadTxt(L.str_waiting_token); } if (typeof window.pkEnterAuthRecoveryWindow === 'function') window.pkEnterAuthRecoveryWindow('load-missing-token', 5000); const initialWait = (typeof window.pkIsAuthRecoveryActive === 'function' && window.pkIsAuthRecoveryActive()) ? 6500 : 5000; let isAuthReady = await waitForAuth(initialWait); if (!isAuthReady) { console.warn("PikPak Master: Auth token wait timeout. Entering recovery recheck before logout."); if (typeof window.pkEnterAuthRecoveryWindow === 'function') window.pkEnterAuthRecoveryWindow('load-missing-token-recheck', 5000); await sleep(1200); isAuthReady = await waitForAuth(5000); } if (!isAuthReady) { console.warn("PikPak Master: Auth token still missing after recovery recheck."); const didLogout = await confirmedLogout('load-missing-token-final', 5000, 5200); if (didLogout) return; setLoad(false); if (typeof window.pkMarkAuthRecovered === 'function') window.pkMarkAuthRecovered(); return; } if (typeof window.pkMarkAuthRecovered === 'function') window.pkMarkAuthRecovered(); } if (!isResuming && el) { el.focus(); } UI.stopBtn.onclick = () => { const isAuthSyncMode = (typeof window.pkIsAuthRecoveryActive === 'function' && window.pkIsAuthRecoveryActive()) || (UI.loadTxt && UI.loadTxt.innerText === L.str_waiting_token); if (isAuthSyncMode) { handleClose(); window.location.href = `${window.location.origin}/login`; return; } S.abortController.abort(); if (S.loading && !S.isFlattened && !S.dupMode && !S.analyzeMode && !S.shareMode && !S.offlineMode && S.path.length > 1) { S.path.pop(); renderCrumb(); const parentNode = S.path[S.path.length - 1]; const parentKey = S.getRealCacheKey(parentNode.id); const cached = (typeof globalCache !== 'undefined') ? globalCache.get(parentKey) : null; if (cached) { S.items = Array.isArray(cached) ? [...cached] : (cached.items || []); ensureItemMap(); refresh(); } } S.pagingLoading = false; updateStat(); setLoad(false); isGUISensitive = false; }; try { let pageCount = 0; const limit = 500; let totalItemsLoaded = S.items.length; do { if (currentId !== activeLoadId) return; if (signal.aborted) throw new DOMException('Aborted', 'AbortError'); const isStarredRoot = S.starredMode && S.path.length === 1; let data = {}; if (S.offlineMode) { if (pageCount > 0) break; if (!isResuming) { S.items = []; S.itemMap.clear(); fetchedIds.clear(); totalItemsLoaded = 0; } let session = isResuming ? globalCache.get('offline_session') : { phase: 'active', nextToken: null, completed: false }; if (!isResuming) globalCache.set('offline_session', session); const handleBatch = (batchTasks, nextToken, currentPhase) => { if (signal.aborted || currentId !== activeLoadId) return; const uniqueTasks = batchTasks.filter(t => !fetchedIds.has(t.id) && !S.itemMap.has(t.id)); if (uniqueTasks.length === 0) return; const newItems = uniqueTasks.map(t => { const ref = t.reference_resource || {}; return { id: t.id, kind: 'drive#task', name: ref.name || t.name || t.file_name || 'Untitled Task', size: t.file_size, phase: t.phase, progress: parseInt(t.progress || 0), message: t.message, icon_link: t.icon_link, thumbnail_link: ref.thumbnail_link ? ref.thumbnail_link : t.icon_link, created_time: t.created_time, modified_time: t.updated_time || ref.modified_time || '', file_id: t.file_id || '', source_url: (t.params && t.params.url) ? t.params.url : '', params: Object.assign({}, t.params || {}, ref.params || {}), mime_type: ref.mime_type || '', starred: !!(ref.starred || (ref.tags && ref.tags.some(tg => tg.name === 'STAR'))) }; }); S.items.push(...newItems); newItems.forEach(f => { fetchedIds.add(f.id); S.itemMap.set(f.id, f); }); totalItemsLoaded = S.items.length; session.phase = currentPhase; session.nextToken = nextToken; session.completed = false; globalCache.set('offline_session', session); globalCache.set('offline_root', { items: [...S.items], fetchedIds: fetchedIds }); S.items.sort((a, b) => new Date(b.created_time || 0) - new Date(a.created_time || 0)); S.pagingLoading = !!nextToken; if (UI.loader.style.display !== 'none') setLoad(false); refresh(); updateStat(); }; await apiTaskList(500, handleBatch, session, signal); if (signal.aborted || currentId !== activeLoadId) return; const finalSession = { phase: 'done', nextToken: null, completed: true }; globalCache.set('offline_session', finalSession); data = { files: [], next_page_token: null }; break; } else if (S.historyMode && S.path.length === 1) { if (pageCount > 0) break; const filters = encodeURIComponent(JSON.stringify({ type: { in: 'TYPE_PLAY' } })); const limit = 50; const sourceSig = 'official_stream'; const CONCURRENCY = 6; const historyRefreshFirstPageOnly = S.historyRefreshFirstPageOnly === true; if (historyRefreshFirstPageOnly) S.historyRefreshFirstPageOnly = false; const fetchHistoryPage = async (pageToken) => { const url = `https://api-drive.mypikpak.com/drive/v1/events?thumbnail_size=SIZE_SMALL&limit=${limit}&filters=${filters}${pageToken ? `&page_token=${encodeURIComponent(pageToken)}` : ''}&_t=${Date.now()}`; const res = await fetch(url, { headers: getHeaders(), signal }); if (!res.ok) throw new Error(`History API ${res.status}`); const data = await res.json(); const events = Array.isArray(data.events) ? data.events : (Array.isArray(data.data) ? data.data : []); return { events, nextToken: data.next_page_token || data.nextPageToken || '' }; }; const parseHistoryEvents = (events) => { const out = []; (Array.isArray(events) ? events : []).forEach(ev => { const ref = ev.reference_resource || {}; const params = ev.params || {}; const refParams = ref.params || {}; const id = String(ev.file_id || ref.id || ''); if (!id) return; const officialEventId = String(ev.id || ev.event_id || ''); if (officialEventId && S._historyDeletedEventIds && S._historyDeletedEventIds.has(officialEventId)) return; const playSeconds = parseOfficialPlaySeconds(params.play_seconds); const playDuration = parseOfficialPlaySeconds(params.play_duration || refParams.duration); const updatedTime = Date.parse(ev.updated_time || ev.create_time || '') || Date.now(); if (playSeconds > 1 || updatedTime > 0) { const mime = ref.mime_type || ''; const isFolder = (ref.kind === 'drive#folder') || mime === 'application/x-directory'; const rawF = (ref && (ref.name || ref.thumbnail_link || ref.icon_link || ref.mime_type)) ? { id, kind: isFolder ? 'drive#folder' : (ref.kind || 'drive#file'), name: ref.name || ev.file_name || ev.name || id, size: Number(ref.size || ref.file_size || ev.file_size || 0), thumbnail_link: ref.thumbnail_link || ref.icon_link || '', icon_link: ref.icon_link || '', web_content_link: ref.web_content_link || '', created_time: ref.created_time || ev.create_time || '', modified_time: ref.modified_time || ref.updated_time || ev.updated_time || ev.create_time || '', mime_type: mime, parent_id: ref.parent_id || '', params: Object.assign({}, refParams, params), starred: !!(ref.starred || (ref.tags && ref.tags.some(tg => tg.name === 'STAR'))), trashed: !!ref.trashed } : null; out.push({ id, t: playSeconds, d: playDuration || 0, ts: updatedTime, source: 'official', officialEventId, rawF }); } }); return out; }; const fetchDetail = async (id) => { try { const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/files/${id}?thumbnail_size=SIZE_MEDIUM`, { headers: getHeaders(), signal }); if (!res.ok) return null; const f = await res.json(); if (f && !f.trashed) return f; return null; } catch (e) { return null; } }; const targetIdSet = new Set(); const isValidHistorySnapshotItem = (f) => { if (!f || !f.id) return false; return !!( f._history_event_id || f.officialEventId || f.eventId || Number(f._history_ts || 0) > 0 || Number(f._history_progress || 0) > 0 || Number(f._history_duration || 0) > 0 ); }; const normalizeHistorySnapshotItems = (list) => { const out = []; const seen = new Set(); (Array.isArray(list) ? list : []).forEach(f => { if (!isValidHistorySnapshotItem(f)) return; const id = String(f.id || ''); const eventId = String(f._history_event_id || f.officialEventId || f.eventId || ''); if (!id || seen.has(id) || (eventId && S._historyDeletedEventIds && S._historyDeletedEventIds.has(eventId))) return; seen.add(id); out.push(f); }); out.sort((a, b) => Number(b._history_ts || 0) - Number(a._history_ts || 0)); return out; }; const extractHistorySnapshotItems = (snap) => { if (!snap) return []; if (Array.isArray(snap)) return snap; if (Array.isArray(snap.items)) return snap.items; return []; }; const readHistorySnapshotItems = () => { if (forceUpdate) return []; const candidates = []; if (typeof globalCache !== 'undefined') { candidates.push(globalCache.get('history_root')); } candidates.push(S.cache.get('history_root')); for (const snap of candidates) { const items = normalizeHistorySnapshotItems(extractHistorySnapshotItems(snap)); if (items.length > 0) return items; } return []; }; const currentHistoryItems = normalizeHistorySnapshotItems(S.items); let historyList = currentHistoryItems.length > 0 ? currentHistoryItems : readHistorySnapshotItems(); const existingIds = new Set(); historyList.forEach(f => { if (!f || !f.id) return; const id = String(f.id); existingIds.add(id); targetIdSet.add(id); }); let historySession = { sourceSig, targetIds: historyList.map(f => String(f.id || '')).filter(Boolean), cursor: historyList.length, nextToken: '', completed: false, updatedAt: Date.now() }; const syncHistoryCache = () => { historyList = normalizeHistorySnapshotItems(historyList); const snapshot = { items: [...historyList], nextToken: historySession.completed ? null : `history:${historySession.cursor || 0}`, sourceSig }; S.cache.set('history_root', snapshot); if (typeof globalCache !== 'undefined') { globalCache.set('history_root', snapshot); globalCache.set('history_session', { ...historySession, targetIds: [...historySession.targetIds] }); } }; const syncHistoryUI = () => { if (signal.aborted || currentId !== activeLoadId) return; historyList = normalizeHistorySnapshotItems(historyList); S.items = [...historyList]; S.itemMap.clear(); S.items.forEach(f => S.itemMap.set(f.id, f)); if (UI.loader.style.display !== 'none') setLoad(false); refresh(); updateStat(); }; const appendHistoryDetail = (rawF, meta) => { if (!rawF || !rawF.id) return false; const eventId = String((meta && (meta.officialEventId || meta.eventId)) || ''); if (eventId && S._historyDeletedEventIds && S._historyDeletedEventIds.has(eventId)) return false; rawF._pkSkipDurationProbe = true; const f = minifyFile(rawF); const local = meta || {}; f._history_progress = local.t || 0; const cloudDur = f.params?.duration || 0; f._history_duration = local.d || cloudDur || 0; f._history_ts = local.ts || 0; f._history_event_id = local.officialEventId || local.eventId || ''; const idx = historyList.findIndex(x => x && x.id === f.id); if (idx !== -1) { historyList[idx] = f; return false; } existingIds.add(f.id); targetIdSet.add(f.id); historyList.push(f); return true; }; let lastHistoryUiSync = 0; const loadHistoryDetails = async (items) => { const needsHistoryDetailFallback = (item) => { const f = item && item.rawF; return !(f && f.id && f.name && (f.mime_type || f.kind)); }; for (let i = 0; i < items.length; i += CONCURRENCY) { if (signal.aborted || currentId !== activeLoadId) return; const chunk = items.slice(i, i + CONCURRENCY); const results = await Promise.all(chunk.map(item => needsHistoryDetailFallback(item) ? fetchDetail(item.id) : Promise.resolve(item.rawF))); let appended = 0; results.forEach((rawF, idx) => { if (appendHistoryDetail(rawF, chunk[idx])) appended++; }); historySession.cursor += chunk.length; historySession.updatedAt = Date.now(); syncHistoryCache(); const now = performance.now(); const isPageTail = i + CONCURRENCY >= items.length; const shouldSyncHistoryUI = historyList.length <= CONCURRENCY || (appended > 0 && (isPageTail || now - lastHistoryUiSync > 280)); if (shouldSyncHistoryUI) { lastHistoryUiSync = now; syncHistoryUI(); } await sleep(10); } }; if (historyList.length > 0) { syncHistoryCache(); syncHistoryUI(); } let pageToken = ''; let safe = 5000; while (safe-- > 0) { if (signal.aborted || currentId !== activeLoadId) return; let pageData = null; try { pageData = await fetchHistoryPage(pageToken); } catch (e) { console.warn('[OfficialHistory] Fetch page failed:', e); break; } if (signal.aborted || currentId !== activeLoadId) return; const pageItems = parseHistoryEvents(pageData.events); const newItems = []; pageItems.forEach(item => { const id = String(item.id || ''); if (!id || existingIds.has(id) || targetIdSet.has(id)) return; targetIdSet.add(id); historySession.targetIds.push(id); newItems.push(item); }); historySession.nextToken = pageData.nextToken || ''; historySession.completed = !historySession.nextToken; S.pagingLoading = !!historySession.nextToken; historySession.updatedAt = Date.now(); syncHistoryCache(); if (newItems.length > 0) { await loadHistoryDetails(newItems); } else if (historyList.length > 0) { syncHistoryUI(); } if (!historySession.nextToken) break; pageToken = historySession.nextToken; await sleep(30); } if (signal.aborted || currentId !== activeLoadId) return; historySession.completed = !historySession.nextToken; S.pagingLoading = false; historySession.updatedAt = Date.now(); syncHistoryCache(); syncHistoryUI(); data = { files: [], next_page_token: null }; break; } else if (S.recentMode && S.path.length === 1 && cur && cur.id === 'recent_root') { const recentPageToken = nextToken || ''; const recentLimit = recentPageToken ? 50 : 100; const url = `https://api-drive.mypikpak.com/drive/v1/events?thumbnail_size=SIZE_MEDIUM&limit=${recentLimit}${recentPageToken ? `&page_token=${encodeURIComponent(recentPageToken)}` : ''}&_t=${Date.now()}`; const res = await fetch(url, { headers: getHeaders(), signal: signal }); if (!res.ok) throw new Error("Recent API " + res.status); const json = await res.json(); const events = Array.isArray(json.events) ? json.events : (Array.isArray(json.data) ? json.data : []); const latestByFileId = new Map(); events.forEach(ev => { const ref = ev && ev.reference_resource; if (!ref || typeof ref !== 'object') return; const id = String(ev.file_id || ref.id || ''); if (!id) return; const mime = ref.mime_type || ''; const isFolder = (ref.kind === 'drive#folder') || (mime === 'application/x-directory'); const isFile = (ref.kind === 'drive#file') || (!isFolder && !!mime); if (!isFolder && !isFile) return; const eventTime = ev.created_time || ev.create_time || ''; const eventTs = Date.parse(eventTime || '') || 0; const prev = latestByFileId.get(id); if (prev && prev.eventTs >= eventTs) return; latestByFileId.set(id, { eventTs, item: { id, kind: isFolder ? 'drive#folder' : 'drive#file', name: ref.name || ev.file_name || ev.name || id, size: ref.size || ref.file_size || ev.file_size || 0, thumbnail_link: ref.thumbnail_link || ref.icon_link || '', icon_link: ref.icon_link || ev.icon_link || '', web_content_link: ref.web_content_link || '', created_time: eventTime, modified_time: eventTime || ref.modified_time || ref.updated_time || ref.created_time || '', mime_type: mime, parent_id: ref.parent_id || '', starred: !!(ref.starred || (ref.tags && ref.tags.some(tg => tg.name === 'STAR'))), trashed: !!ref.trashed, params: Object.assign({}, ev.params || {}, ref.params || {}), _recent_event_id: String(ev.id || ev.event_id || ''), _recent_event_type: ev.type || '', _recent_event_time: eventTime } }); }); const recentNextToken = json.next_page_token || json.nextPageToken || null; data = { files: Array.from(latestByFileId.values()) .sort((a, b) => b.eventTs - a.eventTs) .map(entry => entry.item) .filter(f => f.id && !fetchedIds.has(f.id)), next_page_token: recentNextToken }; } else if (S.shareMode) { if (pageCount > 0) break; const shares = await apiShareList(); data = { files: shares, next_page_token: null }; } else if (S.uploadMode) { if (pageCount > 0) break; try { const phases = "PHASE_TYPE_UNKNOW,PHASE_TYPE_PENDING,PHASE_TYPE_RUNNING,PHASE_TYPE_PAUSED,PHASE_TYPE_ERROR"; const filters = encodeURIComponent(JSON.stringify({ "phase": { "in": phases } })); const url = `https://api-drive.mypikpak.com/drive/v1/tasks?type=upload&limit=100&filters=${filters}&_t=${Date.now()}`; const res = await fetch(url, { headers: getHeaders(), signal: signal }); if (res.ok) { const cloudData = await res.json(); const cloudTasks = cloudData.tasks || []; const cloudIds = new Set(cloudTasks.map(ct => ct.id)); const cloudFileIds = new Set(cloudTasks.map(ct => ct.file_id).filter(Boolean)); S.uploadTasks = S.uploadTasks.filter(lt => { if (lt.status === 'DONE' || (!lt.file_id && !lt._uploadId)) return true; if ((lt.name === 'upload' || lt.name === 'Ghost Task') && !lt.file && lt.file_id) { fetch('https://api-drive.mypikpak.com/drive/v1/files:batchDelete', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: [lt.file_id] }) }).catch(()=>{}); if (S.upMng) S.upMng.removeTask(lt.id); return false; } const existsInCloud = cloudIds.has(lt.id) || (lt.file_id && cloudFileIds.has(lt.file_id)) || (lt._sourceTaskId && cloudIds.has(lt._sourceTaskId)); if (!existsInCloud) { if (S.upMng) S.upMng.removeTask(lt.id); return false; } return true; }); cloudTasks.forEach(ct => { const existsLocally = S.uploadTasks.some(lt => lt.id === ct.id || lt.file_id === ct.file_id || lt._sourceTaskId === ct.id); if (!existsLocally && ct.file_id) { const ref = ct.reference_resource || {}; const taskName = ref.name || ct.name || ct.file_name || 'Ghost Task'; if (taskName === 'upload' || taskName === 'Ghost Task') { fetch('https://api-drive.mypikpak.com/drive/v1/files:batchDelete', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: [ct.file_id] }) }).catch(()=>{}); return; } const ghostTask = { id: 'up_' + Date.now() + '_' + Math.random().toString(36).substr(2), _sourceTaskId: ct.id, kind: 'pk#upload', file: null, name: taskName, size: ct.file_size, parentId: '', status: 'PAUSED', progress: parseInt(ct.progress || 0), speed: 0, message: L.msg_ghost_task_resume, file_id: ct.file_id, hash: (ct.params && ct.params.hash) || '', _xhr: null, _lastCalcTime: 0, _lastCalcLoaded: 0, _lastUiTime: 0, icon_link: ct.icon_link || '', thumbnail_link: ct.thumbnail_link || '' }; S.uploadTasks.push(ghostTask); if (S.upMng) S.upMng.saveTask(ghostTask); } }); } } catch (e) { console.warn("[Upload] Failed to sync cloud upload tasks:", e); } data = { files: [...S.uploadTasks], next_page_token: null }; } else { const isStarredRoot = S.starredMode && S.path.length === 1; const filterObj = { "trashed": { "eq": S.trashMode } }; if (isStarredRoot) { filterObj.trashed = { "eq": false }; filterObj.system_tag = { "in": "STAR" }; } else if (!S.trashMode) { filterObj.phase = { "eq": "PHASE_TYPE_COMPLETE" }; } const filters = `&filters=${encodeURIComponent(JSON.stringify(filterObj))}`; const targetParentId = (S.trashMode || isStarredRoot) ? '*' : (cur.id || ''); const url = `https://api-drive.mypikpak.com/drive/v1/files?thumbnail_size=SIZE_MEDIUM&limit=${limit}${filters}&parent_id=${targetParentId}&_t=${Date.now()}${nextToken ? `&page_token=${nextToken}` : ''}`; const res = await fetch(url, { headers: getHeaders(), signal: signal, priority: 'high' }); if (currentId !== activeLoadId) return; if (!res.ok) { if (res.status === 429) { await sleep(1000); continue; } throw new Error("API " + res.status); } data = await res.json(); } if (data.files && data.files.length >= 0) { S.pagingLoading = !!data.next_page_token; if (currentId !== activeLoadId) return; const currentPathId = S.path[S.path.length - 1]?.id || ''; if (S.trashMode !== snapshot.trash || S.shareMode !== snapshot.share || S.starredMode !== snapshot.starred || S.recentMode !== snapshot.recent || currentPathId !== snapshot.pathId) { console.warn("[Load Guard] Context Drift Detected. Aborting render."); return; } const hasDirtyData = data.files.some(f => f && (S.trashMode ? !f.trashed : f.trashed)); if (!S.shareMode && !S.offlineMode && hasDirtyData) { console.warn(L.log_dirty_data); return; } let validFiles = []; if (S.shareMode || S.offlineMode || S.uploadMode) { validFiles = data.files; } else { validFiles = data.files.map(f => minifyFile(f, false)); } const newUniqueFiles = validFiles.filter(f => !fetchedIds.has(f.id)); if (S.analyzeMode && S.analyzeMap) { newUniqueFiles.forEach(item => { if (item.kind === 'drive#folder' && S.analyzeMap.has(item.id)) { item.size = S.analyzeMap.get(item.id).size.toString(); } }); } if (newUniqueFiles.length > 0 || (pageCount === 0 && !isResuming)) { if (pageCount === 0 && !isResuming) { S.items = newUniqueFiles; S.itemMap.clear(); } else { S.items.push(...newUniqueFiles); } newUniqueFiles.forEach(f => { fetchedIds.add(f.id); S.itemMap.set(f.id, f); }); totalItemsLoaded = S.items.length; if (S.recentMode && S.path.length === 1) { S.recentResultItems = [...S.items]; } if (S.recentMode && realCacheKey === 'recent_root') { globalCache.set('recent_session', { phase: 'active', nextToken: data.next_page_token || null, completed: false, count: S.items.length }); } if (S.offlineMode) { S.cache.set(realCacheKey, [...S.items]); if (typeof globalCache !== 'undefined') globalCache.set(realCacheKey, [...S.items]); } else { const tempCache = { items: [...S.items], nextToken: data.next_page_token }; S.cache.set(realCacheKey, tempCache); if (typeof globalCache !== 'undefined') globalCache.set(realCacheKey, tempCache); } const refreshPagingAppendView = async () => { const suppressSortMask = pageCount > 0; if (suppressSortMask) S._suppressSortMaskForPaging = true; try { await refresh(); } finally { if (suppressSortMask) S._suppressSortMaskForPaging = false; } }; if (pageCount === 0 || UI.loader.style.display !== 'none') { await refreshPagingAppendView(); if (UI.loader.style.display !== 'none') { setLoad(false); } if (pageCount === 0) { const authLoadKey = (!snapshot.pathId || snapshot.pathId === 'root') ? 'root' : String(snapshot.pathId); window.__pkLastManagerLoadOk = { at: Date.now(), key: authLoadKey, count: S.items.length }; } if (S.items.length === 0) { UI.in.innerHTML = ''; renderVisible(); } } else { if (pageCount % 4 === 0) await refreshPagingAppendView(); } } else if (S.recentMode && realCacheKey === 'recent_root') { globalCache.set('recent_session', { phase: 'active', nextToken: data.next_page_token || null, completed: false, count: S.items.length }); const tempCache = { items: [...S.items], nextToken: data.next_page_token }; S.cache.set(realCacheKey, tempCache); if (typeof globalCache !== 'undefined') globalCache.set(realCacheKey, tempCache); } } nextToken = data.next_page_token; pageCount++; if (currentId === activeLoadId) { const isProgressiveSearch = !!((S.offlineMode || S.recentMode) && S.search); const statCount = isProgressiveSearch ? S.display.reduce((cnt, item) => cnt + (item && !item.isHeader && !(S.movingIds && S.movingIds.has(item.id)) ? 1 : 0), 0) : totalItemsLoaded; const selectedCount = S.selMode === 'all' ? Math.max(0, statCount - (S.selEx ? S.selEx.size : 0)) : (S.getSelectedCount ? S.getSelectedCount() : 0); let selectedSizeText = ''; if (selectedCount > 0 && S.selMode !== 'all') { let hasSelectedFolder = false; let selectedSize = 0; for (const id of S.getSelectedIds()) { const item = S.itemMap.get(id); if (!item) continue; const sz = parseInt(item.size || 0, 10) || 0; if (item.kind === 'drive#folder') { hasSelectedFolder = true; } else { selectedSize += sz; } } if (!S.shareMode && !hasSelectedFolder) selectedSizeText = `\u00A0\u00A0${fmtSize(selectedSize)}`; } UI.stat.textContent = L.status_ready.replace('{n}', statCount) + getPagingLoadingSuffix() + (selectedCount > 0 ? `\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0${L.sel_count.replace('{n}', selectedCount)}${selectedSizeText}` : ''); } if (nextToken) await sleep(0); } while (nextToken && currentId === activeLoadId); if (currentId === activeLoadId) { if (!(S.trashMode && S.items.length === 0 && pageCount > 1)) { S.cache.set(realCacheKey, [...S.items]); } if (typeof globalCache !== 'undefined') globalCache.set(realCacheKey, [...S.items]); if (S.recentMode && realCacheKey === 'recent_root') { globalCache.set('recent_session', { phase: 'done', nextToken: null, completed: true, count: S.items.length }); } if (window.pkSmartRefreshTrigger && !(S.recentMode && realCacheKey === 'recent_root')) { window.pkSmartRefreshTrigger(); } indexParents(folderId, cur.name, S.items); S.itemMap.clear(); for (let k = 0; k < S.items.length; k++) S.itemMap.set(S.items[k].id, S.items[k]); syncStars(); if (folderId === 'root' && !S.preloaded) S.preloaded = true; const visibleSubFolders = S.items.filter(f => f.kind === 'drive#folder'); for (let i = visibleSubFolders.length - 1; i >= 0; i--) { const sub = visibleSubFolders[i]; if (!scannedFolderIds.has(sub.id) && !globalCache.has(sub.id)) { backgroundQueue.push({ id: sub.id, name: sub.name, retryCount: 0 }); scannedFolderIds.add(sub.id); } } isGUISensitive = false; runBackgroundCrawler(); S.pagingLoading = false; refresh(); updateStat(); setLoad(false); S._networkRetryCount = 0; } } catch (e) { isGUISensitive = false; if (currentId === activeLoadId) { S.pagingLoading = false; updateStat(); if (!S._isRetrying) setLoad(false); if (e.name !== 'AbortError') { console.error("API Error encountered:", e); if (typeof resetHeaderCache === 'function') resetHeaderCache(); const isAuthError = e.message.includes('401') || e.message.includes('403') || e.message.includes('400') || e.message.includes('CAPTCHA') || e.message.includes('AUTH_RETRY'); const isNotFoundError = e.message.includes('404'); const isNetworkError = e.name === 'TypeError' || e.message.includes('Failed to fetch') || e.message.includes('NetworkError'); if (!S._isRetrying || isNetworkError) { S._isRetrying = true; setLoad(true); if (isAuthError) { resetHeaderCache(); updateLoadTxt(L.str_waiting_token); if (typeof window.pkEnterAuthRecoveryWindow === 'function') window.pkEnterAuthRecoveryWindow('auth-error', 5000); await sleep(1000); try { let h = getHeaders(); if (!h.Authorization || h.Authorization.length < 10) { const isAuthReady = await waitForAuth(5000); if (!isAuthReady) { if (typeof window.pkEnterAuthRecoveryWindow === 'function') window.pkEnterAuthRecoveryWindow('auth-error-missing-token-recheck', 5000); await sleep(1200); const isAuthReadyAgain = await waitForAuth(5000); if (!isAuthReadyAgain) { const didLogout = await confirmedLogout('auth-error-missing-token-recheck-final', 5000, 5200); if (didLogout) return; S._isRetrying = false; setLoad(false); return; } } h = getHeaders(); } let testRes = await fetch('https://api-drive.mypikpak.com/drive/v1/about', { headers: h }); if (!testRes.ok) { console.warn("[Auth] Token rejected by server, entering recovery recheck before logout."); if (typeof window.pkEnterAuthRecoveryWindow === 'function') window.pkEnterAuthRecoveryWindow('auth-error-about-recheck', 5000); await sleep(1200); const isAuthReady = await waitForAuth(5000); if (!isAuthReady) { const didLogout = await confirmedLogout('auth-error-about-first-reject', 5000, 5200); if (didLogout) return; h = getHeaders(); } else { h = getHeaders(); } testRes = await fetch('https://api-drive.mypikpak.com/drive/v1/about', { headers: h }); if (!testRes.ok) { console.warn("[Auth] Token still rejected by server after recovery recheck."); const didLogout = await confirmedLogout('auth-error-about-second-reject', 5000, 5200); if (didLogout) return; h = getHeaders(); testRes = await fetch('https://api-drive.mypikpak.com/drive/v1/about', { headers: h }); if (!testRes.ok) { console.warn("[Auth] Token still unavailable after confirmed logout recheck, keeping current view."); S._isRetrying = false; setLoad(false); return; } } } if (typeof window.pkMarkAuthRecovered === 'function') window.pkMarkAuthRecovered(); const testData = await testRes.json(); if (testData.error) { console.warn("[Auth] API returned error inside response."); const didLogout = await confirmedLogout('auth-error-about-payload-error', 5000, 5200); if (didLogout) return; S._isRetrying = false; setLoad(false); return; } if (!testData.sub || testData.sub === '') { console.warn("[Auth] Token valid but no user info."); const didLogout = await confirmedLogout('auth-error-about-empty-sub', 5000, 5200); if (didLogout) return; console.warn("[Auth] Auth recovered after empty user info, retrying file load."); if (typeof window.pkMarkOfficialAuthReady === 'function') window.pkMarkOfficialAuthReady('about-empty-sub-recovered'); updateLoadTxt(L.loading_detail); load(false, true).finally(() => { S._isRetrying = false; }); return; } } catch(testErr) { console.warn("[Auth] Token verification failed (CORS/Network/Parse):", testErr); const didLogout = await confirmedLogout('auth-error-about-verify-exception', 5000, 5200); if (didLogout) return; S._isRetrying = false; setLoad(false); return; } load(false, true).finally(() => { S._isRetrying = false; }); return; } if (isNotFoundError) { if (S.path.length > 1 || S.path[0].id !== '') { S.path = [{ id: '', name: L.btn_nav_home }]; load(false, true).finally(() => { S._isRetrying = false; }); return; } } if (isNetworkError) { S._networkRetryCount = (S._networkRetryCount || 0) + 1; if (S._networkRetryCount > 5) { console.error("[Network] Max retries reached."); const didLogout = await confirmedLogout('network-retry-exhausted', 5000, 5200); if (didLogout) return; S._isRetrying = false; S._networkRetryCount = 0; setLoad(false); showAlert(L.msg_network_unstable); return; } const delay = 1000 + Math.random() * 2000; const retryMsg = L.msg_network_unstable; updateLoadTxt(retryMsg); console.warn(`[Network] Connection drop detected. Retrying in ${Math.round(delay)}ms... (${S._networkRetryCount}/5)`); await sleep(delay); load(false, true).finally(() => { }); return; } S._isRetrying = false; } if (S.items.length === 0) { setLoad(false); showAlert(L.str_failed + " " + e.message); } } } } } async function refresh() { S.sortId++; const currentReqId = S.sortId; syncLocalUploadVisibility(); if (S.shareParseMode) { setLoad(false); renderCrumb(); if (typeof applyResolvedSortState === 'function') applyResolvedSortState(); if (S.shareParseListActive) { syncShareParseMirror(); renderList(); } else { renderShareParsePanel(); } syncShareParseInsightFilterBar(); updateStat(); return; } const prevViewMode = S.viewMode === 'grid' ? 'grid' : 'list'; const nextCur = S.path[S.path.length - 1] || { id: '' }; const nextIsStandardView = !S.trashMode && !S.shareMode && !S.offlineMode && !S.starredMode && !S.recentMode && !S.historyMode && !S.isFlattened && !S.dupMode && !S.analyzeMode && (!nextCur.id.startsWith('virtual_') || nextCur.id === 'virtual_search_root'); const groupedGridExitSyncPending = !!S._groupedGridExitSyncPending; if (nextIsStandardView) { const nextFolderId = nextCur.id || 'root'; const nextViewMode = (typeof resolvePreferredViewMode === 'function') ? resolvePreferredViewMode(nextFolderId) : (gmGet('pk_file_view_mode', 'grid') === 'list' ? 'list' : 'grid'); if (prevViewMode !== nextViewMode || (groupedGridExitSyncPending && nextViewMode === 'grid')) { beginFolderViewSync(); } S._groupedGridExitSyncPending = false; S.viewMode = nextViewMode; } else { S._groupedGridExitSyncPending = false; endFolderViewSync(); } renderCrumb(); if (!S.dupMode) S.dupItemMap = null; const isStrictVirtual = S.isFlattened || S.dupMode || S.analyzeMode; const cur = S.path[S.path.length - 1]; S.syncNavContextState(); if (!isStrictVirtual) { restoreFolderFirstAfterStrictMode(); } const isInSearchContext = S.path.some(p => p.id === 'virtual_search_root'); const shouldHideHeavyOps = isStrictVirtual || isInSearchContext || S.offlineMode || S.uploadMode; if (UI.btnFolderFirst) { const isAnalyzeRoot = S.analyzeMode && cur.id === 'analyze_root'; const isAnalyzeSub = S.analyzeMode && cur.id !== 'analyze_root'; const shouldHideFF = S.isFlattened || S.dupMode || isAnalyzeRoot; if (UI.btnFolderFirst) { UI.btnFolderFirst.style.display = shouldHideFF ? 'none' : 'flex'; if (isAnalyzeSub) S.renderFolderFirst(); } } if (UI.btnAnalyze) { UI.btnAnalyze.style.display = (shouldHideHeavyOps || S.trashMode || S.shareMode || S.shareParseMode || S.starredMode || S.recentMode || S.historyMode) ? 'none' : 'flex'; } if (UI.btnExport) { UI.btnExport.style.display = (shouldHideHeavyOps || S.trashMode || S.shareMode || S.starredMode || S.recentMode || S.historyMode) ? 'none' : 'flex'; } if (UI.scan) { UI.scan.style.display = (shouldHideHeavyOps || S.trashMode || S.shareMode || S.starredMode || S.recentMode || S.historyMode) ? 'none' : 'flex'; } if (UI.btnExit) { UI.btnExit.style.display = isStrictVirtual ? 'flex' : 'none'; } if (UI.lblSearchPath) { const isAnalyzeRoot = S.analyzeMode && cur.id === 'analyze_root'; const showAnalyzeGroupTools = isAnalyzeRoot && !S.search && S.analyzeSimGroups && !S.shareParseMode; const isAnalyzeDupView = S.analyzeMode && S.analyzeSimGroups; if (UI.win) { UI.win.classList.toggle('pk-analyze-group-tools-on', !!showAnalyzeGroupTools); UI.win.classList.toggle('pk-share-parse-mode', !!S.shareParseMode); } UI.lblSearchPath.style.display = (S.dupMode || S.isFlattened || isAnalyzeRoot) ? 'flex' : 'none'; if (UI.btnAnaSelect) UI.btnAnaSelect.style.display = showAnalyzeGroupTools ? 'flex' : 'none'; if (UI.btnAnaSort) UI.btnAnaSort.style.display = showAnalyzeGroupTools ? 'flex' : 'none'; if (UI.vp) UI.vp.style.overflowX = (S.dupMode || isAnalyzeDupView) ? 'hidden' : ''; } if (UI.filterBar) { if (S.isFlattened && !S.dupMode) { UI.filterBar.style.display = 'flex'; if (S.filterState.active) { UI.filterBtn.style.display = 'none'; UI.filterActiveUI.style.display = 'flex'; if (typeof renderActiveFilterUI === 'function') renderActiveFilterUI(); } else { UI.filterBtn.style.display = 'flex'; UI.filterActiveUI.style.display = 'none'; } } else { UI.filterBar.style.display = 'none'; } } const isStarredRoot = S.starredMode && S.path.length === 1; const isRecentRoot = S.recentMode && S.path.length === 1; if (UI.btnNewFolder) { const isNewFolderBlocked = S.isFlattened || S.dupMode || S.shareMode || S.offlineMode || S.uploadMode || S.historyMode || isStarredRoot || isRecentRoot || cur.id === 'analyze_root' || cur.id === 'virtual_search_root'; UI.btnNewFolder.style.display = isNewFolderBlocked ? 'none' : (S.trashMode ? 'none' : 'flex'); } if (UI.btnPaste) { const isPasteBlocked = S.isFlattened || S.dupMode || S.shareMode || S.offlineMode || S.uploadMode || S.historyMode || isStarredRoot || isRecentRoot || cur.id === 'analyze_root' || cur.id === 'virtual_search_root'; UI.btnPaste.style.display = isPasteBlocked ? 'none' : (S.trashMode ? 'none' : 'inline-flex'); } const isInVirtualSearch = S.path.some(p => p.id === 'virtual_search_root'); const isInsideSearchResult = isInVirtualSearch && cur.id !== 'virtual_search_root'; if (S.search && !S.dupMode && !isInsideSearchResult) { const q = S.search.toLowerCase(); const includePath = UI.chkSearchPath && UI.chkSearchPath.checked; if (S.analyzeMode && cur.id === 'analyze_root' && S.analyzeSimGroups) { const newDisplay = []; S.analyzeSimGroups.forEach((g, gIdx) => { const groupItems = g.ids.map(id => S.itemMap.get(id)).filter(Boolean); const isGroupHit = g.type.toLowerCase().includes(q) || groupItems.some(it => { const nameMatch = it.name.toLowerCase().includes(q); let pathMatch = false; if (includePath && it._pathStr) { const homeText = L.btn_nav_home; const parentPath = (it._pathStr === homeText || it._pathStr.startsWith(homeText + '/')) ? it._pathStr : (homeText + '/' + it._pathStr); const fullItemPath = parentPath.endsWith('/') ? (parentPath + it.name) : (parentPath + '/' + it.name); pathMatch = fullItemPath.toLowerCase().includes(q); } return nameMatch || pathMatch; }); if (isGroupHit) { newDisplay.push({ id: `grp_${gIdx}`, isHeader: true, name: g.type, count: g.ids.length, type: g.type || L.str_group }); groupItems.sort((a, b) => parseInt(b.size || 0) - parseInt(a.size || 0)); groupItems.forEach(it => { if (it.name.toLowerCase().includes(q)) { const name = it.name; const idx = name.toLowerCase().indexOf(q); const len = q.length; const start = Math.max(0, idx - 15); const end = Math.min(name.length, idx + len + 30); let pre = start > 0 ? "..." : ""; let suf = end < name.length ? "..." : ""; it._hlNameHTML = `${pre}${esc(name.substring(start, idx))}${esc(name.substring(idx, idx + len))}${esc(name.substring(idx + len, end))}${suf}`; } else { delete it._hlNameHTML; } newDisplay.push(it); }); } }); S.display = newDisplay; } else if (UI.chkGlobal && UI.chkGlobal.checked && cur.id === 'virtual_search_root') { let results = []; const seenIds = new Set(); let realMyPackId = ''; if (typeof globalCache !== 'undefined') { const rootFiles = globalCache.get('') || globalCache.get('root'); if (rootFiles) { const realObj = rootFiles.find(f => f.kind === 'drive#folder' && f.name === CONF.SYSTEM_FOLDER_NAME); if (realObj) realMyPackId = realObj.id; } } if (CONF.SYSTEM_FOLDER_NAME.toLowerCase().includes(q)) { const realObj = (globalCache.get('') || globalCache.get('root'))?.find(f => f.name === CONF.SYSTEM_FOLDER_NAME); const sysRoot = { id: realMyPackId, kind: 'drive#folder', name: CONF.SYSTEM_FOLDER_NAME, parent_id: '', size: 0, modified_time: '', starred: false, tags: [], _lineage: [], _isSystemRoot: true, icon_link: realObj ? realObj.icon_link : '', thumbnail_link: realObj ? realObj.icon_link : '' }; results.push(sysRoot); if (realMyPackId) seenIds.add(realMyPackId); } if (typeof globalCache !== 'undefined') { for (const [key, files] of globalCache) { if (currentReqId !== S.sortId) return; if (!Array.isArray(files)) continue; if (key && (key.endsWith('_root') || key === 'root_trashed')) continue; let parentLineage = (typeof globalLineageMap !== 'undefined') ? globalLineageMap.get(key) : undefined; if (!parentLineage) { if (key === '' || key === 'root') parentLineage = []; else parentLineage = null; } for (let i = 0, len = files.length; i < len; i++) { const f = files[i]; if (seenIds.has(f.id)) continue; if (f && f.name && f.name.toLowerCase().includes(q)) { let itemLineage = parentLineage; const fObj = { ...f, _lineage: itemLineage }; const fName = fObj.name; const fIdx = fName.toLowerCase().indexOf(q); if (fIdx !== -1) { const start = Math.max(0, fIdx - 15); const end = Math.min(fName.length, fIdx + q.length + 30); let pre = start > 0 ? "..." : ""; let suf = end < fName.length ? "..." : ""; const b = fName.substring(start, fIdx); const m = fName.substring(fIdx, fIdx + q.length); const a = fName.substring(fIdx + q.length, end); fObj._hlNameHTML = `${pre}${esc(b)}${esc(m)}${esc(a)}${suf}`; } results.push(fObj); seenIds.add(f.id); } } } } S.display = results; S.items = results; S.lastGlobalResults = [...results]; ensureItemMap(); } else if (isInVirtualSearch && cur.id !== 'virtual_search_root') { let ancestors = (typeof globalLineageMap !== 'undefined' && globalLineageMap.get(cur.id)) || cur._lineage || []; const currentFolderAsAncestor = [...ancestors, { id: cur.id, name: cur.name }]; S.display = S.items.map(item => { const itemLineage = (item.kind === 'drive#folder' && typeof globalLineageMap !== 'undefined' && globalLineageMap.has(item.id)) ? globalLineageMap.get(item.id) : currentFolderAsAncestor; return { ...item, _lineage: itemLineage }; }); } else { const query = S.search.toLowerCase(); const includePath = UI.chkSearchPath && UI.chkSearchPath.checked; const isPathMode = S.isFlattened || (S.analyzeMode && cur.id === 'analyze_root'); const rootPathStr = S.path.map(p => p.name).join('/'); let filtered = S.items.filter(i => { if (!i) return false; if (i.name && i.name.toLowerCase().includes(query)) return true; if (includePath && isPathMode) { let pStr = ""; if (S.analyzeMode) { pStr = i._pathStr || L.btn_nav_home; } else if (i._lineage) { let cleanLineage = i._lineage.map(x => x.name).filter(n => n && n !== 'Root' && n !== L.str_root_dir_cn); if (cleanLineage[0] !== L.btn_nav_home) cleanLineage.unshift(L.btn_nav_home); pStr = cleanLineage.join('/'); } const fullItemPath = pStr.endsWith('/') ? (pStr + i.name) : (pStr + '/' + i.name); if (fullItemPath.toLowerCase().includes(query)) return true; } return false; }); if (S.offlineMode) { const cfg = S.offlineFilters; filtered = filtered.filter(i => { const p = i.phase; if (p === 'PHASE_TYPE_COMPLETE') return cfg.complete; if (['PHASE_TYPE_RUNNING', 'PHASE_TYPE_PENDING', 'PHASE_TYPE_PAUSED'].includes(p)) return cfg.running; return cfg.failed; }); } filtered.forEach(item => { const name = item.name; const idx = name.toLowerCase().indexOf(query); if (idx !== -1) { const len = query.length; const start = Math.max(0, idx - 15); const end = Math.min(name.length, idx + len + 30); let prefix = start > 0 ? "..." : ""; let suffix = end < name.length ? "..." : ""; const partBefore = name.substring(start, idx); const partMatch = name.substring(idx, idx + len); const partAfter = name.substring(idx + len, end); item._hlNameHTML = `${prefix}${esc(partBefore)}${esc(partMatch)}${esc(partAfter)}${suffix}`; } }); S.display = filtered; } } else { if (S.path.length > 0 && S.path[0].id === 'starred') { S.display = S.items.filter(i => (i.starred || (i.tags && i.tags.some(t => t.name === 'STAR'))) && !i.trashed); } else if (S.isFlattened && S.filterState && S.filterState.active && S.filterState.cat !== 'all') { const cat = S.filterState.cat; const ext = S.filterState.ext; const fExts = { video: ['mp4','mkv','avi','mov','wmv','flv','webm','ts','m4v','3gp','mpg','mpeg','rm','rmvb','asf','vob','dat','divx','f4v','m2ts','mts','tp','trp','ogv','mpe','m2v','m3u8'], audio: ['mp3','wav','flac','aac','ogg','wma','ape','m4a','amr','opus','m4b','alac','aiff','mid','midi','ra','dts','ac3','dsf','dff'], image: ['jpg','jpeg','png','gif','bmp','webp','svg','tif','tiff','ico','heic','heif','raw','cr2','nef','arw','dng','orf','avif','psd','ai','eps','jfif','jpe'], document: ['txt','html','pdf','pptx','chm','docx','xlsx','htm','doc','dwg','mdb','ppt','xls','rtf','odt','ods','odp','epub','mobi','azw3','djvu','cbz','cbr','md','log','csv','xml','json'], software: ['apk','exe','ipa','dmg','rpm','deb','msi','pkg','xapk','apks','aab','jar','bin','sh','bat','cmd'], archive: ['zip','rar','7z','tar','gz','iso','cab','bz2','xz','tgz','wim','esd','img','zst','lzh'], torrent: ['torrent'] }; let matchExts =[]; if (cat === 'other') { const definedExts = new Set([...fExts.video, ...fExts.audio, ...fExts.image, ...fExts.document, ...fExts.software, ...fExts.archive, ...fExts.torrent]); S.display = S.items.filter(i => { if (i.kind === 'drive#folder') return false; const n = (i.name || '').toLowerCase(); const e = n.split('.').pop(); return !definedExts.has(e); }); } else { if (ext === 'all') { matchExts = fExts[cat] ||[]; } else { matchExts = [ext]; } S.display = S.items.filter(i => { if (i.kind === 'drive#folder') return false; const n = (i.name || '').toLowerCase(); const e = n.split('.').pop(); return matchExts.includes(e); }); } } else if (S.offlineMode) { const cfg = S.offlineFilters; S.display = S.items.filter(i => { const p = i.phase; if (p === 'PHASE_TYPE_COMPLETE') return cfg.complete; if (['PHASE_TYPE_RUNNING', 'PHASE_TYPE_PENDING', 'PHASE_TYPE_PAUSED'].includes(p)) return cfg.running; return cfg.failed; }); } else if (S.uploadMode) { const cfg = S.uploadFilters; S.display = S.items.filter(i => { const s = i.status; if (s === 'DONE') return cfg.complete; if (s === 'PAUSED' || s === 'ERROR') return cfg.paused; return cfg.running; }); } else if (S.analyzeMode && cur.id === 'analyze_root' && S.analyzeSimGroups) { const newDisplay =[]; S.analyzeSimGroups.forEach((g, gIdx) => { newDisplay.push({ id: `grp_${gIdx}`, isHeader: true, name: g.type, count: g.ids.length, type: g.type || L.str_group }); const groupItems = g.ids.map(id => S.itemMap.get(id)).filter(Boolean); groupItems.sort((a, b) => { const pA = a._pathStr || ""; const pB = b._pathStr || ""; if (pA.length !== pB.length) return pA.length - pB.length; return pA.localeCompare(pB); }); let lastPathInGroup = null; groupItems.forEach(it => { const currentPath = it._pathStr || ""; if (currentPath === lastPathInGroup && currentPath !== "") { it._isSameFolder = true; } else { it._isSameFolder = false; lastPathInGroup = currentPath; } newDisplay.push(it); }); }); S.display = newDisplay; } else { S.display = [...S.items]; } } if (currentReqId !== S.sortId) return; const isFlattenBaseEmpty = S.isFlattened && !S.dupMode && !S.analyzeMode && !S.scanning && Array.isArray(S.items) && S.items.length === 0; const isAnalyzeBaseEmpty = S.analyzeMode && cur && cur.id === 'analyze_root' && !S.analyzeSimGroups && !S.scanning && Array.isArray(S.items) && S.items.length === 0; if ((isFlattenBaseEmpty || isAnalyzeBaseEmpty) && !S._autoExitingVirtualEmpty) { S._autoExitingVirtualEmpty = true; try { if (typeof S.exitVirtualNavMode === 'function') { await S.exitVirtualNavMode(); } else if (UI.btnExit) { UI.btnExit.click(); } else { S.isFlattened = false; S.analyzeMode = false; S.dupMode = false; refresh(); } } finally { S._autoExitingVirtualEmpty = false; } return; } const visibleSet = new Set(S.display.map(i => i.id)); if (S.selMode === 'all') { const nextExcluded = new Set(); for (const id of S.selEx) { if (visibleSet.has(id)) nextExcluded.add(id); } S.selEx = nextExcluded; } else { const nextSelected = S.getSelectedIds().filter(id => visibleSet.has(id)); S.setExplicitSelection(nextSelected); } S.dupReasons.clear(); S.dupGroups.clear(); if (!S.dupMode) S.pinnedDupPath = null; const isStandardView = !S.trashMode && !S.shareMode && !S.offlineMode && !S.starredMode && !S.recentMode && !S.historyMode && !S.isFlattened && !S.dupMode && !S.analyzeMode && (!cur.id.startsWith('virtual_') || cur.id === 'virtual_search_root'); const folderFirstViewKey = S.starredMode ? '__starred__' : (S.recentMode ? '__recent__' : ''); if (isStandardView) { const folderId = cur.id || 'root'; if (S._viewAppliedForId !== folderId) { S.viewMode = resolvePreferredViewMode(folderId); S._viewAppliedForId = folderId; } if (S._sortAppliedForId !== folderId) { syncFolderFirstFromGlobal(); S._sortAppliedForId = folderId; if (S.renderFolderFirst) S.renderFolderFirst(); } } else if (folderFirstViewKey) { if (S._sortAppliedForId !== folderFirstViewKey) { syncFolderFirstFromGlobal(); S._sortAppliedForId = folderFirstViewKey; if (S.renderFolderFirst) S.renderFolderFirst(); } } if (typeof applyResolvedSortState === 'function') applyResolvedSortState(); if (S.dupMode) { setLoad(true); S.dupRunning = true; UI.stopBtn.onclick = () => { S.dupRunning = false; }; updateLoadTxt(L.loading_dup.replace('{p}', 0)); await sleep(20); const cfg = S.dupConfig || { video: true, image: false, other: false }; let candidates = S.display.filter(i => { if (!i.mime_type) return false; const isVideo = i.mime_type.startsWith('video'); const isImage = i.mime_type.startsWith('image'); const isOther = !isVideo && !isImage; if (isVideo && cfg.video) return true; if (isImage && cfg.image) return true; if (isOther && cfg.other) return true; return false; }); const groups = await computeDuplicateGroups(candidates, cfg, () => S.dupRunning); groups.sort((a, b) => { const getPriority = (t) => { if (t === L.tag_hash) return 3; if (t === L.tag_sim) return 2; if (t === L.tag_name) return 1; return 0; }; const pA = getPriority(a.type); const pB = getPriority(b.type); if (pA !== pB) return pB - pA; return b.ids.length - a.ids.length; }); const dupItemMap = new Map(); for (let i = 0, len = S.items.length; i < len; i++) { dupItemMap.set(S.items[i].id, S.items[i]); } S.dupItemMap = dupItemMap; const dupBuckets = { all: [], hash: [], sim: [], name: [] }; for (let i = 0, len = groups.length; i < len; i++) { const g = groups[i]; const validIds = []; for (let k = 0, idsLen = g.ids.length; k < idsLen; k++) { const id = g.ids[k]; if (dupItemMap.has(id)) validIds.push(id); } if (validIds.length <= 1) continue; const normalizedGroup = { ...g, ids: validIds }; dupBuckets.all.push(normalizedGroup); if (g.type === L.tag_hash) dupBuckets.hash.push(normalizedGroup); else if (g.type === L.tag_sim) dupBuckets.sim.push(normalizedGroup); else if (g.type === L.tag_name) dupBuckets.name.push(normalizedGroup); } if (dupBuckets.all.length === 0) { S.dupRunning = false; setLoad(false); showToast(L.msg_dup_none); if (UI.btnExit) UI.btnExit.click(); return; } S.dupBuckets = dupBuckets; S.dupRawGroups = dupBuckets.all; if (UI.chkName) UI.chkName.checked = true; if (UI.chkSim) UI.chkSim.checked = true; if (UI.chkHash) UI.chkHash.checked = true; const applyDupSelection = () => { const targetPath = UI.selDupFolder.value; const invertChk = document.getElementById('pk-dup-invert'); if (!targetPath || targetPath === "__RESET__") { S.pinnedDupPath = null; S.sel.clear(); if (invertChk) { invertChk.checked = false; invertChk.disabled = true; invertChk.parentNode.style.opacity = '0.5'; } renderDupView(); updateStat(); if(targetPath) UI.selDupFolder.value = ""; return; } if (invertChk) { invertChk.disabled = false; invertChk.parentNode.style.opacity = '1'; } const isInvert = invertChk ? invertChk.checked : false; S.pinnedDupPath = targetPath; const itemMap = new Map(); for(const item of S.items) itemMap.set(item.id, item); S.sel.clear(); S.dupRawGroups.forEach(g => { const filesInTarget = []; const filesInOther = []; g.ids.forEach(id => { const item = itemMap.get(id); if (item) { let path = ""; if (item._cachedPath !== undefined) { path = item._cachedPath; } else { if (item._lineage && item._lineage.length > 0) { path = item._lineage.map(p => p.name).join('/'); } else { const curFolder = S.path[S.path.length - 1]; path = curFolder ? curFolder.name : L.current_dir; } item._cachedPath = path; } if (path === targetPath) filesInTarget.push(item); else filesInOther.push(item); } }); if (filesInOther.length > 0) { if (isInvert) { filesInOther.forEach(f => S.sel.add(f.id)); } else { filesInTarget.forEach(f => S.sel.add(f.id)); } } else if (filesInTarget.length > 0) { if (!isInvert) { filesInTarget.forEach(f => S.sel.add(f.id)); } } }); renderDupView(); updateStat(); if (S._dupFolderPickedScrollTop) { S._dupFolderPickedScrollTop = false; if (S._dupFolderPickedTopRaf) cancelAnimationFrame(S._dupFolderPickedTopRaf); S._dupFolderPickedTopRaf = requestAnimationFrame(() => { S._dupFolderPickedTopRaf = 0; if (UI.vp && UI.vp.scrollTop !== 0) UI.vp.scrollTop = 0; }); } }; UI.selDupFolder.onchange = applyDupSelection; setTimeout(() => { const invertChk = document.getElementById('pk-dup-invert'); if (invertChk) { invertChk.onchange = () => { if (UI.selDupFolder.value && UI.selDupFolder.value !== "__RESET__") { applyDupSelection(); } }; } const smartBtn = document.getElementById('pk-dup-smart-btn'); if (smartBtn) { smartBtn.addEventListener('click', () => { if (S.pinnedDupPath) { S.pinnedDupPath = null; UI.selDupFolder.value = ""; if (invertChk) { invertChk.checked = false; invertChk.disabled = true; invertChk.parentNode.style.opacity = '0.5'; } renderDupView(); } }, { capture: true }); } }, 0); S.dupRunning = false; setLoad(false); if (UI.dupTools) UI.dupTools.style.display = 'flex'; if (UI.dupFilters) { UI.dupFilters.style.display = 'flex'; const simChkWrapper = UI.dupFilters.querySelector('#pk-chk-sim')?.closest('.pk-dup-chk'); if (simChkWrapper) { simChkWrapper.style.display = S.dupConfig.video ? 'flex' : 'none'; } } renderDupView(); } else { if (UI.dupTools) UI.dupTools.style.display = 'none'; if (UI.dupFilters) UI.dupFilters.style.display = 'none'; if (S.uploadMode || (S.analyzeMode && S.analyzeSimGroups && cur.id === 'analyze_root')) { renderList(); if (gmGet('pk_keep_pos', true) && S.latestChildId) { const targetIdx = S.display.findIndex(x => x.id === S.latestChildId); if (targetIdx !== -1) { const targetTop = getItemScrollTopByIndex(targetIdx); UI.vp.scrollTop = Math.max(0, targetTop - (UI.vp.clientHeight / 2) + (CONF.rowHeight / 2)); S.clearSelection(); S.activeId = S.latestChildId; renderVisible(); } S.latestChildId = null; } updateStat(); return; } const suppressSortMaskForPaging = S._suppressSortMaskForPaging === true; const showLargeSortMask = S.display.length > 5000 && !S.offlineMode && !S.historyMode && !suppressSortMaskForPaging; if (showLargeSortMask) { setLoad(true); updateLoadTxt(L.str_sorting); await sleep(10); } const sortedList = await new Promise(resolve => { const isRoot = S.path.length === 1 && S.path[0].id === ''; const proxyList = new Array(S.display.length); const len = S.display.length; for (let i = 0; i < len; i++) { const item = S.display[i]; const isStarred = S.starredSet.has(item.id) || !!(item.starred || (item.tags && item.tags.some(t => t.name === 'STAR'))); const isMyPack = item._isSystemRoot || (!S.trashMode && item.kind === 'drive#folder' && ( (item.id === '' || item.id === 'root') || (isRoot && item.name === CONF.SYSTEM_FOLDER_NAME) )); const ext = item.name.split('.').pop().toLowerCase(); const isVid = ['mp4', 'mkv', 'avi', 'mov', 'wmv', 'flv', 'webm', 'ts', 'm4v', '3gp'].includes(ext); const isImg = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp', 'tiff', 'ico'].includes(ext); const mimeGroup = isVid ? 1 : (isImg ? 2 : 3); let finalDur = S.durationMap.get(item.id) || parseFloat(item.params?.duration || 0); if (finalDur > 0 && item.params) item.params.duration = finalDur; const historySortDur = S.historyMode ? (parseFloat(item._history_duration || 0) || finalDur) : finalDur; let pathStr = ""; if (S.analyzeMode) pathStr = item._pathStr || ""; else if (item._lineage) pathStr = item._lineage.map(x=>x.name).join('/'); proxyList[i] = { i: i, n: item.name, k: item.kind === 'drive#folder' ? 1 : 0, s: item.size || 0, t: item.modified_time, d: S.historyMode ? historySortDur : finalDur, st: isStarred ? 1 : 0, mp: isMyPack ? 1 : 0, m: mimeGroup, p: pathStr, ct: item.created_time, pt: item._history_ts || 0, pg: historySortDur > 0 ? Math.min(100, Math.max(0, ((item._history_progress || 0) / historySortDur) * 100)) : -1, v: parseInt(item.view_count || 0), sc: parseInt(item.save_count || 0), ss: item.share_status || 'OK', ed: parseInt(item.expiration_days || -1), lc: parseInt(item.limit_count || 0) }; } const workerCode = ` self.onmessage = function(e) { const { proxy, sort, dir, reqId, folderFirst, locale } = e.data; const parseSize = n => parseInt(n || 0); const parseTime = t => t ? new Date(t).getTime() : 0; const collator = new Intl.Collator(locale, { numeric: true, sensitivity: 'base', caseFirst: 'lower' }); const getCharWeight = (str) => { if (!str) return 0; const c = str.charAt(0); if (/[0-9]/.test(c)) return 20; if (/[\\u4e00-\\u9fa5]/.test(c)) return 30; if (/[a-zA-Z]/.test(c)) return 40; return 10; }; const compareNames = (nameA, nameB) => { const rankA = getCharWeight(nameA); const rankB = getCharWeight(nameB); if (rankA !== rankB) { return rankA - rankB; } return collator.compare(nameA, nameB); }; proxy.sort((a, b) => { if (a.mp !== b.mp) return b.mp - a.mp; if (folderFirst && a.k !== b.k) { return b.k - a.k; } if (sort === 'view_count' || sort === 'save_count') { const valA = sort === 'view_count' ? (a.v || 0) : (a.sc || 0); const valB = sort === 'view_count' ? (b.v || 0) : (b.sc || 0); if (valA !== valB) return (valB - valA) * dir; return compareNames(a.n, b.n) * dir; } if (sort === 'share_status') { const getStatusRank = (item) => { if (item.lc > 0 && item.sc >= item.lc) return 5; if (item.ss === 'DELETED') return 4; if (item.ss === 'EXPIRED') return 3; if (item.ed === -1) return 1; return 2; }; const rA = getStatusRank(a), rB = getStatusRank(b); if (rA !== rB) return (rA - rB) * dir; return compareNames(a.n, b.n) * dir; } if (sort === 'modified_time') { const tA = parseTime(a.t), tB = parseTime(b.t); if (tA !== tB) return (tB - tA) * dir; return compareNames(a.n, b.n) * dir; } if (sort === 'created_time') { const tA = parseTime(a.ct), tB = parseTime(b.ct); if (tA !== tB) return (tB - tA) * dir; return compareNames(a.n, b.n) * dir; } if (sort === 'progress') { const pctA = typeof a.pg === 'number' ? a.pg : -1; const pctB = typeof b.pg === 'number' ? b.pg : -1; if (pctA !== pctB) return (pctB - pctA) * dir; return compareNames(a.n, b.n) * dir; } if (sort === 'play_time') { if (a.pt !== b.pt) return (b.pt - a.pt) * dir; return compareNames(a.n, b.n) * dir; } if (sort === 'path') { const pathCmp = compareNames(a.p, b.p); if (pathCmp !== 0) return pathCmp * dir; return compareNames(a.n, b.n) * dir; } if (sort === 'size') { if (a.k !== b.k) return folderFirst ? (b.k - a.k) : (a.k - b.k); const sizeA = parseSize(a.s), sizeB = parseSize(b.s); if (sizeA !== sizeB) return (sizeB - sizeA) * dir; return compareNames(a.n, b.n) * dir; } if (sort === 'duration') { if (a.k !== b.k) return folderFirst ? (b.k - a.k) : (a.k - b.k); if (a.k) return compareNames(a.n, b.n) * dir; const hasDurA = a.d > 0; const hasDurB = b.d > 0; if (hasDurA !== hasDurB) return hasDurA ? -1 : 1; if (hasDurA) { if (a.d !== b.d) return (b.d - a.d) * dir; return compareNames(a.n, b.n) * dir; } const isVidA = (a.m === 1); const isVidB = (b.m === 1); if (isVidA !== isVidB) return isVidA ? -1 : 1; if (isVidA) { return compareNames(a.n, b.n); } else { const getExt = s => { const i = s.lastIndexOf('.'); return i > 0 ? s.slice(i).toLowerCase() : ''; }; const extA = getExt(a.n); const extB = getExt(b.n); const extCmp = compareNames(extA, extB); if (extCmp !== 0) { return extCmp * dir; } return compareNames(a.n, b.n) * dir; } } if (sort === 'starred') { if (a.st !== b.st) return b.st - a.st; return (parseTime(b.t) - parseTime(a.t)) * dir; } return compareNames(a.n, b.n) * dir; }); const sortedIndices = new Uint32Array(proxy.length); for (let i = 0; i < proxy.length; i++) sortedIndices[i] = proxy[i].i; self.postMessage({ indices: sortedIndices, reqId: reqId }, [sortedIndices.buffer]); }; `; const blob = new Blob([workerCode], { type: 'application/javascript' }); const workerUrl = URL.createObjectURL(blob); const sortWorker = new Worker(workerUrl); sortWorker.onmessage = (e) => { const { indices, reqId } = e.data; URL.revokeObjectURL(workerUrl); sortWorker.terminate(); if (reqId === S.sortId) { resolve(Array.from(indices).map(idx => S.display[idx])); } else resolve(null); }; const sortLocale = ({'zh':'zh-CN','tc':'zh-TW','ja':'ja','ko':'ko','en':'en','id':'id','ms':'ms'})[getLang()] || 'en'; sortWorker.postMessage({ proxy: proxyList, sort: S.sort, dir: S.dir, reqId: currentReqId, folderFirst: S.folderFirst, locale: sortLocale }); }); if (showLargeSortMask) setLoad(false); if (sortedList === null) return; S.display = sortedList; } if (currentReqId !== S.sortId) return; const currentIds = new Set(S.display.map(i => i.id)); if (S.selMode === 'all') { const nextExcluded = new Set(); for (const id of S.selEx) { if (currentIds.has(id)) nextExcluded.add(id); } S.selEx = nextExcluded; } else { const nextSelected = S.getSelectedIds().filter(id => currentIds.has(id)); S.setExplicitSelection(nextSelected); } renderList(); if (gmGet('pk_keep_pos', true) && S.latestChildId) { const targetIdx = S.display.findIndex(x => x.id === S.latestChildId); if (targetIdx !== -1) { const rowTop = getItemScrollTopByIndex(targetIdx); const vpHeight = UI.vp.clientHeight; const centerScroll = Math.max(0, rowTop - (vpHeight / 2) + (CONF.rowHeight / 2)); UI.vp.scrollTop = centerScroll; S.activeId = S.latestChildId; S.clearSelection(); S.activeId = S.latestChildId; renderVisible(); } S.latestChildId = null; } updateStat(); } function renderDupView() { if (!S.dupMode) return; clearGridStableRows(UI.in); const L = getStrings(); const showName = UI.chkName.checked; const showSim = UI.chkSim.checked; const showHash = UI.chkHash.checked; const newDisplay = []; if (!S.dupItemMap || S.dupItemMap.size !== S.items.length) { S.dupItemMap = new Map(); const len = S.items.length; for (let i = 0; i < len; i++) { S.dupItemMap.set(S.items[i].id, S.items[i]); } } const itemMap = S.dupItemMap; const getCachedPath = (item) => { if (item._cachedPath !== undefined) return item._cachedPath; let pStr = ""; if (item._lineage && item._lineage.length > 0) { pStr = item._lineage.map(node => node.name).join('/'); } else { const curFolder = S.path[S.path.length - 1]; pStr = curFolder ? curFolder.name : L.current_dir; } item._cachedPath = pStr; return pStr; }; S.dupGroups.clear(); let dupBuckets = S.dupBuckets; if (!dupBuckets || !Array.isArray(dupBuckets.all)) { dupBuckets = { all: Array.isArray(S.dupRawGroups) ? S.dupRawGroups : [], hash: [], sim: [], name: [] }; const allLen = dupBuckets.all.length; for (let i = 0; i < allLen; i++) { const g = dupBuckets.all[i]; if (g.type === L.tag_hash) dupBuckets.hash.push(g); else if (g.type === L.tag_sim) dupBuckets.sim.push(g); else if (g.type === L.tag_name) dupBuckets.name.push(g); } S.dupBuckets = dupBuckets; } if (dupBuckets.all.length === 0 && !S.dupRunning) { if (UI.btnExit) { UI.btnExit.click(); } else { S.dupMode = false; S.isFlattened = false; refresh(); } return; } if (!S.dupRunning) { let hasLiveDupGroup = false; const allGroups = dupBuckets.all || []; for (let i = 0; i < allGroups.length; i++) { const ids = allGroups[i].ids || []; let liveCount = 0; for (let k = 0; k < ids.length; k++) { if (itemMap.has(ids[k])) { liveCount++; if (liveCount >= 2) { hasLiveDupGroup = true; break; } } } if (hasLiveDupGroup) break; } if (!hasLiveDupGroup) { if (UI.btnExit) { UI.btnExit.click(); } else { S.dupMode = false; S.isFlattened = false; refresh(); } return; } } let groupsToRender = []; if (showHash) groupsToRender.push(...dupBuckets.hash); if (showSim) groupsToRender.push(...dupBuckets.sim); if (showName) groupsToRender.push(...dupBuckets.name); if (S.pinnedDupPath) { const pinnedGroups = []; const normalGroups = []; for (const g of groupsToRender) { let isPinned = false; for (const id of g.ids) { const item = itemMap.get(id); if (item) { const path = getCachedPath(item); if (path === S.pinnedDupPath) { isPinned = true; break; } } } if (isPinned) pinnedGroups.push(g); else normalGroups.push(g); } groupsToRender = [...pinnedGroups, ...normalGroups]; } const dynamicFolderStats = new Map(); const groupsLen = groupsToRender.length; const searchKey = S.search ? S.search.toLowerCase().trim() : null; const includePath = S.search && UI.chkSearchPath ? UI.chkSearchPath.checked : false; for (let gIdx = 0; gIdx < groupsLen; gIdx++) { const g = groupsToRender[gIdx]; if (g.type === L.tag_name && !showName) continue; if (g.type === L.tag_sim && !showSim) continue; if (g.type === L.tag_hash && !showHash) continue; const ids = g.ids; const idsLen = ids.length; const groupItems = []; const groupPaths = []; for (let k = 0; k < idsLen; k++) { const item = itemMap.get(ids[k]); if (item) { groupItems.push(item); groupPaths.push(getCachedPath(item)); } } if (groupItems.length < 2) continue; const activeGroupIds = groupItems.map(item => item.id); for (let k = 0; k < activeGroupIds.length; k++) { S.dupGroups.set(activeGroupIds[k], gIdx); } if (searchKey) { const isMatch = groupItems.some((item, idx) => { const nameHit = item.name.toLowerCase().includes(searchKey); let pathHit = false; if (includePath) { const rawPath = groupPaths[idx] || ""; const homeText = L.btn_nav_home; const parentPath = (rawPath === homeText || rawPath.startsWith(homeText + '/')) ? rawPath : (homeText + '/' + rawPath); const fullItemPath = parentPath.endsWith('/') ? (parentPath + (item.name || "")) : (parentPath + '/' + (item.name || "")); pathHit = fullItemPath.toLowerCase().includes(searchKey); } return nameHit || pathHit; }); if (!isMatch) continue; } const pathsLen = groupPaths.length; for (let p = 0; p < pathsLen; p++) { const pStr = groupPaths[p]; dynamicFolderStats.set(pStr, (dynamicFolderStats.get(pStr) || 0) + 1); } const firstItem = groupItems[0]; let featureStr = ""; if (firstItem) { if (g.type === L.tag_hash) { featureStr = `[ ${fmtSize(firstItem.size)} ] `; } else if (g.type === L.tag_sim) { const dur = firstItem.params?.duration || 0; featureStr = `[ ${dur > 0 ? fmtDur(dur) : '--:--'} ] `; } else if (g.type === L.tag_name) { featureStr = `[ ${L.tag_name_short} ] `; } } const baseName = firstItem ? (g.type === L.tag_name ? firstItem.name.replace(/\.[^/.]+$/, "") : firstItem.name) : `${L.str_group} ${gIdx}`; const countStr = `${activeGroupIds.length} ${L.str_items}`; const smartTitle = `${countStr} | ${featureStr}${baseName}`; newDisplay.push({ id: `grp_${gIdx}`, isHeader: true, name: smartTitle, count: activeGroupIds.length, type: g.type || L.str_group, groupIds: activeGroupIds }); const selectedDupPath = S.pinnedDupPath || ""; const compareDupGroupData = (a, b) => { const op = S.groupSortOp || 'path'; const dir = S.groupSortDir || 1; let res = 0; if (op === 'time') { res = new Date(b.it.modified_time) - new Date(a.it.modified_time); } else if (op === 'size') { const sa = BigInt(a.it.size || 0), sb = BigInt(b.it.size || 0); res = sa < sb ? 1 : (sa > sb ? -1 : 0); } else if (op === 'path') { if (a.path.length !== b.path.length) res = a.path.length - b.path.length; else res = a.path.localeCompare(b.path); } else if (op === 'name') { res = a.it.name.localeCompare(b.it.name); } return res * dir; }; const sortDupGroupChunk = arr => arr.slice().sort(compareDupGroupData); const groupData = groupItems.map((it, idx) => ({ it, path: groupPaths[idx] })); let sortedGroupData; if (selectedDupPath) { const groupA = []; const groupB = []; for (let i = 0; i < groupData.length; i++) { const row = groupData[i]; if (row.path === selectedDupPath) groupA.push(row); else groupB.push(row); } if (groupA.length > 0) { sortedGroupData = sortDupGroupChunk(groupA).concat(sortDupGroupChunk(groupB)); } else { sortedGroupData = sortDupGroupChunk(groupData); } } else { sortedGroupData = sortDupGroupChunk(groupData); } let lastFullPath = null; for (let idx = 0; idx < sortedGroupData.length; idx++) { const { it: item, path: fullPath } = sortedGroupData[idx]; item._dupFullPath = fullPath; if (idx > 0 && fullPath === lastFullPath) { item._isSameFolder = true; } else { item._isSameFolder = false; } lastFullPath = fullPath; newDisplay.push(item); } } const dupFolderWrap = document.getElementById('pk-dup-folder-sel-wrap'); if (dupFolderWrap) { dupFolderWrap.style.width = "220px"; dupFolderWrap.style.flexShrink = "0"; } UI.selDupFolder.style.width = "220px"; UI.selDupFolder.style.flexShrink = "0"; const currentSelection = UI.selDupFolder.value; const invertChk = document.getElementById('pk-dup-invert'); const invertLabel = invertChk ? invertChk.parentNode : null; if (dynamicFolderStats.size <= 1) { if (dupFolderWrap) dupFolderWrap.style.display = 'none'; UI.selDupFolder.style.display = 'none'; if (invertLabel) invertLabel.style.display = 'none'; } else { if (dupFolderWrap) dupFolderWrap.style.display = 'inline-flex'; UI.selDupFolder.style.display = 'inline-block'; if (invertLabel) invertLabel.style.display = 'inline-flex'; } let dropdownHtml = ``; if (dynamicFolderStats.size > 0) { dropdownHtml += ``; } const truncateMiddle = (str, len = 30) => { if (!str || str.length <= len * 2 + 3) return str; return str.substring(0, len) + ' ... ' + str.substring(str.length - len); }; const sortLocale = ({'zh':'zh-CN','tc':'zh-TW','ja':'ja','ko':'ko','en':'en','id':'id','ms':'ms'})[getLang()] || 'en'; const collator = new Intl.Collator(sortLocale, { numeric: true }); const sortedFolders = Array.from(dynamicFolderStats.entries()) .sort((a, b) => { const arrA = a[0].split('/'); const arrB = b[0].split('/'); const len = Math.min(arrA.length, arrB.length); for (let i = 0; i < len; i++) { const cmp = collator.compare(arrA[i], arrB[i]); if (cmp !== 0) return cmp; } return arrA.length - arrB.length; }); const optionsArr = sortedFolders.map(([path, count]) => { return ``; }); dropdownHtml += optionsArr.join(''); UI.selDupFolder.innerHTML = dropdownHtml; if (currentSelection && dynamicFolderStats.has(currentSelection)) { UI.selDupFolder.value = currentSelection; } else if (currentSelection === "__RESET__") { UI.selDupFolder.value = "__RESET__"; } else { if (S.pinnedDupPath && !dynamicFolderStats.has(S.pinnedDupPath)) { S.pinnedDupPath = null; } UI.selDupFolder.value = ""; } syncDupFolderButtonText(); S.display = newDisplay; const visibleIds = new Set(newDisplay.map(d => d.id)); for(let id of S.sel) { if (!visibleIds.has(id)) S.sel.delete(id); } if (UI.chkAll) UI.chkAll.checked = false; renderList(); updateStat(); } function parseShareInput(raw) { const text = String(raw || '').trim(); if (!text) return ''; const linkMatch = text.match(/(?:https?:\/\/)?(?:www\.)?mypikpak\.com\/s\/([A-Za-z0-9_-]{8,128})(?=[^A-Za-z0-9_-]|$)/i); if (linkMatch && linkMatch[1]) return linkMatch[1]; return /^[A-Za-z0-9_-]{8,128}$/.test(text) ? text : ''; } function parseSharePassCode(raw) { const text = String(raw || '').trim(); if (!text) return ''; const validPass = /^[A-Za-z0-9]{4,10}$/; const scan = (src, regex) => { let m; while ((m = regex.exec(src))) { const v = String(m[1] || '').trim(); if (validPass.test(v)) return v; } return ''; }; const decoded = (() => { try { return decodeURIComponent(text); } catch(e) { return text; } })(); return scan(text, /[?&#](?:s_code|pass_code|passcode|pass|pwd)=([A-Za-z0-9]{4,10})(?=&|#|\s|$)/ig) || scan(decoded, /[?&#](?:s_code|pass_code|passcode|pass|pwd)=([A-Za-z0-9]{4,10})(?=&|#|\s|$)/ig) || scan(decoded, /(?:密码|密碼|提取码|提取碼|访问码|訪問碼|口令|パスワード|パスコード|パス|コード|비밀번호|비번|암호|코드)\s*[::]?\s*([A-Za-z0-9]{4,10})(?=\s|$|[,,;;。、])/g) || scan(decoded, /\b(?:password|passcode|pass|code|kata\s+sandi|kata\s+laluan|sandi|kode|kod)\b\s*[::]?\s*([A-Za-z0-9]{4,10})(?=\s|$|[,,;;。、])/ig); } function parseShareClipboardText(text) { const raw = String(text || '').trim(); if (!raw) return null; const linkMatch = raw.match(/https?:\/\/(?:www\.)?(?:mypikpak\.com|app\.mypikpak\.com|drive\.mypikpak\.com)\/s\/([A-Za-z0-9_-]{8,128})(?=[^A-Za-z0-9_-]|$)/i); const link = linkMatch && linkMatch[1] ? linkMatch[1] : parseShareInput(raw); if (!link) return null; return { link, pass_code: parseSharePassCode(raw) }; } async function handleShareParseClipboardImport() { const L = getStrings(); if (S.shareParseLoading) return; if (!navigator.clipboard || typeof navigator.clipboard.readText !== 'function') { showToast(L.msg_share_clipboard_denied, 'warning'); return; } let text = ''; try { text = await navigator.clipboard.readText(); } catch(e) { showToast(L.msg_share_clipboard_denied, 'warning'); return; } if (!String(text || '').trim()) { showToast(L.msg_share_clipboard_empty, 'warning'); return; } const parsed = parseShareClipboardText(text); if (!parsed || !parsed.link) { showToast(L.msg_share_clipboard_no_link, 'warning'); return; } const linkInput = UI.in ? UI.in.querySelector('#pk-share-parse-link') : null; const passInput = UI.in ? UI.in.querySelector('#pk-share-parse-pass') : null; S.shareParseDraft = { raw: parsed.link, pass_code: parsed.pass_code || '' }; if (linkInput) linkInput.value = parsed.link; if (passInput) passInput.value = parsed.pass_code || ''; await handleShareParseSubmit(); } function stringifyShareParsePayload(data) { try { return JSON.stringify(data || {}); } catch(e) { return String(data || ''); } } function getShareParsePayload(data) { return data && data.data && typeof data.data === 'object' ? data.data : (data || {}); } function readOfficialErrorText(data) { const payload = getShareParsePayload(data); const task = payload.task || {}; return [payload.share_status, payload.status, payload.status_code, payload.error, payload.error_code, payload.code, payload.result, payload.phase, task.phase, payload.status_text, payload.error_description, payload.message, payload.msg].filter(v => v !== undefined && v !== null).join(' '); } function hasShareParsePassSignal(data, text = '') { const raw = `${readOfficialErrorText(data)} ${typeof data === 'string' ? data : stringifyShareParsePayload(data)} ${text}`; return /PASS[_-]?WORD|PASSWORD|PASS[_-]?CODE|PASSCODE|ACCESS[_-]?CODE|EXTRACTION[_-]?CODE|EXTRACT[_-]?CODE|S[_-]?CODE|PWD/i.test(raw) || /密码|密碼|提取码|提取碼|访问码|訪問碼|パスワード|パスコード|暗証番号|비밀번호|비번|암호|kata\s*sandi|kata\s*laluan|sandi|kode|kod/i.test(raw); } function resolveOfficialErrorByStableFields(status, data, scope = '') { const body = stringifyShareParsePayload(data); const lower = body.toLowerCase(); const payload = getShareParsePayload(data); const errCode = Number(payload.error_code || payload.code || 0); const err = String(payload.error || payload.error_code || payload.result || payload.phase || payload.status || '').toLowerCase(); const officialText = readOfficialErrorText(data); const upper = officialText.toUpperCase(); const allText = `${officialText} ${body}`; const hasExplicitError = !!(payload.error || payload.error_description || payload.status_text || (payload.error_code && Number(payload.error_code) !== 0)); const hasPassSignal = hasShareParsePassSignal(data, allText); const hasExpiredSignal = status === 410 || /EXPIRED|EXPIRE|SHARE_EXPIRED|LINK_EXPIRED/.test(upper) || lower.includes('expired') || lower.includes('expire') || /过期|過期|失效|期限切れ|有効期限|만료|kedaluwarsa|tamat\s*tempoh|luput/i.test(allText); const hasCancelSignal = /DELETED|DELETE|CANCEL|CANCELLED|REMOVED/.test(upper) || lower.includes('deleted') || lower.includes('cancel') || /取消|删除|刪除|削除|キャンセル|취소|삭제|dibatalkan|dihapus|dipadam/i.test(allText); if (scope === 'share_parse') { if (hasPassSignal && (status === 400 || status === 401 || status === 403 || /PASS[_-]?WORD|PASSWORD|PASS[_-]?CODE|PASSCODE/.test(upper))) return 'msg_share_parse_bad_pass'; if (status === 401 || status === 403) return 'msg_share_parse_auth_error'; if (status === 404 || lower.includes('not_found') || lower.includes('not found')) return 'msg_share_parse_not_found'; if (hasExpiredSignal) return 'msg_share_parse_expired'; if (hasCancelSignal) return 'msg_share_parse_cancelled'; if (status && status >= 400) return 'msg_share_parse_unknown_error'; if (hasExplicitError) return hasPassSignal ? 'msg_share_parse_bad_pass' : 'msg_share_parse_unknown_error'; } if (scope === 'share_save') { if (err === 'file_restore_own' || errCode === 9 || /FILE_RESTORE_OWN|RESTORE_OWN|OWN_SHARE|SAVE_OWN/.test(upper) || lower.includes('file_restore_own') || lower.includes('restore_own') || lower.includes('own_share') || lower.includes('save_own') || /自己的分享|自己的共享|自分の共有|본인 공유|share sendiri|perkongsian sendiri/i.test(allText)) return 'msg_share_parse_save_own'; if (err === 'file_space_not_enough' || errCode === 8 || /FILE_SPACE_NOT_ENOUGH|SPACE_NOT_ENOUGH|QUOTA|INSUFFICIENT/.test(upper) || lower.includes('space_not_enough') || lower.includes('quota') || /空间|空間|容量|ストレージ|저장 공간|ruang|storan|penyimpanan/i.test(allText)) return 'msg_share_parse_save_space_not_enough'; if (/TOO_MANY|FILE_COUNT|EXCEED|LIMIT_EXCEEDED/.test(upper) || lower.includes('too_many') || lower.includes('file_count') || lower.includes('exceed') || /数量过多|數量過多|超出|多すぎ|초과|terlalu banyak|melebihi|melampaui/i.test(allText)) return 'msg_share_parse_save_too_many'; if (lower.includes('partial') || lower.includes('failed_file') || lower.includes('fail_list') || lower.includes('failed_files')) return 'msg_share_parse_save_partial_failed'; if (/pass_code_token|access_token|token/i.test(body) && /expired|expire|invalid|unauthorized|forbidden/i.test(body)) return 'msg_share_parse_save_token_expired'; if (status === 401) return 'msg_share_parse_save_auth_error'; if (status === 403) return /pass_code_token|token/i.test(body) ? 'msg_share_parse_save_token_expired' : 'msg_share_parse_save_auth_error'; if (status === 404 || hasExpiredSignal || lower.includes('not_found') || lower.includes('not found')) return 'msg_share_parse_save_share_expired'; if (hasCancelSignal) return 'msg_share_parse_save_cancelled'; if (errCode && errCode !== 0) return 'msg_share_parse_save_failed'; if (String(payload.phase || '').toUpperCase() === 'PHASE_TYPE_ERROR') return 'msg_share_parse_save_task_failed'; } return ''; } function resolveShareParseErrorKey(status, data) { return resolveOfficialErrorByStableFields(status, data, 'share_parse'); } function makeShareParseError(key, status = 0, data = null) { const err = new Error(key || 'msg_share_parse_failed'); err.shareParseKey = key || 'msg_share_parse_failed'; err.status = status; err.data = data; return err; } async function fetchShareParseInfo(shareId, passCode) { const headers = getHeaders(); if (!headers.Authorization || headers.Authorization.length < 10) throw makeShareParseError('msg_share_parse_auth_error'); const url = `https://api-drive.mypikpak.com/drive/v1/share?share_id=${encodeURIComponent(shareId)}&pass_code=${encodeURIComponent(passCode || '')}`; const res = await fetch(url, { method: 'GET', headers }); syncTime(res.headers); if (!res.ok) { const bodyText = await res.text().catch(() => ''); let errData = {}; try { errData = bodyText ? JSON.parse(bodyText) : {}; } catch(e) { errData = { raw_text: bodyText }; } throw makeShareParseError(resolveShareParseErrorKey(res.status, errData), res.status, errData); } const data = await res.json(); const key = resolveShareParseErrorKey(0, data); if (key) throw makeShareParseError(key, 200, data); return data; } function readShareParseUserText(raw) { const data = getShareParsePayload(raw); const user = data.share_user || data.share_user_info || data.user || data.creator || data.owner || data.user_info || {}; if (typeof user === 'string') return user; return data.share_user_name || data.user_name || data.nickname || user.nickname || user.name || user.user_name || user.username || user.email || user.id || ''; } function readShareParseTitle(raw) { const data = getShareParsePayload(raw); const share = data.share || data.share_info || {}; const file = data.file || data.file_info || {}; return data.title || data.share_title || data.name || data.file_name || share.title || share.name || file.name || ''; } function buildShareParseInfo(shareId, passCode, rawInfo) { const root = rawInfo || {}; const data = getShareParsePayload(root); return { share_id: shareId, pass_code: passCode || '', pass_code_token: data.pass_code_token || root.pass_code_token || '', share_title: readShareParseTitle(root), share_user: readShareParseUserText(root), raw_share_info: root, parsed_at: Date.now() }; } function getShareParseErrorMessage(err) { const L = getStrings(); if (err && err.shareParseKey && L[err.shareParseKey]) return L[err.shareParseKey]; if (err && (err.name === 'TypeError' || err.name === 'AbortError')) return L.msg_share_parse_network_error; return L.msg_share_parse_failed || L.msg_share_parse_unknown_error; } function clearShareParseListMirror() { const hasShareMirror = Array.isArray(S.items) && S.items.some(item => item && item._isShareItem); if (S.shareParseMode || hasShareMirror) { S.items = []; S.display = []; if (S.itemMap && typeof S.itemMap.clear === 'function') S.itemMap.clear(); } if (S.sel && typeof S.sel.clear === 'function') S.sel.clear(); if (S.selEx && typeof S.selEx.clear === 'function') S.selEx.clear(); S.selMode = 'explicit'; S.activeId = null; S.lastSelIdx = -1; } function syncShareParseMirror() { if (syncShareParseInsightMirror()) return; if (!(S.shareParseMode && S.shareParseListActive)) { clearShareParseListMirror(); return; } const items = Array.isArray(S.shareParseItems) ? S.shareParseItems : []; const baseDisplay = Array.isArray(S.shareParseDisplay) ? S.shareParseDisplay : items; const q = String(S.search || '').trim().toLowerCase(); const includePath = !!(UI.chkSearchPath && UI.chkSearchPath.checked); const pathQ = normalizeSharePathSearchText(q); const display = sortShareParseDisplayList(q ? baseDisplay.filter(item => { if (!item || item.isHeader) return false; const name = String(item.name || '').toLowerCase(); if (name.includes(q)) return true; if (!includePath) return false; const path = normalizeSharePathSearchText(item._shareFilePathText || item._pathStr || getSharePathText(item._sharePath) || ''); return path.includes(pathQ); }) : baseDisplay); if (!S.shareParseItemMap || typeof S.shareParseItemMap.clear !== 'function') S.shareParseItemMap = new Map(); S.shareParseItemMap.clear(); items.forEach(item => { if (item && item.id) S.shareParseItemMap.set(item.id, item); }); S.items = items; S.display = display; if (S.itemMap && typeof S.itemMap.clear === 'function') { S.itemMap.clear(); display.forEach(item => { if (item && item.id && !item.isHeader) S.itemMap.set(item.id, item); }); } } function resetShareParseListState(clearInfo = false) { S.shareParseListReqId = (S.shareParseListReqId || 0) + 1; if (!S.shareParseSaving) { S.shareParseSaveReqId = (S.shareParseSaveReqId || 0) + 1; S.shareParseSaveError = ''; S.shareParseSaveResult = null; } S.shareParseListActive = false; S.shareParseItems = []; S.shareParseDisplay = []; S.shareParseItemMap = new Map(); S.shareParsePath = []; S.shareParseCurParentId = ''; S.shareParseNextPageToken = ''; cancelShareParseAutoPaging(); S.shareParseListLoading = false; S.shareParseListError = ''; S.shareParseLatestChildId = null; S.resetShareParseSideNav(); resetShareParseInsightState(true); if (clearInfo) S.shareParseInfo = null; clearShareParseListMirror(); updateShareParseInsightButtons(); } function ensureShareParseFolderCache() { if (!(S.shareParseFolderCache instanceof Map)) S.shareParseFolderCache = new Map(); return S.shareParseFolderCache; } function getShareParseFolderCacheScopeKey() { const info = S.shareParseInfo || {}; const shareId = String(info.share_id || ''); if (!shareId) return ''; return JSON.stringify([shareId, String(info.pass_code || '')]); } function getShareParseFolderCacheKey(parentId = '') { const scope = getShareParseFolderCacheScopeKey(); if (!scope) return ''; return `${scope}|${String(parentId || '')}`; } function cloneShareParseCacheItem(item) { const copy = Object.assign({}, item || {}); if (copy.params && typeof copy.params === 'object') copy.params = Object.assign({}, copy.params); if (Array.isArray(copy.medias)) copy.medias = copy.medias.slice(); if (Array.isArray(copy._shareAncestorIds)) copy._shareAncestorIds = copy._shareAncestorIds.slice(); if (Array.isArray(copy._sharePath)) copy._sharePath = S.cloneShareParsePath(copy._sharePath); if (copy._rawShareFile && typeof copy._rawShareFile === 'object') copy._rawShareFile = Object.assign({}, copy._rawShareFile); const info = S.shareParseInfo || {}; if (info.share_id) copy._shareId = info.share_id; if (info.pass_code_token) copy._passCodeToken = info.pass_code_token; return copy; } function readShareParseFolderCache(parentId = '') { const key = getShareParseFolderCacheKey(parentId); if (!key) return null; const cache = ensureShareParseFolderCache(); const hit = cache.get(key); if (!hit) return null; cache.delete(key); cache.set(key, hit); return { parentId: hit.parentId || '', path: S.cloneShareParsePath(hit.path || []), items: (Array.isArray(hit.items) ? hit.items : []).map(cloneShareParseCacheItem), nextPageToken: hit.nextPageToken || '', complete: !!hit.complete, savedAt: hit.savedAt || 0 }; } function writeShareParseFolderCacheEntry(parentId = '', pathNodes = [], items = [], nextPageToken = '') { const key = getShareParseFolderCacheKey(parentId); if (!key) return; const cache = ensureShareParseFolderCache(); if (cache.has(key)) cache.delete(key); cache.set(key, { parentId: String(parentId || ''), path: S.cloneShareParsePath(pathNodes || []), items: (Array.isArray(items) ? items : []).map(cloneShareParseCacheItem), nextPageToken: nextPageToken || '', complete: !nextPageToken, savedAt: Date.now() }); } function writeShareParseFolderCache(parentId = '') { writeShareParseFolderCacheEntry( parentId, S.shareParsePath || [], S.shareParseItems || [], S.shareParseNextPageToken || '' ); } function restoreShareParseFolderScroll(opts = {}, append = false) { if (append) return; if (Number.isFinite(opts.shareParseRestoreScrollTop) && UI.vp) { S.shareParseLatestChildId = null; const top = Math.max(0, opts.shareParseRestoreScrollTop); requestAnimationFrame(() => { if (!UI.vp) return; const maxTop = Math.max(0, UI.vp.scrollHeight - UI.vp.clientHeight); UI.vp.scrollTop = Math.min(top, maxTop); if (typeof renderVisible === 'function') renderVisible(); }); } else if (gmGet('pk_keep_pos', true) && S.shareParseLatestChildId && UI.vp) { const childId = S.shareParseLatestChildId; S.shareParseLatestChildId = null; requestAnimationFrame(() => { if (!S.shareParseMode || !UI.vp) return; const targetIdx = (S.display || []).findIndex(x => x && x.id === childId); if (targetIdx === -1) return; const rowTop = getItemScrollTopByIndex(targetIdx); const centerScroll = Math.max(0, rowTop - (UI.vp.clientHeight / 2) + (CONF.rowHeight / 2)); UI.vp.scrollTop = centerScroll; S.clearSelection(); S.activeId = childId; if (typeof renderVisible === 'function') renderVisible(); }); } else { S.shareParseLatestChildId = null; } } function updateShareParseSaveButton() { const L = getStrings(); const btn = UI.btnShareParseSave; if (!btn) return; const visible = !!(S.shareParseMode && S.shareParseListActive); btn.style.display = visible ? 'inline-flex' : 'none'; btn.disabled = !visible || !!S.shareParseSaving; btn.setAttribute('data-pk-tip', S.shareParseSaving ? L.btn_share_parse_saving : L.btn_share_parse_save_selected); const span = btn.querySelector('span'); if (span) span.textContent = S.shareParseSaving ? L.btn_share_parse_saving : L.btn_share_parse_save_selected; updateShareParseInsightButtons(); } function resetShareParseInsightState(clearData = true) { S.shareParseInsightReqId = (S.shareParseInsightReqId || 0) + 1; S.shareParseInsightMode = false; S.shareParseInsightScanning = false; S.shareParseInsightStop = true; S.shareParseInsightError = ''; S.shareParseInsightProgress = { folder: 0, file: 0 }; S.shareParseInsightToastStopped = false; if (clearData) { S.shareParseInsightItems = []; S.shareParseInsightDisplay = []; S.shareParseInsightItemMap = new Map(); S.shareParseInsightFilterState = { active: false, cat: 'all', ext: 'all' }; S.shareParseInsightReturnPath = []; S.shareParseInsightReturnParentId = ''; } } function setSearchWrapVisible(visible) { const box = UI.searchInput ? (UI.searchInput.closest('.pk-search') || UI.searchInput.parentNode) : null; if (box) box.style.setProperty('display', visible ? 'flex' : 'none', 'important'); } function clearShareParseSearch() { S.search = ''; if (UI.searchInput) UI.searchInput.value = ''; if (UI.searchClear) UI.searchClear.style.display = 'none'; if (UI.chkSearchPath) UI.chkSearchPath.checked = false; } function sortShareParseDisplayList(list) { const src = Array.isArray(list) ? list.slice() : []; const sort = S.sort || 'modified_time'; const dir = S.dir || 1; const collator = new Intl.Collator(({'zh':'zh-CN','tc':'zh-TW','ja':'ja','ko':'ko','en':'en','id':'id','ms':'ms'})[getLang()] || undefined, { numeric: true, sensitivity: 'base', caseFirst: 'lower' }); const charWeight = (str) => { const c = String(str || '').charAt(0); if (/[0-9]/.test(c)) return 20; if (/[\u4e00-\u9fa5]/.test(c)) return 30; if (/[a-zA-Z]/.test(c)) return 40; return 10; }; const cmpName = (a, b) => { const aa = String(a || ''), bb = String(b || ''); const wa = charWeight(aa), wb = charWeight(bb); return wa !== wb ? wa - wb : collator.compare(aa, bb); }; const asSize = v => parseInt(v || 0, 10) || 0; const asTime = v => v ? new Date(v).getTime() || 0 : 0; const asDur = item => parseFloat((item && item.params && item.params.duration) || item.duration || item.media_duration || 0) || 0; const asPath = item => String((item && (item._shareFilePathText || item._pathStr)) || (item && item._sharePath ? getSharePathText(item._sharePath) : '') || ''); const extGroup = item => { const ext = String(item && item.name || '').split('.').pop().toLowerCase(); if (['mp4','mkv','avi','mov','wmv','flv','webm','ts','m4v','3gp'].includes(ext)) return 1; if (['jpg','jpeg','png','gif','bmp','svg','webp','tiff','ico'].includes(ext)) return 2; return 3; }; src.sort((a, b) => { const fa = a && a.kind === 'drive#folder' ? 1 : 0; const fb = b && b.kind === 'drive#folder' ? 1 : 0; if (S.folderFirst && fa !== fb) return fb - fa; if (sort === 'path') { const pc = cmpName(asPath(a), asPath(b)); if (pc !== 0) return pc * dir; return cmpName(a && a.name, b && b.name) * dir; } if (sort === 'size') { if (fa !== fb) return S.folderFirst ? (fb - fa) : (fa - fb); const sa = asSize(a && a.size), sb = asSize(b && b.size); if (sa !== sb) return (sb - sa) * dir; return cmpName(a && a.name, b && b.name) * dir; } if (sort === 'duration') { if (fa !== fb) return S.folderFirst ? (fb - fa) : (fa - fb); if (fa) return cmpName(a && a.name, b && b.name) * dir; const da = asDur(a), db = asDur(b); const ha = da > 0, hb = db > 0; if (ha !== hb) return ha ? -1 : 1; if (ha && da !== db) return (db - da) * dir; const ga = extGroup(a), gb = extGroup(b); if (ga !== gb) return ga - gb; return cmpName(a && a.name, b && b.name) * dir; } if (sort === 'modified_time') { const ta = asTime(a && a.modified_time), tb = asTime(b && b.modified_time); if (ta !== tb) return (tb - ta) * dir; return cmpName(a && a.name, b && b.name) * dir; } return cmpName(a && a.name, b && b.name) * dir; }); return src; } function filterShareInsightItemsByCategory(items) { const state = S.shareParseInsightFilterState || { active: false, cat: 'all', ext: 'all' }; if (!state.active || state.cat === 'all') return Array.isArray(items) ? items.slice() : []; const fExts = { video: ['mp4','mkv','avi','mov','wmv','flv','webm','ts','m4v','3gp','mpg','mpeg','rm','rmvb','asf','vob','dat','divx','f4v','m2ts','mts','tp','trp','ogv','mpe','m2v','m3u8'], audio: ['mp3','wav','flac','aac','ogg','wma','ape','m4a','amr','opus','m4b','alac','aiff','mid','midi','ra','dts','ac3','dsf','dff'], image: ['jpg','jpeg','png','gif','bmp','webp','svg','tif','tiff','ico','heic','heif','raw','cr2','nef','arw','dng','orf','avif','psd','ai','eps','jfif','jpe'], document: ['txt','html','pdf','pptx','chm','docx','xlsx','htm','doc','dwg','mdb','ppt','xls','rtf','odt','ods','odp','epub','mobi','azw3','djvu','cbz','cbr','md','log','csv','xml','json'], software: ['apk','exe','ipa','dmg','rpm','deb','msi','pkg','xapk','apks','aab','jar','bin','sh','bat','cmd'], archive: ['zip','rar','7z','tar','gz','iso','cab','bz2','xz','tgz','wim','esd','img','zst','lzh'], torrent: ['torrent'] }; const definedExts = new Set([...fExts.video, ...fExts.audio, ...fExts.image, ...fExts.document, ...fExts.software, ...fExts.archive, ...fExts.torrent]); const matchExts = state.ext === 'all' ? (fExts[state.cat] || []) : [state.ext]; return (Array.isArray(items) ? items : []).filter(item => { if (!item || item.kind === 'drive#folder') return false; const ext = String(item.name || '').toLowerCase().split('.').pop(); return state.cat === 'other' ? !definedExts.has(ext) : matchExts.includes(ext); }); } function syncShareParseInsightMirror() { if (!(S.shareParseMode && S.shareParseListActive && S.shareParseInsightMode)) return false; const items = Array.isArray(S.shareParseInsightItems) ? S.shareParseInsightItems : []; const baseDisplay = filterShareInsightItemsByCategory(items); const q = String(S.search || '').trim().toLowerCase(); const includePath = !!(UI.chkSearchPath && UI.chkSearchPath.checked); const pathQ = normalizeSharePathSearchText(q); const display = sortShareParseDisplayList(q ? baseDisplay.filter(item => { if (!item || item.isHeader) return false; const name = String(item.name || '').toLowerCase(); if (name.includes(q)) return true; if (!includePath) return false; const path = normalizeSharePathSearchText(item._shareFilePathText || item._pathStr || getSharePathText(item._sharePath) || ''); return path.includes(pathQ); }) : baseDisplay); if (!S.shareParseInsightItemMap || typeof S.shareParseInsightItemMap.clear !== 'function') S.shareParseInsightItemMap = new Map(); S.shareParseInsightItemMap.clear(); items.forEach(item => { if (item && item.id) S.shareParseInsightItemMap.set(item.id, item); }); S.items = items; S.display = display; if (S.itemMap && typeof S.itemMap.clear === 'function') { S.itemMap.clear(); display.forEach(item => { if (item && item.id && !item.isHeader) S.itemMap.set(item.id, item); }); } return true; } function formatShareParseInsightProgress() { const L = getStrings(); const p = S.shareParseInsightProgress || { folder: 0, file: 0 }; return (L.msg_share_parse_scan_progress || '').replace('{folder}', p.folder || 0).replace('{file}', p.file || 0); } function syncSearchPathTooltipByTextState() { const con = UI && UI.lblSearchPath; if (!con) return; const L = getStrings(); const shortTxt = con.querySelector('.pk-txt-short'); const longTxt = con.querySelector('.pk-txt-long'); const shortVisible = !!(shortTxt && getComputedStyle(shortTxt).display !== 'none' && (!longTxt || getComputedStyle(longTxt).display === 'none')); if (shortVisible) { con.setAttribute('data-pk-tip', L.lbl_search_path); } else { con.removeAttribute('data-pk-tip'); con.removeAttribute('title'); } } function syncShareParseInsightExitCompact() { const btn = UI && UI.btnShareParseBackList; if (!btn) return; btn.classList.remove('pk-share-exit-compact'); } function syncShareParseInsightSearchPathLabel() { const con = UI && UI.lblSearchPath; if (con) { con.classList.remove('pk-share-path-compact'); syncSearchPathTooltipByTextState(); } syncShareParseInsightExitCompact(); } function updateShareParseInsightButtons() { const L = getStrings(); const inList = !!(S.shareParseMode && S.shareParseListActive && S.shareParseInfo && S.shareParseInfo.pass_code_token); const insightMode = !!S.shareParseInsightMode; const scanning = !!S.shareParseInsightScanning; if (UI.btnShareParseInsight) { const visible = inList && !insightMode && !scanning; UI.btnShareParseInsight.style.display = visible ? 'inline-flex' : 'none'; UI.btnShareParseInsight.disabled = !visible; UI.btnShareParseInsight.setAttribute('data-pk-tip', L.btn_share_parse_insight); UI.btnShareParseInsight.style.marginLeft = '8px'; UI.btnShareParseInsight.style.marginRight = '0'; const span = UI.btnShareParseInsight.querySelector('span'); if (span) span.textContent = L.btn_share_parse_insight; } if (UI.btnShareParseStopScan) { const visible = inList && insightMode && scanning; UI.btnShareParseStopScan.style.display = visible ? 'inline-flex' : 'none'; UI.btnShareParseStopScan.disabled = !visible; UI.btnShareParseStopScan.setAttribute('data-pk-tip', L.btn_share_parse_stop_scan); } if (UI.btnShareParseBackList) { const visible = inList && insightMode && !scanning; UI.btnShareParseBackList.style.display = visible ? 'inline-flex' : 'none'; UI.btnShareParseBackList.disabled = !visible; UI.btnShareParseBackList.style.marginLeft = '8px'; UI.btnShareParseBackList.style.color = '#d93025'; UI.btnShareParseBackList.setAttribute('data-pk-tip', L.btn_exit); UI.btnShareParseBackList.removeAttribute('title'); UI.btnShareParseBackList.innerHTML = `${CONF.icons.close} ${L.btn_exit}`; UI.btnShareParseBackList.classList.toggle('pk-share-exit-cold', visible); UI.btnShareParseBackList.onpointerenter = () => UI.btnShareParseBackList.classList.remove('pk-share-exit-cold'); if (!visible) UI.btnShareParseBackList.classList.remove('pk-share-exit-compact', 'pk-share-exit-cold'); } syncShareParseInsightSearchPathLabel(); } function isShareDetailFolder(raw) { raw = raw || {}; const mime = raw.mime_type || raw.mimeType || raw.content_type || ''; const kind = String(raw.kind || '').toLowerCase(); return raw.kind === 'drive#folder' || kind === 'folder' || kind.includes('folder') || mime === 'application/x-directory'; } function getShareDetailFileId(raw, parentId = '', index = 0) { raw = raw || {}; const name = raw.name || raw.file_name || raw.title || ''; return String(raw.id || raw.file_id || raw.fileId || raw.resource_id || raw.resourceId || `share_${parentId || 'root'}_${index}_${name}`); } function getSharePathText(pathNodes) { const L = getStrings(); const nodes = (Array.isArray(pathNodes) ? pathNodes : []).filter(n => n && !n._sharePanel); const text = nodes.map(n => n.name || '').filter(Boolean).join(' / '); return text || L.label_share_parse_path_root; } function normalizeSharePathSearchText(text) { return String(text || '').toLowerCase().replace(/[\\/]+/g, '/').replace(/\s*\/\s*/g, '/'); } function mapShareInsightFile(file, index, pathNodes, parentId) { const raw = file || {}; const id = getShareDetailFileId(raw, parentId, index); const name = raw.name || raw.file_name || raw.title || ''; const mime = raw.mime_type || raw.mimeType || raw.content_type || ''; const source = { ...raw, id, kind: 'drive#file', name, parent_id: parentId || raw.parent_id || raw.parentId || '', size: raw.size || raw.file_size || 0, mime_type: mime, thumbnail_link: raw.thumbnail_link || raw.thumbnailLink || raw.thumbnail || '', icon_link: raw.icon_link || raw.iconLink || raw.icon || '', modified_time: raw.modified_time || raw.modifiedTime || raw.updated_time || raw.created_time || '', hash: raw.hash || raw.gcid || raw.md5_checksum || '', tags: [], starred: false, trashed: false, _pkSkipDurationProbe: true }; const mapped = minifyFile(source, false); const curPath = Array.isArray(pathNodes) && pathNodes.length ? pathNodes.slice() : buildShareParseRootPath(); const ancestorIds = curPath.map(n => n && n.id).filter(idv => idv && idv !== 'share_parse_panel'); const pathText = getSharePathText(curPath); return Object.assign(mapped, { _isShareItem: true, _isShareInsightItem: true, _shareId: (S.shareParseInfo && S.shareParseInfo.share_id) || '', _passCodeToken: (S.shareParseInfo && S.shareParseInfo.pass_code_token) || '', _shareParentId: parentId || raw.parent_id || raw.parentId || '', _shareAncestorIds: ancestorIds, _sharePath: curPath, _shareFilePathText: pathText, _rawShareFile: raw, _pkSkipDurationProbe: true }); } async function scanShareParseInsight(reqId, targets = null, silent = false) { const currentPath = Array.isArray(S.shareParsePath) && S.shareParsePath.length ? S.shareParsePath.slice() : buildShareParseRootPath(); const currentParentId = S.shareParseCurParentId || getShareParseRootParentId(); const selected = Array.isArray(targets) ? targets.filter(item => item && item._isShareItem && item.id) : []; const queue = []; const result = []; const resultKeys = new Set(); const pushInsightFile = (file, pathNodes, parentId) => { const item = mapShareInsightFile(file, result.length, pathNodes, parentId || ''); const key = `${item._shareParentId || parentId || ''}/${item.id || item.name || result.length}`; if (resultKeys.has(key)) return; resultKeys.add(key); result.push(item); }; if (selected.length) { selected.forEach(item => { const parentId = item._shareParentId || item.parent_id || ''; const itemPath = Array.isArray(item._sharePath) && item._sharePath.length ? item._sharePath.slice() : (item.kind === 'drive#folder' ? currentPath.concat([{ id: item.id, name: item.name || '', parent_id: parentId }]) : currentPath); if (item.kind === 'drive#folder') queue.push({ id: item.id, path: itemPath }); else pushInsightFile(item._rawShareFile || item, itemPath, parentId); }); } else { queue.push({ id: currentParentId || '', path: currentPath }); } let scannedFolders = 0; const startedAt = Date.now(); const syncInsightProgress = () => { S.shareParseInsightProgress = { folder: scannedFolders, file: result.length }; if (silent) { const sec = Math.max(1, Math.round((Date.now() - startedAt) / 1000)); const speed = Math.max(1, Math.round(scannedFolders / sec)); updateLoadTxt(`${L.str_scanning} ${scannedFolders} ${L.unit_folders} | ${L.str_files}: ${result.length} | ${L.str_speed}: ${speed}`); } else { S.shareParseInsightItems = result.slice(); S.shareParseInsightDisplay = S.shareParseInsightItems.slice(); syncShareParseInsightMirror(); renderShareParseCurrentView(); } }; const canContinueShareParseInsight = () => S.shareParseMode && (silent || S.shareParseInsightMode) && S.shareParseInsightReqId === reqId && !S.shareParseInsightStop; const scanConcurrency = Math.max(1, Math.min(128, parseInt(CONF.shareParseInsightConcurrency || 128, 10) || 128)); const scanShareParseInsightFolder = async (node) => { const parentId = node.id || ''; const directItems = []; const consumeCachedItems = (items) => { (Array.isArray(items) ? items : []).forEach((item, idx) => { const raw = item && item._rawShareFile ? item._rawShareFile : item; const itemId = item && item.id ? item.id : getShareDetailFileId(raw, parentId, idx); const name = (item && item.name) || (raw && (raw.name || raw.file_name || raw.title)) || ''; if ((item && item.kind === 'drive#folder') || isShareDetailFolder(raw)) { const itemPath = Array.isArray(item && item._sharePath) && item._sharePath.length ? S.cloneShareParsePath(item._sharePath) : node.path.concat([{ id: itemId, name, parent_id: parentId }]); queue.push({ id: itemId, path: itemPath }); } else { pushInsightFile(raw || item, node.path, parentId); } }); }; const consumeRawFiles = (files) => { const baseIndex = directItems.length; (Array.isArray(files) ? files : []).forEach((file, idx) => { const cacheItem = mapShareDetailFile(file, baseIndex + idx, { parentId, path: node.path }); directItems.push(cacheItem); const itemId = getShareDetailFileId(file, parentId, baseIndex + idx); const name = file.name || file.file_name || file.title || ''; if (isShareDetailFolder(file)) { queue.push({ id: itemId, path: node.path.concat([{ id: itemId, name, parent_id: parentId }]) }); } else { pushInsightFile(file, node.path, parentId); } }); }; let pageToken = ''; const cached = readShareParseFolderCache(parentId); if (cached) { directItems.push(...cached.items); consumeCachedItems(cached.items); pageToken = cached.nextPageToken || ''; syncInsightProgress(); if (!pageToken) { scannedFolders++; syncInsightProgress(); return; } } do { if (!canContinueShareParseInsight()) break; const raw = await fetchShareDetail(parentId, pageToken || ''); if (!canContinueShareParseInsight()) break; consumeRawFiles(getShareDetailFiles(raw)); syncInsightProgress(); pageToken = getShareDetailNextToken(raw); writeShareParseFolderCacheEntry(parentId, node.path, directItems, pageToken || ''); if (pageToken) await sleep(30); } while (pageToken && canContinueShareParseInsight()); scannedFolders++; syncInsightProgress(); }; await new Promise((resolve, reject) => { let running = 0; let settled = false; const pump = () => { if (settled) return; if (!canContinueShareParseInsight()) { settled = true; resolve(); return; } while (running < scanConcurrency && queue.length && canContinueShareParseInsight()) { const node = queue.shift(); running++; scanShareParseInsightFolder(node).then(() => { running--; pump(); }).catch(err => { if (settled) return; settled = true; reject(err); }); } if (!queue.length && running === 0) { settled = true; resolve(); } }; pump(); }); return result; } function filterShareParseInsightItemsByScanRule(items, scanFilter) { const src = Array.isArray(items) ? items.slice() : []; const rawTotal = src.length; const hasActiveFilter = !!(scanFilter && ((scanFilter.keyword || '').trim() || scanFilter.minBytes > 0 || scanFilter.maxBytes > 0)); if (!hasActiveFilter) return { items: src, rawTotal, hasActiveFilter }; const kwList = String(scanFilter.keyword || '').toLowerCase().split(/[,,]/).map(k => k.trim()).filter(k => k); const minBytes = Math.max(0, parseInt(scanFilter.minBytes || 0, 10) || 0); const maxBytes = Math.max(0, parseInt(scanFilter.maxBytes || 0, 10) || 0); return { items: src.filter(item => { if (kwList.length > 0) { const fullLowerName = String(item && item.name || '').toLowerCase(); const lastDot = fullLowerName.lastIndexOf('.'); const nameWithoutExt = lastDot > 0 ? fullLowerName.substring(0, lastDot) : fullLowerName; if (kwList.some(k => nameWithoutExt.includes(k))) return false; } const sz = parseInt(item && item.size || 0, 10) || 0; if (sz < minBytes) return false; if (maxBytes > 0 && sz > maxBytes) return false; return true; }), rawTotal, hasActiveFilter }; } function openShareParseInsightScanModal(selectedTargets = null) { const L = getStrings(); const shareTargets = Array.isArray(selectedTargets) ? selectedTargets.filter(item => item && item._isShareItem && item.id).slice() : []; const lastMin = gmGet('pk_scan_last_min', 0); const lastMax = gmGet('pk_scan_last_max', ''); const lastUnit = gmGet('pk_scan_last_unit', 'MB'); const lastKeyword = gmGet('pk_scan_last_keyword', ''); const targetDesc = shareTargets.length > 0 ? L.lbl_scan_selected.replace('{n}', shareTargets.length) : L.lbl_scan_current; const inputNonce = `${Date.now()}_${Math.random().toString(36).slice(2)}`; const m = showModal(`

${L.btn_share_parse_insight}

${targetDesc}
${L.lbl_keyword_filter}
${L.lbl_ana_min}
${CONF.crumbIcons.down.replace('points="6 9 12 15 18 9"', 'points="18 15 12 9 6 15"')}
${CONF.crumbIcons.down}
-
${L.lbl_ana_max}
${CONF.crumbIcons.down.replace('points="6 9 12 15 18 9"', 'points="18 15 12 9 6 15"')}
${CONF.crumbIcons.down}
${lastUnit}
MB
GB
TB
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { Object.assign(modalBox.style, { width: 'auto', padding: '0', overflow: 'visible', height: 'auto', minHeight: 'auto' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '26px', right: '26px' }); } const inpMin = m.querySelector('#spis_val_min'); const inpMax = m.querySelector('#spis_val_max'); const unitBtn = m.querySelector('#spis_unit_btn'); const unitMenu = m.querySelector('#spis_unit_menu'); const unitTxt = m.querySelector('#spis_unit_txt'); let currentUnit = lastUnit; const syncLimitBorder = (inp) => { if (!inp) return; inp.style.borderColor = String(inp.value || '').trim() ? 'var(--pk-pri)' : 'var(--pk-bd)'; }; [inpMin, inpMax].forEach(inp => { syncLimitBorder(inp); inp.addEventListener('input', () => syncLimitBorder(inp)); }); m.querySelector('#spis_inc_min').onclick = (e) => { e.stopPropagation(); inpMin.value = (parseInt(inpMin.value) || 0) + 1; inpMin.dispatchEvent(new Event('input')); }; m.querySelector('#spis_dec_min').onclick = (e) => { e.stopPropagation(); inpMin.value = Math.max(0, (parseInt(inpMin.value) || 1) - 1); inpMin.dispatchEvent(new Event('input')); }; m.querySelector('#spis_inc_max').onclick = (e) => { e.stopPropagation(); inpMax.value = (parseInt(inpMax.value) || 0) + 1; inpMax.dispatchEvent(new Event('input')); }; m.querySelector('#spis_dec_max').onclick = (e) => { e.stopPropagation(); inpMax.value = Math.max(0, (parseInt(inpMax.value) || 1) - 1); inpMax.dispatchEvent(new Event('input')); }; unitBtn.onclick = (e) => { e.stopPropagation(); unitMenu.style.display = unitMenu.style.display === 'block' ? 'none' : 'block'; }; m.querySelectorAll('#spis_unit_menu .pk-ana-item').forEach(item => { item.onclick = (e) => { e.stopPropagation(); m.querySelectorAll('#spis_unit_menu .pk-ana-item').forEach(i => i.classList.remove('act')); item.classList.add('act'); currentUnit = item.dataset.v; unitTxt.textContent = currentUnit; unitMenu.style.display = 'none'; }; }); const closeMenu = () => { if (unitMenu) unitMenu.style.display = 'none'; }; setTimeout(() => document.addEventListener('click', closeMenu), 0); const orgRemove = m.remove.bind(m); m.remove = () => { document.removeEventListener('click', closeMenu); orgRemove(); }; const saveShareInsightScanInputs = () => { gmSet('pk_scan_last_min', parseInt(inpMin.value) || 0); gmSet('pk_scan_last_max', inpMax.value.trim()); gmSet('pk_scan_last_unit', currentUnit); gmSet('pk_scan_last_keyword', m.querySelector('#spis_keyword').value.trim()); }; m.querySelector('#spis_cancel').onclick = () => { saveShareInsightScanInputs(); m.remove(); }; const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) closeBtn.onclick = () => { saveShareInsightScanInputs(); m.remove(); }; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#spis_start').click(); } }); m.querySelector('#spis_start').onclick = () => { saveShareInsightScanInputs(); const vMin = parseInt(inpMin.value) || 0; const vMax = parseInt(inpMax.value) || 0; const kw = m.querySelector('#spis_keyword').value.trim(); if (vMin < 0 || (vMax > 0 && vMin > vMax)) { inpMin.style.borderColor = '#d93025'; if (vMax > 0 && vMin > vMax) inpMax.style.borderColor = '#d93025'; return; } gmSet('pk_scan_last_min', vMin); gmSet('pk_scan_last_max', vMax > 0 ? vMax : ''); gmSet('pk_scan_last_unit', currentUnit); gmSet('pk_scan_last_keyword', kw); let mult = 1; if (currentUnit === 'MB') mult = 1024 * 1024; else if (currentUnit === 'GB') mult = 1024 * 1024 * 1024; else if (currentUnit === 'TB') mult = 1024 * 1024 * 1024 * 1024; const scanFilter = { minBytes: Math.floor(vMin * mult), maxBytes: vMax > 0 ? Math.floor(vMax * mult) : 0, keyword: kw }; m.remove(); if (typeof runShareParseInsightScan === 'function') runShareParseInsightScan(scanFilter, shareTargets); }; return m; } function handleShareParseInsightStart() { const L = getStrings(); if (!(S.shareParseMode && S.shareParseListActive)) { showToast(L.msg_share_parse_scan_need_list, 'warning'); return; } const info = S.shareParseInfo || {}; if (!info.share_id || !info.pass_code_token) { showToast(L.msg_share_parse_save_need_token || L.msg_share_parse_token_missing, 'warning'); return; } if (S.shareParseInsightScanning) return; openShareParseInsightScanModal(getSelectedShareParseItems()); } async function runShareParseInsightScan(scanFilter = null, presetTargets = null) { const L = getStrings(); if (!(S.shareParseMode && S.shareParseListActive)) { showToast(L.msg_share_parse_scan_need_list, 'warning'); return; } const info = S.shareParseInfo || {}; if (!info.share_id || !info.pass_code_token) { showToast(L.msg_share_parse_save_need_token || L.msg_share_parse_token_missing, 'warning'); return; } if (S.shareParseInsightScanning) return; const insightTargets = Array.isArray(presetTargets) ? presetTargets.filter(item => item && item._isShareItem && item.id) : getSelectedShareParseItems(); const oldStopHandler = UI.stopBtn ? UI.stopBtn.onclick : null; clearShareParseSearch(); const reqId = (S.shareParseInsightReqId || 0) + 1; S.shareParseInsightReqId = reqId; S.shareParseInsightMode = false; S.shareParseInsightScanning = true; S.shareParseInsightStop = false; S.shareParseInsightToastStopped = false; S.shareParseInsightError = ''; S.shareParseInsightItems = []; S.shareParseInsightDisplay = []; S.shareParseInsightItemMap = new Map(); S.shareParseInsightFilterState = { active: false, cat: 'all', ext: 'all' }; S.shareParseInsightProgress = { folder: 0, file: 0 }; S.shareParseInsightReturnPath = Array.isArray(S.shareParsePath) ? S.shareParsePath.slice() : buildShareParseRootPath(); S.shareParseInsightReturnParentId = S.shareParseCurParentId || getShareParseRootParentId(); setLoad(true); updateLoadTxt(`${L.str_scanning} 0 ${L.unit_folders} | ${L.str_files}: 0`); if (UI.stopBtn) UI.stopBtn.onclick = handleShareParseInsightStop; try { const rawItems = await scanShareParseInsight(reqId, insightTargets, true); if (!S.shareParseMode || S.shareParseInsightReqId !== reqId) return; const filteredResult = filterShareParseInsightItemsByScanRule(rawItems, scanFilter); const items = filteredResult.items; if (S.shareParseInsightStop) { return; } if (!items.length) { S.shareParseInsightItems = []; S.shareParseInsightDisplay = []; S.shareParseInsightItemMap = new Map(); S.shareParseInsightError = ''; S.shareParseInsightProgress = { folder: 0, file: 0 }; if (filteredResult.rawTotal > 0 && filteredResult.hasActiveFilter) showToast(L.msg_scan_filter_empty || L.msg_share_parse_scan_empty); else showToast(L.msg_share_parse_scan_empty, 'warning'); updateShareParseInsightButtons(); renderShareParseCurrentView(); return; } S.shareParseInsightMode = true; if (typeof applyResolvedViewMode === 'function') applyResolvedViewMode('share_parse_insight'); S.shareParseInsightItems = items.slice(); S.shareParseInsightDisplay = S.shareParseInsightItems.slice(); S.shareParseInsightError = ''; S.sel.clear(); S.selEx.clear(); S.selMode = 'explicit'; S.activeId = null; S.lastSelIdx = -1; syncShareParseInsightMirror(); showToast(L.msg_share_parse_scan_done, 'success'); renderShareParseCurrentView(); } catch(e) { if (!S.shareParseMode || S.shareParseInsightReqId !== reqId) return; const msg = getShareParseErrorMessage(e) || L.msg_share_parse_scan_failed; S.shareParseInsightError = ''; showToast(msg, 'error'); } finally { if (S.shareParseInsightReqId === reqId) { S.shareParseInsightScanning = false; setLoad(false); if (UI.stopBtn) UI.stopBtn.onclick = oldStopHandler; updateShareParseInsightButtons(); renderShareParseCurrentView(); } } } function handleShareParseInsightStop() { if (!S.shareParseInsightScanning) return; S.shareParseInsightStop = true; S.shareParseInsightScanning = false; S.shareParseInsightToastStopped = true; setLoad(false); updateShareParseInsightButtons(); renderShareParseCurrentView(); } function handleShareParseInsightBack() { if (!S.shareParseMode) return; const returnPath = Array.isArray(S.shareParseInsightReturnPath) && S.shareParseInsightReturnPath.length ? S.shareParseInsightReturnPath.slice() : buildShareParseRootPath(); const returnParentId = S.shareParseInsightReturnParentId || getShareParseRootParentId(); resetShareParseInsightState(false); S.shareParseInsightMode = false; if (typeof applyResolvedViewMode === 'function') applyResolvedViewMode('share_parse_list'); S.shareParseInsightStop = true; S.shareParseInsightScanning = false; S.shareParseInsightError = ''; S.shareParsePath = returnPath; S.shareParseCurParentId = returnParentId; S.sel.clear(); S.selEx.clear(); S.selMode = 'explicit'; S.activeId = null; S.lastSelIdx = -1; syncShareParseMirror(); renderShareParseCurrentView(); } function getSelectedShareParseItems() { if (!(S.shareParseMode && S.shareParseListActive)) return []; const ids = S.getSelectedIds(); const out = []; ids.forEach(id => { const item = (S.shareParseInsightItemMap && S.shareParseInsightItemMap.get(id)) || (S.shareParseItemMap && S.shareParseItemMap.get(id)) || (S.itemMap && S.itemMap.get(id)); if (item && item._isShareItem && item.id) out.push(item); }); return out; } function normalizeShareParseIdList(value) { if (Array.isArray(value)) return value.map(v => String(v || '').trim()).filter(Boolean); if (value === undefined || value === null) return []; return String(value).split(',').map(v => v.trim()).filter(Boolean); } function getShareParseTraceFileId(item) { const raw = (item && item._rawShareFile) || {}; const params = raw.params || {}; const ids = normalizeShareParseIdList(raw.trace_file_ids || raw.traceFileIds || params.trace_file_ids || params.traceFileIds || raw.trace_file_id || raw.traceFileId); return ids[0] || (item && item.id) || ''; } function getShareParseAncestorIds(item) { const raw = (item && item._rawShareFile) || {}; const params = raw.params || {}; const ids = normalizeShareParseIdList(item && item._shareAncestorIds).concat( normalizeShareParseIdList(raw.ancestor_ids || raw.ancestorIds || params.ancestor_ids || params.ancestorIds) ); return Array.from(new Set(ids)); } function groupShareParseRestoreItems(items) { const groups = new Map(); const pushGroup = (key, item) => { if (!groups.has(key)) groups.set(key, []); groups.get(key).push(item); }; (items || []).forEach(item => { if (!item || !item._isShareItem || !item.id) return; const raw = item._rawShareFile || {}; const ancestorIds = getShareParseAncestorIds(item); const parentId = String(item._shareParentId || item.parent_id || raw.parent_id || raw.parentId || '').trim(); const sharePath = Array.isArray(item._sharePath) ? item._sharePath.map(n => n && (n.id || n.name)).filter(Boolean).join('/') : ''; const pathText = String(item._shareFilePathText || sharePath || '').trim(); let key = ''; if (ancestorIds.length || parentId) key = `a:${ancestorIds.join('/')}|p:${parentId}`; else if (pathText) key = `path:${pathText}`; else key = `item:${item.id}`; pushGroup(key, item); }); return Array.from(groups.values()).filter(group => group.length); } function buildShareParseRestorePayload(items, saveInfo = null) { const info = saveInfo || S.shareParseInfo || {}; const fileIds = items.map(item => item && item.id).filter(Boolean); const traceIds = items.map(getShareParseTraceFileId).filter(Boolean); const ancestorIds = Array.from(new Set(items.flatMap(getShareParseAncestorIds))); const payload = { share_id: info.share_id || '', pass_code_token: info.pass_code_token || '', file_ids: fileIds, params: { trace_file_ids: (traceIds.length ? traceIds : fileIds).join(',') } }; if (ancestorIds.length) payload.ancestor_ids = ancestorIds; return payload; } function resolveShareParseSaveErrorKey(status, data) { return resolveOfficialErrorByStableFields(status, data, 'share_save'); } function getShareParseSaveErrorMessage(err) { const L = getStrings(); const raw = err && err.data ? err.data : null; const data = raw ? getShareParsePayload(raw) : {}; const officialDesc = String((data && data.error_description) || (raw && raw.error_description) || '').trim(); if (officialDesc && !/^msg_/.test(officialDesc)) return officialDesc; if (err && err.shareParseKey && L[err.shareParseKey]) return L[err.shareParseKey]; if (err && (err.name === 'TypeError' || err.name === 'AbortError')) return L.msg_share_parse_save_network_error; return (err && err.message && !/^msg_/.test(err.message)) ? `${L.msg_share_parse_save_failed}: ${err.message}` : L.msg_share_parse_save_failed; } function getShareParseOfficialErrorMessage(err) { const raw = err && err.data ? err.data : null; const data = raw ? getShareParsePayload(raw) : {}; const isOfficialText = (v, allowCode = false) => { const t = String(v || '').trim(); return t && !/^msg_/.test(t) && (allowCode || !/^[a-z0-9_.:-]+$/i.test(t)); }; const pick = (obj, allowCode = false) => { if (!obj || typeof obj !== 'object') return ''; const fields = ['status_text','error_description','error_message','errorMessage','message','msg','description','detail','reason']; for (const k of fields) if (isOfficialText(obj[k], allowCode)) return String(obj[k]).trim(); return ''; }; return pick(data) || pick(raw) || pick(data.error) || pick(raw && raw.error) || pick(data, true) || pick(raw, true) || pick(data.error, true) || pick(raw && raw.error, true) || ''; } function extractShareParseSaveTaskIds(raw) { const data = getShareParsePayload(raw); const ids = []; const push = (v) => { if (v !== undefined && v !== null && String(v).trim()) ids.push(String(v).trim()); }; push(data.task_id || data.taskId || data.restore_task_id || data.restoreTaskId || raw && (raw.task_id || raw.taskId || raw.restore_task_id || raw.restoreTaskId)); normalizeShareParseIdList(data.task_ids || data.taskIds || data.restore_task_ids || data.restoreTaskIds || raw && (raw.task_ids || raw.taskIds || raw.restore_task_ids || raw.restoreTaskIds)).forEach(push); const collectTask = (task) => { if (!task || typeof task !== 'object') return; push(task.id || task.task_id || task.taskId || task.restore_task_id || task.restoreTaskId); }; collectTask(data.task || raw && raw.task); const list = data.tasks || data.task_list || raw && (raw.tasks || raw.task_list); if (Array.isArray(list)) list.forEach(collectTask); return Array.from(new Set(ids)); } async function pollShareParseSaveTasks(taskIds, reqId, onProgress) { while (true) { await sleep(2000); if (S.shareParseSaveReqId !== reqId) return false; let complete = 0; for (const taskId of taskIds) { const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/tasks/${encodeURIComponent(taskId)}?_t=${Date.now()}`, { headers: getHeaders() }); if (!res.ok) continue; const data = await res.json().catch(() => ({})); const phase = String(data.phase || (data.task && data.task.phase) || '').toUpperCase(); const key = resolveShareParseSaveErrorKey(0, data); if (key || phase === 'PHASE_TYPE_ERROR') { throw makeShareParseError(key || 'msg_share_parse_save_task_failed', 200, data); } if (phase === 'PHASE_TYPE_COMPLETE' || phase === 'PHASE_TYPE_COMPLETED' || phase === 'COMPLETE' || phase === 'SUCCESS') complete++; } if (onProgress) onProgress(complete, taskIds.length); if (complete >= taskIds.length) return true; } } async function handleShareParseSaveSelected() { const L = getStrings(); if (S.shareParseSaving) return; if (!(S.shareParseMode && S.shareParseListActive)) { showToast(L.msg_share_parse_save_need_list, 'warning'); return; } const info = S.shareParseInfo || {}; if (!info.share_id || !info.pass_code_token) { showToast(L.msg_share_parse_save_need_token, 'warning'); return; } const selectedItems = getSelectedShareParseItems(); if (!selectedItems.length) { showToast(L.msg_share_parse_select_first, 'warning'); return; } const reqId = (S.shareParseSaveReqId || 0) + 1; S.shareParseSaveReqId = reqId; S.shareParseSaving = true; S.shareParseSaveError = ''; S.shareParseSaveResult = null; updateShareParseSaveButton(); showToast(L.msg_share_parse_save_start); const saveInfo = { share_id: info.share_id || '', pass_code_token: info.pass_code_token || '' }; const totalSaveCount = selectedItems.length; let savedSubmitCount = 0; const formatSaveProgress = (done, total) => (L.msg_share_parse_save_progress || L.btn_share_parse_saving).replace('{done}', done).replace('{total}', total); const formatTaskProgress = (done, total) => (L.msg_share_parse_save_task_progress || L.btn_share_parse_saving).replace('{done}', done).replace('{total}', total); const progressTask = FloatBarManager.create(formatSaveProgress(0, totalSaveCount)); try { const headers = getHeaders(); if (!headers.Authorization || headers.Authorization.length < 10) throw makeShareParseError('msg_share_parse_save_auth_error'); const groups = groupShareParseRestoreItems(selectedItems); if (!groups.length) throw makeShareParseError('msg_share_parse_select_first'); const allTaskIds = []; const results = []; for (const groupItems of groups) { if (S.shareParseSaveReqId !== reqId) return; progressTask.update(formatSaveProgress(savedSubmitCount, totalSaveCount)); const payload = buildShareParseRestorePayload(groupItems, saveInfo); const res = await fetch('https://api-drive.mypikpak.com/drive/v1/share/restore', { method: 'POST', headers, body: JSON.stringify(payload) }); syncTime(res.headers); const bodyText = await res.text().catch(() => ''); let data = {}; try { data = bodyText ? JSON.parse(bodyText) : {}; } catch(e) { data = { raw_text: bodyText }; } if (S.shareParseSaveReqId !== reqId) return; if (!res.ok) throw makeShareParseError(resolveShareParseSaveErrorKey(res.status, data), res.status, data); const key = resolveShareParseSaveErrorKey(0, data); if (key) throw makeShareParseError(key, 200, data); extractShareParseSaveTaskIds(data).forEach(id => allTaskIds.push(id)); results.push(data || {}); savedSubmitCount += groupItems.length; progressTask.update(formatSaveProgress(Math.min(savedSubmitCount, totalSaveCount), totalSaveCount)); } const taskIds = Array.from(new Set(allTaskIds)); if (taskIds.length) { progressTask.update(formatTaskProgress(0, taskIds.length)); const done = await pollShareParseSaveTasks(taskIds, reqId, (doneCount, totalCount) => { progressTask.update(formatTaskProgress(doneCount, totalCount)); }); if (S.shareParseSaveReqId !== reqId) return; if (done === false) return; } S.shareParseSaveResult = { groups: groups.length, taskIds, results }; if (S.shareParseMode && S.shareParseListActive) { S.clearSelection(); renderVisible(); } showToast(L.msg_share_parse_save_success); } catch(e) { if (S.shareParseSaveReqId !== reqId) return; const msg = getShareParseSaveErrorMessage(e); S.shareParseSaveError = msg; showToast(msg, 'error'); } finally { if (S.shareParseSaveReqId === reqId) { S.shareParseSaving = false; updateShareParseSaveButton(); updateStat(); } progressTask.destroy(); } } function getShareParseRootParentId() { const info = S.shareParseInfo || {}; const raw = info.raw_share_info || {}; const data = getShareParsePayload(raw); const share = data.share || data.share_info || {}; return data.root_id || data.root_folder_id || data.share_root_id || share.root_id || share.root_folder_id || share.share_root_id || ''; } function buildShareParseRootPath() { const L = getStrings(); const info = S.shareParseInfo || {}; const rootId = getShareParseRootParentId(); const title = L.picker_all || L.label_share_parse_root; return [ { id: 'share_parse_panel', name: L.label_share_parse_path_root, _sharePanel: true }, { id: rootId || '', name: title, parent_id: rootId || '', _shareRoot: true } ]; } function getShareDetailFiles(raw) { const data = getShareParsePayload(raw); if (Array.isArray(data.files)) return data.files; if (Array.isArray(data.file_list)) return data.file_list; if (Array.isArray(data.list)) return data.list; if (data.file && typeof data.file === 'object') return [data.file]; if (data.file_info && typeof data.file_info === 'object') return [data.file_info]; if (Array.isArray(raw && raw.files)) return raw.files; return []; } function getShareDetailNextToken(raw) { const data = getShareParsePayload(raw); return data.next_page_token || data.nextPageToken || (raw && (raw.next_page_token || raw.nextPageToken)) || ''; } function resolveShareDetailErrorKey(status, data) { const body = stringifyShareParsePayload(data); const lower = body.toLowerCase(); const hasTokenSignal = /pass_code_token|access_token|token/i.test(body) && /expired|expire|invalid|unauthorized|forbidden/i.test(body); if (hasTokenSignal) return 'msg_share_parse_token_expired'; if ((status === 401 || status === 403) && /pass_code_token/i.test(body)) return 'msg_share_parse_token_expired'; const base = resolveShareParseErrorKey(status, data); if (base) return base; if (status && status >= 400) return 'msg_share_parse_detail_failed'; return ''; } async function fetchShareDetail(parentId, pageToken) { const info = S.shareParseInfo || {}; if (!info.share_id) throw makeShareParseError('msg_share_parse_need_parse_first'); if (!info.pass_code_token) throw makeShareParseError('msg_share_parse_token_missing'); const headers = getHeaders(); if (!headers.Authorization || headers.Authorization.length < 10) throw makeShareParseError('msg_share_parse_auth_error'); const params = [ ['share_id', info.share_id], ['pass_code_token', info.pass_code_token], ['limit', '100'], ['thumbnail_size', 'SIZE_MEDIUM'], ['folders_first', 'true'] ]; if (parentId !== undefined && parentId !== null && String(parentId) !== '') params.push(['parent_id', parentId]); if (pageToken !== undefined && pageToken !== null && String(pageToken) !== '') params.push(['page_token', pageToken]); const query = params.map(([k, v]) => `${k}=${encodeURIComponent(v == null ? '' : String(v))}`).join('&'); const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/share/detail?${query}`, { method: 'GET', headers }); syncTime(res.headers); if (!res.ok) { const bodyText = await res.text().catch(() => ''); let errData = {}; try { errData = bodyText ? JSON.parse(bodyText) : {}; } catch(e) { errData = { raw_text: bodyText }; } throw makeShareParseError(resolveShareDetailErrorKey(res.status, errData), res.status, errData); } const data = await res.json(); const key = resolveShareDetailErrorKey(0, data); if (key) throw makeShareParseError(key, 200, data); return data; } function mapShareDetailFile(file, index = 0, ctx = null) { const raw = file || {}; const parentId = ctx && ctx.parentId !== undefined ? (ctx.parentId || '') : (raw.parent_id || raw.parentId || S.shareParseCurParentId || ''); const rawId = raw.id || raw.file_id || raw.fileId || raw.resource_id || raw.resourceId || ''; const name = raw.name || raw.file_name || raw.title || ''; const mime = raw.mime_type || raw.mimeType || raw.content_type || ''; const rawKind = String(raw.kind || '').toLowerCase(); const isFolder = raw.kind === 'drive#folder' || rawKind === 'folder' || rawKind.includes('folder') || mime === 'application/x-directory'; const id = String(rawId || `share_${parentId || 'root'}_${index}_${name}`); const source = { ...raw, id, kind: isFolder ? 'drive#folder' : 'drive#file', name, parent_id: parentId, size: raw.size || raw.file_size || 0, mime_type: mime, thumbnail_link: raw.thumbnail_link || raw.thumbnailLink || raw.thumbnail || '', icon_link: raw.icon_link || raw.iconLink || raw.icon || '', modified_time: raw.modified_time || raw.modifiedTime || raw.updated_time || raw.created_time || '', hash: raw.hash || raw.gcid || raw.md5_checksum || '', tags: [], starred: false, trashed: false, _pkSkipDurationProbe: true }; const mapped = minifyFile(source, false); const curPath = ctx && Array.isArray(ctx.path) && ctx.path.length ? S.cloneShareParsePath(ctx.path) : (Array.isArray(S.shareParsePath) && S.shareParsePath.length ? S.shareParsePath.slice() : buildShareParseRootPath()); const ancestorIds = curPath.map(n => n && n.id).filter(idv => idv && idv !== 'share_parse_panel'); const sharePath = isFolder ? curPath.concat([{ id, name, parent_id: parentId }]) : curPath; return Object.assign(mapped, { _isShareItem: true, _shareId: (S.shareParseInfo && S.shareParseInfo.share_id) || '', _passCodeToken: (S.shareParseInfo && S.shareParseInfo.pass_code_token) || '', _shareParentId: parentId, _shareAncestorIds: ancestorIds, _sharePath: sharePath, _rawShareFile: raw, _pkSkipDurationProbe: true }); } function renderShareParseListStatus(message, type = '') { if (!UI.in) return; if (typeof clearGridStableRows === 'function') clearGridStableRows(UI.in); UI.in.style.height = '100%'; UI.in.style.transform = 'none'; if (type === 'loading') { const spin = '
'; UI.in.innerHTML = `
${spin}
${esc(message || '')}
`; } else { UI.in.innerHTML = `
${CONF.emptySVG}
${esc(message || '')}
`; } if (UI.pop) { UI.pop.style.display = 'none'; UI.pop.innerHTML = ''; } if (UI.ctx) UI.ctx.style.display = 'none'; } function syncShareParseInsightFilterBar() { if (!UI.filterBar) return; const visible = !!(S.shareParseMode && S.shareParseListActive && S.shareParseInsightMode && !S.shareParseInsightScanning); UI.filterBar.style.display = visible ? 'flex' : 'none'; if (!visible) return; const state = S.shareParseInsightFilterState || { active: false, cat: 'all', ext: 'all' }; if (state.active) { UI.filterBtn.style.display = 'none'; UI.filterActiveUI.style.display = 'flex'; if (typeof renderActiveFilterUI === 'function') renderActiveFilterUI(); } else { UI.filterBtn.style.display = 'flex'; UI.filterActiveUI.style.display = 'none'; } } function renderShareParseCurrentView() { if (!S.shareParseMode) return; renderCrumb(); if (typeof applyResolvedSortState === 'function') applyResolvedSortState(); if (S.shareParseListActive) { syncShareParseMirror(); renderList(); } else { renderShareParsePanel(); } updateShareParseInsightButtons(); syncShareParseInsightFilterBar(); updateStat(); } function cancelShareParseAutoPaging() { S.shareParseAutoLoadToken = (S.shareParseAutoLoadToken || 0) + 1; S.shareParseAutoLoadRunning = false; S.shareParseAutoLoadTarget = ''; } function scheduleShareParseAutoPaging(parentId = '') { if (!S.shareParseMode || S.shareParseInsightMode || !S.shareParseListActive || !S.shareParseNextPageToken) return; S.pagingLoading = true; updateStat(); const target = String(parentId || S.shareParseCurParentId || ''); if (S.shareParseAutoLoadRunning && S.shareParseAutoLoadTarget === target) return; const token = (S.shareParseAutoLoadToken || 0) + 1; S.shareParseAutoLoadToken = token; S.shareParseAutoLoadTarget = target; setTimeout(() => drainShareParseAutoPaging(token, target).catch(() => {}), 0); } async function drainShareParseAutoPaging(token, target) { S.shareParseAutoLoadRunning = true; try { while ( S.shareParseAutoLoadToken === token && S.shareParseMode && S.shareParseListActive && !S.shareParseInsightMode && String(S.shareParseCurParentId || '') === String(target || '') && S.shareParseNextPageToken ) { if (S.shareParseListLoading) { await sleep(80); continue; } await loadShareParseFolder(null, { append: true, autoPaging: true }); await sleep(30); } } finally { if (S.shareParseAutoLoadToken === token) { S.shareParseAutoLoadRunning = false; S.shareParseAutoLoadTarget = ''; S.pagingLoading = !!(S.shareParseMode && S.shareParseListActive && !S.shareParseInsightMode && S.shareParseNextPageToken); updateStat(); } } } async function loadShareParseFolder(folderNode = null, opts = {}) { const L = getStrings(); if (!S.shareParseMode) return; const append = !!opts.append; if (!append) cancelShareParseAutoPaging(); if (!append && S.shareParseInsightMode) resetShareParseInsightState(true); if (append && (!S.shareParseListActive || !S.shareParseNextPageToken || S.shareParseListLoading)) return; const parentId = append ? (S.shareParseCurParentId || '') : (folderNode && folderNode.id ? folderNode.id : getShareParseRootParentId()); const pageToken = append ? S.shareParseNextPageToken : ''; const targetPathForNav = (!append && folderNode && Array.isArray(folderNode._sharePath) && folderNode._sharePath.length) ? S.cloneShareParsePath(folderNode._sharePath) : (!append ? buildShareParseRootPath() : null); const shouldRecordShareParseNav = !append && !opts.suppressShareParseNav && !S.shareParseNavSuspendRecord && S.shareParseListActive && !S.shareParseInsightMode; const prevShareParseNavSnap = shouldRecordShareParseNav ? S.makeShareParseNavSnapshot() : null; if (prevShareParseNavSnap && targetPathForNav && !S.isSameShareParsePath(prevShareParseNavSnap.path, targetPathForNav)) { S.pushShareParseNavSnapshot(prevShareParseNavSnap, true); } const reqId = (S.shareParseListReqId || 0) + 1; S.shareParseListReqId = reqId; S.shareParseListActive = true; if (!append && typeof applyResolvedViewMode === 'function') applyResolvedViewMode('share_parse_list'); S.shareParseListLoading = true; S.shareParseListError = ''; S.shareParseCurParentId = parentId || ''; S.pagingLoading = !!append; if (append) updateStat(); if (!append) { const cached = readShareParseFolderCache(parentId || ''); if (cached) { S.shareParseItems = cached.items; S.shareParseDisplay = S.shareParseItems.slice(); S.shareParseItemMap = new Map(); S.shareParseItems.forEach(item => { if (item && item.id) S.shareParseItemMap.set(item.id, item); }); S.shareParsePath = cached.path.length ? cached.path : (targetPathForNav ? S.cloneShareParsePath(targetPathForNav) : buildShareParseRootPath()); S.shareParseCurParentId = cached.parentId || parentId || ''; S.shareParseNextPageToken = cached.nextPageToken || ''; S.shareParseListLoading = false; S.pagingLoading = !!S.shareParseNextPageToken; S.sel.clear(); S.selEx.clear(); S.selMode = 'explicit'; S.activeId = null; S.lastSelIdx = -1; syncShareParseMirror(); renderShareParseCurrentView(); restoreShareParseFolderScroll(opts, false); scheduleShareParseAutoPaging(S.shareParseCurParentId || parentId || ''); return; } S.shareParseItems = []; S.shareParseDisplay = []; S.shareParseItemMap = new Map(); S.shareParseNextPageToken = ''; S.sel.clear(); S.selEx.clear(); S.selMode = 'explicit'; S.activeId = null; S.lastSelIdx = -1; if (folderNode && Array.isArray(folderNode._sharePath) && folderNode._sharePath.length) { S.shareParsePath = folderNode._sharePath.slice(); } else { S.shareParsePath = buildShareParseRootPath(); } } syncShareParseMirror(); renderShareParseCurrentView(); try { const raw = await fetchShareDetail(parentId || '', pageToken || ''); if (!S.shareParseMode || S.shareParseListReqId !== reqId) return; const mapped = getShareDetailFiles(raw).map((file, idx) => mapShareDetailFile(file, idx)); S.shareParseItems = append ? S.shareParseItems.concat(mapped) : mapped; S.shareParseDisplay = S.shareParseItems.slice(); S.shareParseItemMap = new Map(); S.shareParseItems.forEach(item => { if (item && item.id) S.shareParseItemMap.set(item.id, item); }); S.shareParseNextPageToken = getShareDetailNextToken(raw); S.shareParseListError = ''; writeShareParseFolderCache(parentId || ''); } catch(e) { if (!S.shareParseMode || S.shareParseListReqId !== reqId) return; const msg = getShareParseErrorMessage(e) || L.msg_share_parse_load_failed; S.shareParseListError = msg; if (!append) { S.shareParseItems = []; S.shareParseDisplay = []; S.shareParseItemMap = new Map(); } else { S.shareParseNextPageToken = ''; } showToast(msg, 'error'); } finally { if (S.shareParseListReqId === reqId) { S.shareParseListLoading = false; S.pagingLoading = !!(S.shareParseMode && S.shareParseListActive && !S.shareParseInsightMode && S.shareParseNextPageToken); if (S.shareParseMode) renderShareParseCurrentView(); restoreShareParseFolderScroll(opts, append); if (!append && S.shareParseMode && S.shareParseListReqId === reqId) scheduleShareParseAutoPaging(parentId || ''); } } } function loadShareParseNextPage() { return loadShareParseFolder(null, { append: true }); } function maybeLoadShareParseNextPage() { if (S.shareParseInsightMode) return; scheduleShareParseAutoPaging(S.shareParseCurParentId || ''); } const AUDIO_FILE_EXTS = new Set(['mp3','wav','flac','aac','m4a','ogg','opus','ape','wma','amr','m4b','alac','aiff','aif','mid','midi','ra','dts','ac3','dsf','dff']); function getItemExt(it) { const name = String((it && it.name) || '').toLowerCase(); const m = name.match(/\.([^.\/\\]+)$/); return m ? m[1] : ''; } function isAudioLikeItem(it) { if (!it || it.isHeader || it.kind === 'drive#folder') return false; const m = String((it && it.mime_type) || '').toLowerCase(); return m.startsWith('audio/') || AUDIO_FILE_EXTS.has(getItemExt(it)); } function getAudioDirectUrl(detail) { if (!detail) return ''; if (detail.web_content_link) return detail.web_content_link; const oct = detail.links && detail.links['application/octet-stream']; return (oct && oct.url) || ''; } function getAudioPhysicalId(it) { if (!it) return ''; if ((S.offlineMode && it.kind === 'drive#task') || (S.uploadMode && it.file_id)) return it.file_id || it.id || ''; return it.id || ''; } function isVideoLikeItem(it) { if (isAudioLikeItem(it)) return false; const m = String((it && it.mime_type) || '').toLowerCase(); const n = String((it && it.name) || '').toLowerCase(); const dur = Number((it && it.params && it.params.duration) || 0); return m.startsWith('video/') || dur > 0 || ['mp4','mkv','avi','mov','wmv','flv','webm','ts','m4v','3gp'].some(e => n.endsWith('.' + e)); } function isImageLikeItem(it) { const m = String((it && it.mime_type) || '').toLowerCase(); const n = String((it && it.name) || '').toLowerCase(); return m.startsWith('image/') || ['jpg','jpeg','png','gif','webp','bmp','avif'].some(e => n.endsWith('.' + e)); } function isArchiveLike(it) { const m = String((it && it.mime_type) || '').toLowerCase(); const n = String((it && it.name) || '').toLowerCase(); return m.includes('zip') || m.includes('rar') || m.includes('archive') || ['zip','rar','7z','tar','gz','bz2','xz'].some(e => n.endsWith('.' + e)); } let activeTextPreviewReqId = 0; let activeShareTextPreviewFileId = ''; const textPreviewStateMap = new WeakMap(); function isTxtFileLike(it) { if (!it || it.isHeader || it.kind === 'drive#folder') return false; const ext = String((it && it.file_extension) || '').toLowerCase(); const m = String((it && it.mime_type) || '').toLowerCase(); return ext === '.txt' || getItemExt(it) === 'txt' || m.includes('text/plain'); } function showUnsupportedOpenToast() { const L = getStrings(); showToast(L.msg_open_download_after, 'warning'); } function getTextDirectUrlFromFileInfo(fileInfo) { if (!fileInfo) return ''; if (fileInfo.web_content_link) return fileInfo.web_content_link; const oct = fileInfo.links && fileInfo.links['application/octet-stream']; return (oct && oct.url) || ''; } function makeTextPreviewError(code, cause = null) { const err = new Error(code || 'txt_preview_load_failed'); err.code = code || 'load_failed'; if (cause) err.cause = cause; return err; } function stripTextBom(text) { return text && text.charCodeAt(0) === 0xFEFF ? text.slice(1) : text; } function decodeUtf16Bytes(bytes, littleEndian, offset = 0) { let out = ''; let chunk = []; const flush = () => { if (chunk.length) { out += String.fromCharCode.apply(null, chunk); chunk = []; } }; for (let i = offset; i + 1 < bytes.length; i += 2) { const code = littleEndian ? (bytes[i] | (bytes[i + 1] << 8)) : ((bytes[i] << 8) | bytes[i + 1]); chunk.push(code); if (chunk.length >= 8192) flush(); } flush(); return stripTextBom(out); } function tryDecodeText(bytes, label) { try { return stripTextBom(new TextDecoder(label).decode(bytes)); } catch(e) { return null; } } function getTextDecodeScore(text) { if (!text) return 0; let bad = 0; for (let i = 0; i < text.length; i++) { const code = text.charCodeAt(i); if (code === 0xFFFD) bad += 1; else if ((code < 32 && code !== 9 && code !== 10 && code !== 13) || code === 127) bad += 0.4; } return bad / Math.max(1, text.length); } function decodeTextArrayBuffer(buffer) { const bytes = new Uint8Array(buffer || new ArrayBuffer(0)); if (!bytes.byteLength) return { text: '', encoding: '' }; if (bytes.length >= 3 && bytes[0] === 0xEF && bytes[1] === 0xBB && bytes[2] === 0xBF) { const text = tryDecodeText(bytes.subarray(3), 'utf-8'); if (text !== null) return { text, encoding: 'utf-8-bom' }; } if (bytes.length >= 2 && bytes[0] === 0xFF && bytes[1] === 0xFE) return { text: decodeUtf16Bytes(bytes, true, 2), encoding: 'utf-16le' }; if (bytes.length >= 2 && bytes[0] === 0xFE && bytes[1] === 0xFF) return { text: decodeUtf16Bytes(bytes, false, 2), encoding: 'utf-16be' }; const attempts = []; const utf8 = tryDecodeText(bytes, 'utf-8'); if (utf8 !== null) { const score = getTextDecodeScore(utf8); if (score <= 0.001) return { text: utf8, encoding: 'utf-8' }; attempts.push({ text: utf8, encoding: 'utf-8', score }); } ['gb18030','gbk','big5','shift_jis','euc-kr','windows-1252'].forEach(label => { const text = tryDecodeText(bytes, label); if (text !== null) attempts.push({ text, encoding: label, score: getTextDecodeScore(text) }); }); attempts.sort((a, b) => a.score - b.score); const best = attempts[0]; if (!best || best.score > 0.25) throw makeTextPreviewError('decode_failed'); return { text: best.text, encoding: best.encoding }; } async function readTextFromDirectUrl(url, fileInfo) { if (!url) throw makeTextPreviewError('load_failed'); const size = Number(fileInfo && fileInfo.size); if (Number.isFinite(size) && size > CONF.textPreviewMaxBytes) throw makeTextPreviewError('too_large'); let res; try { res = await fetch(url); } catch(e) { throw makeTextPreviewError('load_failed', e); } if (!res || !res.ok) throw makeTextPreviewError('load_failed'); const contentLength = Number(res.headers && res.headers.get ? (res.headers.get('content-length') || 0) : 0); if (Number.isFinite(contentLength) && contentLength > CONF.textPreviewMaxBytes) throw makeTextPreviewError('too_large'); let buffer; try { buffer = await res.arrayBuffer(); } catch(e) { throw makeTextPreviewError('load_failed', e); } if (!buffer || buffer.byteLength === 0) return ''; if (buffer.byteLength > CONF.textPreviewMaxBytes) throw makeTextPreviewError('too_large'); try { return decodeTextArrayBuffer(buffer).text; } catch(e) { if (e && e.code === 'decode_failed') throw e; throw makeTextPreviewError('decode_failed', e); } } async function readTextFromFileInfo(fileInfo) { const url = getTextDirectUrlFromFileInfo(fileInfo); if (!url) throw makeTextPreviewError('load_failed'); return readTextFromDirectUrl(url, fileInfo); } function getTextPreviewState(modal) { return modal ? textPreviewStateMap.get(modal) : null; } function formatTextPreviewShortcutLabel(label, shortcut) { return shortcut ? `${label} (${shortcut})` : label; } function setTextPreviewButtonTip(btn, label) { if (!btn || !label) return; btn.setAttribute('aria-label', label); btn.setAttribute('data-pk-tip', label); btn.removeAttribute('title'); } function setupTextPreviewShortcutKeys(modal) { if (!modal) return; modal.tabIndex = 0; setTimeout(() => { if (document.contains(modal)) modal.focus(); }, 10); modal.addEventListener('keydown', (e) => { const target = e.target; const tag = String(target && target.tagName || '').toLowerCase(); if (target && (target.isContentEditable || tag === 'input' || tag === 'textarea' || tag === 'select')) return; const key = String(e.key || '').toLowerCase(); if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); modal.querySelector('.pk-modal-close')?.click(); return; } if (key === 'f' && !e.ctrlKey && !e.altKey && !e.metaKey) { e.preventDefault(); e.stopPropagation(); modal.querySelector('#pk_txt_preview_fullscreen')?.click(); return; } if (e.key === 'Enter' && e.ctrlKey && !e.altKey && !e.metaKey) { const parseBtn = modal.querySelector('#pk_txt_preview_parse_links'); if (parseBtn && !parseBtn.disabled) { e.preventDefault(); e.stopPropagation(); parseBtn.click(); } } }, true); } function cleanupTextPreviewModal(modal) { const state = getTextPreviewState(modal); if (state) { state.text = ''; state.loading = false; state.parsingLinks = false; } document.documentElement.classList.remove('pk-txt-preview-fullscreen-lock'); document.body.classList.remove('pk-txt-preview-fullscreen-lock'); if (modal) textPreviewStateMap.delete(modal); } function getTextPreviewDefaultCloudTarget() { const path = Array.isArray(S.path) ? S.path : []; const curFolder = path[path.length - 1] || {}; const curId = String(curFolder.id || ''); const isVirtual = curId.startsWith('virtual_') || curId.includes('_root') || curId === 'analyze_root'; const isHomeSubDir = !S.trashMode && !S.shareMode && !S.shareParseMode && !S.offlineMode && !S.uploadMode && !S.starredMode && !S.recentMode && !S.historyMode && !S.isFlattened && !S.dupMode && path.length > 1 && !isVirtual; const L = getStrings(); return { id: isHomeSubDir ? curId : '', name: isHomeSubDir ? (curFolder.name || L.lbl_default_folder) : L.lbl_default_folder, path: isHomeSubDir ? path.filter(p => !String(p.id || '').startsWith('virtual_')) : null }; } function isCloudTaskRateLimited(reqErr) { const errText = String((reqErr && (reqErr.message || reqErr.status || reqErr.code)) || ''); return errText.includes('429'); } async function apiAddOfflineTaskWithRetry(url, targetId = '', extraParams = {}) { let retry = 0; while (retry < 3) { try { await apiAddOfflineTask(url, targetId || '', extraParams || {}); return true; } catch (reqErr) { if (isCloudTaskRateLimited(reqErr)) { retry++; if (retry >= 3) throw reqErr; await sleep(2000 * retry); } else { throw reqErr; } } } return false; } function refreshCloudTaskView(targetId = '') { setTimeout(() => updateQuotaUI(), 1000); const path = Array.isArray(S.path) ? S.path : []; const curPathId = (path[path.length - 1] && path[path.length - 1].id) || ''; if (S.offlineMode || (targetId && curPathId === targetId)) { load(false, true); } else if (!targetId && path.length === 1) { if (window.pkSmartRefreshTrigger) window.pkSmartRefreshTrigger(true); } } function showSnapshotModal(urlList, options = {}) { const L = getStrings(); return new Promise((resolve) => { let currentSaveId = options.targetId || ''; let currentSaveName = options.targetName || L.lbl_default_folder; let currentSavePath = options.currentPath || null; const displayUrl = urlList[0]; const countSuffix = urlList.length > 1 ? (L.str_snap_link_count_suffix || '').replace('{n}', urlList.length) : ''; const snapIcon = ``; const sm = showModal(`

${L.title_save_method}

${L.msg_save_snapshot_desc}
${snapIcon}
${esc(displayUrl)}${countSuffix}
${L.lbl_save_to}
${CONF.typeIcons.folder.replace('width="30"', 'width="20"').replace('height="30"', 'height="20"')} ${esc(currentSaveName)} ${L.btn_modify}
`); const mBox = sm.querySelector('.pk-modal'); if (mBox) { mBox.style.padding = '24px'; mBox.style.width = 'auto'; } const closeBtn = sm.querySelector('.pk-modal-close'); if (closeBtn) { closeBtn.style.top = '30px'; closeBtn.style.right = '24px'; } sm.querySelector('#snap_change_dir').onclick = () => { showFolderSelector(currentSaveId, (id, name, fullItem, selectedPathChain) => { currentSaveId = id; currentSaveName = name; sm.querySelector('#snap_dir_name').textContent = name; if (selectedPathChain) currentSavePath = selectedPathChain; if (typeof options.onTargetChange === 'function') options.onTargetChange(id, name, fullItem, selectedPathChain); }, currentSavePath); }; const doClose = (result) => { sm.remove(); resolve(result ? { confirm: true, targetId: currentSaveId, targetName: currentSaveName, path: currentSavePath } : { confirm: false }); }; sm.querySelector('#snap_cancel').onclick = () => doClose(false); if (closeBtn) closeBtn.onclick = () => doClose(false); sm.querySelector('#snap_save').onclick = () => doClose(true); sm.tabIndex = 0; setTimeout(() => sm.focus(), 10); sm.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); sm.querySelector('#snap_save').click(); } }); }); } async function submitCloudLinks(links, options = {}) { const L = getStrings(); const finalLinks = Array.from(new Set((Array.isArray(links) ? links : []).map(l => String(l || '').trim()).filter(Boolean))); if (!finalLinks.length) return { successCount: 0, failCount: 0, cancelled: false }; let saveToId = options.targetId || ''; let saveToName = options.targetName || L.lbl_default_folder; let currentSavePath = options.currentPath || null; const sourceModal = options.sourceModal || null; const hideSourceModal = !!sourceModal && options.hideSourceModal !== false; const sourceDisplay = options.sourceDisplay || 'flex'; if (sourceModal && hideSourceModal) sourceModal.style.display = 'none'; const videoPlatformRegex = /(?:youtube\.com|youtu\.be|twitter\.com|x\.com|tiktok\.com|douyin\.com|facebook\.com|fb\.watch|instagram\.com|t\.me|bilibili\.com)/i; let snapshotLinks = []; let directLinks = finalLinks; if (!options.skipSnapshot) { snapshotLinks = finalLinks.filter(l => /^https?:\/\//i.test(l) && !videoPlatformRegex.test(l)); directLinks = finalLinks.filter(l => !/^https?:\/\//i.test(l) || videoPlatformRegex.test(l)); } let snapshotParams = null; const isSingleSnapshotTask = !options.skipSnapshot && finalLinks.length === 1 && snapshotLinks.length === 1; if (isSingleSnapshotTask) { const result = await showSnapshotModal(snapshotLinks, { targetId: saveToId, targetName: saveToName, currentPath: currentSavePath, onTargetChange: (id, name, fullItem, selectedPathChain) => { saveToId = id; saveToName = name; if (selectedPathChain) currentSavePath = selectedPathChain; if (typeof options.onTargetChange === 'function') options.onTargetChange(id, name, fullItem, selectedPathChain); } }); if (!result.confirm) { if (sourceModal && hideSourceModal && document.contains(sourceModal)) sourceModal.style.display = sourceDisplay; return { successCount: 0, failCount: 0, cancelled: true }; } saveToId = result.targetId || ''; saveToName = result.targetName || saveToName; currentSavePath = result.path || currentSavePath; snapshotParams = { save_as: 'snapshot', targetId: saveToId }; } if (sourceModal && options.removeSourceModal && document.contains(sourceModal)) sourceModal.remove(); const progressTask = FloatBarManager.create(L.msg_creating_cloud_task); let successCount = 0; let failCount = 0; try { const processQueue = [ ...directLinks.map(u => ({ url: u, isSnap: false })), ...snapshotLinks.map(u => ({ url: u, isSnap: true })) ]; for (let i = 0; i < processQueue.length; i++) { const item = processQueue[i]; progressTask.update(L.str_creating_task_n.replace('{n}', i + 1).replace('{t}', processQueue.length)); try { const pid = (item.isSnap && snapshotParams) ? snapshotParams.targetId : saveToId; const extras = item.isSnap ? { save_as: 'snapshot' } : {}; const created = await apiAddOfflineTaskWithRetry(item.url, pid, extras); if (!created) throw new Error('Cloud task create failed after retry'); successCount++; if (typeof globalNeedsSync !== 'undefined') globalNeedsSync = true; } catch(e) { console.error(`${options.logPrefix || 'Task Create Failed'}[${item.url}]:`, e); failCount++; } await sleep(300); } } finally { progressTask.destroy(); } if (failCount > 0) { showToast(L.msg_cloud_task_finish.replace('{s}', successCount).replace('{f}', failCount), 'warning'); } else { showToast((options.successMessage || L.msg_cloud_task_success).replace('{n}', successCount)); } refreshCloudTaskView(saveToId); return { successCount, failCount, cancelled: false }; } function updateTextPreviewModalControls(modal) { if (!modal || !document.contains(modal)) return; const L = getStrings(); const state = getTextPreviewState(modal); if (!state) return; const parseBtn = modal.querySelector('#pk_txt_preview_parse_links'); const maxBtn = modal.querySelector('#pk_txt_preview_fullscreen'); if (parseBtn) { const canParse = !state.loading && !state.error && !state.empty && !!state.text && !state.parsingLinks; const baseLabel = state.parsingLinks ? (L.txt_preview_parse_links_working || L.txt_preview_parse_links) : L.txt_preview_parse_links; const label = formatTextPreviewShortcutLabel(baseLabel, CONF.txtPreviewHotkeys.parse); parseBtn.disabled = !canParse; setTextPreviewButtonTip(parseBtn, label); } if (maxBtn) { const baseLabel = state.fullscreen ? L.txt_preview_exit_fullscreen : L.txt_preview_fullscreen; const label = formatTextPreviewShortcutLabel(baseLabel, CONF.txtPreviewHotkeys.fullscreen); maxBtn.innerHTML = state.fullscreen ? CONF.icons.txtPreviewExitFullscreenSVG : CONF.icons.txtPreviewFullscreenSVG; maxBtn.setAttribute('aria-pressed', state.fullscreen ? 'true' : 'false'); setTextPreviewButtonTip(maxBtn, label); } } function toggleTextPreviewFullscreen(modal) { const state = getTextPreviewState(modal); if (!modal || !state) return; const modalBox = modal.querySelector('.pk-modal.pk-txt-preview-modal'); if (!modalBox) return; state.fullscreen = !state.fullscreen; modalBox.classList.toggle('pk-txt-preview-fullscreen', state.fullscreen); modal.classList.toggle('pk-txt-preview-fullscreen-ov', state.fullscreen); document.documentElement.classList.toggle('pk-txt-preview-fullscreen-lock', state.fullscreen); document.body.classList.toggle('pk-txt-preview-fullscreen-lock', state.fullscreen); updateTextPreviewModalControls(modal); requestAnimationFrame(() => { const bodyEl = modal.querySelector('.pk-txt-preview-body'); if (bodyEl) bodyEl.scrollTop = bodyEl.scrollTop; }); } async function handleTextPreviewParseLinks(modal) { const state = getTextPreviewState(modal); if (!modal || !document.contains(modal) || !state || state.loading || state.error || state.parsingLinks) return; const L = getStrings(); const links = parseCloudLinks(state.text, true); if (!links.length) { showToast(L.txt_preview_no_links, 'warning'); return; } state.parsingLinks = true; updateTextPreviewModalControls(modal); try { const target = getTextPreviewDefaultCloudTarget(); await submitCloudLinks(links, { targetId: target.id || '', targetName: target.name || L.lbl_default_folder, currentPath: target.path || null, successMessage: L.txt_preview_links_submitted || L.msg_cloud_task_success, logPrefix: 'TXT Cloud Task Create Failed' }); } catch(e) { console.error('TXT preview parse links failed:', e); showToast(L.txt_preview_links_failed || L.str_action_failed, 'error'); } finally { state.parsingLinks = false; updateTextPreviewModalControls(modal); } } function showTextPreviewModal(fileInfo) { const L = getStrings(); const title = (fileInfo && fileInfo.name) || L.txt_preview_title; const parseLabel = formatTextPreviewShortcutLabel(L.txt_preview_parse_links, CONF.txtPreviewHotkeys.parse); const fullLabel = formatTextPreviewShortcutLabel(L.txt_preview_fullscreen, CONF.txtPreviewHotkeys.fullscreen); const closeLabel = formatTextPreviewShortcutLabel(L.txt_preview_close || L.btn_close || L.btn_cancel, CONF.txtPreviewHotkeys.close); document.querySelectorAll('.pk-modal-ov').forEach(ov => { if (ov.querySelector('.pk-txt-preview')) ov.remove(); }); const state = { text: '', loading: true, error: false, empty: false, fileName: title, parsingLinks: false, fullscreen: false }; const m = showModal(`

${esc(title)}


`); m.classList.add('pk-txt-preview-ov'); const modalBox = m.querySelector('.pk-modal'); if (modalBox) modalBox.classList.add('pk-txt-preview-modal'); textPreviewStateMap.set(m, state); const titleEl = m.querySelector('.pk-txt-preview-title'); if (titleEl) { titleEl.removeAttribute('title'); titleEl.removeAttribute('data-pk-tip'); } const originalRemove = m.remove.bind(m); m.remove = () => { cleanupTextPreviewModal(m); originalRemove(); }; const parseBtn = m.querySelector('#pk_txt_preview_parse_links'); const maxBtn = m.querySelector('#pk_txt_preview_fullscreen'); const closeBtn = m.querySelector('.pk-modal-close'); if (parseBtn) parseBtn.onclick = () => { handleTextPreviewParseLinks(m); }; if (maxBtn) maxBtn.onclick = () => toggleTextPreviewFullscreen(m); setTextPreviewButtonTip(closeBtn, closeLabel); setupTextPreviewShortcutKeys(m); updateTextPreviewModalState(m, 'loading'); return m; } function updateTextPreviewModalState(modal, mode, payload = {}) { if (!modal || !document.contains(modal)) return; const L = getStrings(); const statusEl = modal.querySelector('.pk-txt-preview-status'); const preEl = modal.querySelector('.pk-txt-preview-pre'); const bodyEl = modal.querySelector('.pk-txt-preview-body'); if (!statusEl || !preEl) return; const previewState = getTextPreviewState(modal); if (previewState) { previewState.loading = mode === 'loading'; previewState.error = mode === 'error' || mode === 'too_large'; previewState.empty = mode === 'empty'; if (mode === 'success') previewState.text = String(payload.text || ''); else previewState.text = ''; } const showStatus = (key, isError = false) => { preEl.style.display = 'none'; preEl.textContent = ''; statusEl.style.display = 'flex'; statusEl.classList.toggle('pk-txt-preview-error', !!isError); statusEl.textContent = L[key] || L.txt_preview_load_failed || L.str_action_failed; }; if (mode === 'success') { statusEl.style.display = 'none'; statusEl.classList.remove('pk-txt-preview-error'); preEl.style.display = 'block'; preEl.textContent = String(payload.text || ''); } else if (mode === 'empty') showStatus('txt_preview_empty', false); else if (mode === 'too_large') showStatus('txt_preview_too_large', true); else if (mode === 'error') showStatus(payload.key || 'txt_preview_load_failed', true); else showStatus('txt_preview_loading', false); if (bodyEl) bodyEl.scrollTop = 0; updateTextPreviewModalControls(modal); } function updateTextPreviewModalError(modal, err) { const code = err && err.code; if (code === 'too_large') updateTextPreviewModalState(modal, 'too_large'); else if (code === 'decode_failed') updateTextPreviewModalState(modal, 'error', { key: 'txt_preview_decode_failed' }); else updateTextPreviewModalState(modal, 'error', { key: 'txt_preview_load_failed' }); } function isTextPreviewRequestCurrent(reqId, modal, isShare, fileId = '') { if (!modal || !document.contains(modal)) return false; if (activeTextPreviewReqId !== reqId) return false; if (!isShare) return true; return !!S.shareParseMode && String(activeShareTextPreviewFileId || '') === String(fileId || ''); } async function handleOpenTextFile(item) { if (!item || item._isShareItem) return; const modal = showTextPreviewModal(item); const reqId = ++activeTextPreviewReqId; activeShareTextPreviewFileId = ''; const isCurrent = () => isTextPreviewRequestCurrent(reqId, modal, false); try { const itemUrl = getTextDirectUrlFromFileInfo(item); let detail = item; const readFromDetail = async (fileInfo) => { const text = await readTextFromFileInfo(fileInfo); if (!isCurrent()) return; updateTextPreviewModalState(modal, text.length ? 'success' : 'empty', { text }); }; try { if (!itemUrl) { if (!item.id) throw makeTextPreviewError('load_failed'); detail = await apiGet(item.id); } await readFromDetail(detail); } catch(e) { if (itemUrl && e && e.code === 'load_failed') { if (!item.id) throw makeTextPreviewError('load_failed', e); detail = await apiGet(item.id); await readFromDetail(detail); } else { throw e; } } } catch(e) { if (isCurrent()) updateTextPreviewModalError(modal, e); } } async function handleOpenShareTextFile(item) { if (!item || !item._isShareItem) return; const modal = showTextPreviewModal(item); const reqId = ++activeTextPreviewReqId; const fileId = String(item.id || item.file_id || ''); activeShareTextPreviewFileId = fileId; const isCurrent = () => isTextPreviewRequestCurrent(reqId, modal, true, fileId); try { const detail = await fetchShareFileInfo(item); if (!isCurrent()) return; const text = await readTextFromFileInfo(detail); if (!isCurrent()) return; updateTextPreviewModalState(modal, text.length ? 'success' : 'empty', { text }); } catch(e) { if (isCurrent()) updateTextPreviewModalError(modal, e); } } function getShareFileInfoAccessToken(detail) { const apps = Array.isArray(detail && detail.apps) ? detail.apps : []; for (const app of apps) { const link = String((app && app.link) || ''); if (!link || link.indexOf('access_token=') < 0) continue; try { const u = new URL(link); const token = u.searchParams.get('access_token'); if (token) return token; } catch(e) { const m = link.match(/[?&]access_token=([^&#]+)/); if (m) return decodeURIComponent(m[1]); } } return ''; } function getShareDirectUrl(detail) { if (!detail) return ''; if (detail.web_content_link) return detail.web_content_link; const links = detail.links || {}; const keys = Object.keys(links || {}); for (const k of keys) { const v = links[k]; if (v && v.url) return v.url; } const medias = Array.isArray(detail.medias) ? detail.medias : []; for (const m of medias) { if (m && m.link && m.link.url) return m.link.url; } return ''; } async function fetchShareFileInfo(item) { const info = S.shareParseInfo || {}; const shareId = item._shareId || info.share_id || ''; const passToken = item._passCodeToken || info.pass_code_token || ''; if (!shareId || !passToken) throw makeShareParseError('msg_share_unzip_no_access'); const fileId = item.id || item.file_id || ''; const url = `https://api-drive.mypikpak.com/drive/v1/share/file_info?share_id=${encodeURIComponent(shareId)}&file_id=${encodeURIComponent(fileId)}&pass_code_token=${encodeURIComponent(passToken)}`; const res = await fetch(url, { method: 'GET', headers: getHeaders() }); syncTime(res.headers); const data = await res.json().catch(() => ({})); if (!res.ok || (data.share_status && data.share_status !== 'OK')) throw makeShareParseError(resolveShareDetailErrorKey(res.status, data), res.status, data); return data.file_info || data.file || data; } function mergeSharePlayableDetail(item, detail) { if (!item || !detail) return item; const directUrl = getShareDirectUrl(detail); Object.assign(item, { name: detail.name || item.name, size: detail.size || item.size, mime_type: detail.mime_type || item.mime_type, icon_link: detail.icon_link || item.icon_link, thumbnail_link: detail.thumbnail_link || item.thumbnail_link, web_content_link: directUrl || item.web_content_link || '', medias: Array.isArray(detail.medias) ? detail.medias : (item.medias || []), hash: detail.hash || detail.gcid || item.hash || '', gcid: detail.hash || detail.gcid || item.gcid || '', md5_checksum: detail.md5_checksum || item.md5_checksum || '', params: Object.assign({}, item.params || {}, detail.params || {}), _shareResolvedDetail: detail, _pkSkipDurationProbe: true }); return item; } async function resolveSharePlayableFile(item) { if (!item || !item._isShareItem) return item; if (item._shareResolvedDetail && (item.web_content_link || (Array.isArray(item.medias) && item.medias.length) || isArchiveLike(item))) return item; const detail = await fetchShareFileInfo(item); return mergeSharePlayableDetail(item, detail); } function parseShareRestoreTraceMap(raw) { const data = getShareParsePayload(raw); const params = data.params || (data.task && data.task.params) || raw.params || {}; let trace = params.trace_file_ids || params.traceFileIds || data.trace_file_ids || data.traceFileIds || ''; if (!trace) return {}; if (typeof trace === 'object') return trace; try { return JSON.parse(trace); } catch(e) {} return {}; } async function restoreShareFileForOpen(item, detail = null) { const info = S.shareParseInfo || {}; const sourceId = getShareParseTraceFileId(item) || (detail && detail.id) || item.id || ''; const ancestorIds = Array.from(new Set(getShareParseAncestorIds(item))); const payload = { share_id: item._shareId || info.share_id || '', pass_code_token: item._passCodeToken || info.pass_code_token || '', file_ids: [], params: { trace_file_ids: sourceId } }; if (ancestorIds.length) payload.ancestor_ids = ancestorIds; const res = await fetch('https://api-drive.mypikpak.com/drive/v1/share/restore', { method: 'POST', headers: getHeaders(), body: JSON.stringify(payload) }); syncTime(res.headers); const data = await res.json().catch(() => ({})); if (!res.ok) throw makeShareParseError(resolveShareParseSaveErrorKey(res.status, data), res.status, data); const key = resolveShareParseSaveErrorKey(0, data); if (key) throw makeShareParseError(key, 200, data); const taskIds = extractShareParseSaveTaskIds(data); if (!taskIds.length) return data.file_id || data.fileId || ''; for (let loop = 0; loop < 60; loop++) { await sleep(1000); for (const taskId of taskIds) { const tRes = await fetch(`https://api-drive.mypikpak.com/drive/v1/tasks/${encodeURIComponent(taskId)}?_t=${Date.now()}`, { headers: getHeaders() }); if (!tRes.ok) continue; const tData = await tRes.json().catch(() => ({})); const phase = String(tData.phase || (tData.task && tData.task.phase) || '').toUpperCase(); const tKey = resolveShareParseSaveErrorKey(0, tData); if (tKey || phase === 'PHASE_TYPE_ERROR') throw makeShareParseError(tKey || 'msg_share_parse_save_task_failed', 200, tData); if (phase === 'PHASE_TYPE_COMPLETE' || phase === 'PHASE_TYPE_COMPLETED' || phase === 'COMPLETE' || phase === 'SUCCESS') { const map = parseShareRestoreTraceMap(tData); return map[sourceId] || Object.values(map)[0] || tData.file_id || (tData.task && tData.task.file_id) || ''; } } } throw makeShareParseError('msg_share_parse_save_task_failed'); } async function submitShareArchiveUnzip(item, detail, pwdValue, archiveFiles) { const restoredId = await restoreShareFileForOpen(item, detail); if (!restoredId) throw makeShareParseError('msg_share_parse_save_task_failed'); const savedFile = Object.assign({}, detail || item, { id: restoredId, _isShareItem: false }); return sendUnzipRequest(savedFile, pwdValue || "", archiveFiles || []); } async function handleOpenShareArchive(item) { const L = getStrings(); if (!item || !item._isShareItem) return; setLoad(true); updateLoadTxt(L.loading); try { const detail = await fetchShareFileInfo(item); mergeSharePlayableDetail(item, detail); const token = getShareFileInfoAccessToken(detail); if (!token) throw makeShareParseError('msg_share_unzip_no_access'); const gcid = detail.hash || detail.gcid || item.hash || item.gcid || ''; setLoad(false); await showArchivePreview(item, "", { title: detail.name || item.name, gcid, fileId: detail.id || item.id, listUrl: `https://api-drive.mypikpak.com/decompress/v1/list?access_token=${encodeURIComponent(token)}`, getHeaders: getHeaders, submitUnzip: (pwd, files) => submitShareArchiveUnzip(item, detail, pwd, files) }); } catch (e) { setLoad(false); showToast(getShareParseOfficialErrorMessage(e) || ((e && e.message && !/^msg_/.test(e.message) && !e.shareParseKey) ? e.message : '') || L.str_action_failed, 'error'); } } async function handleOpenShareFile(item) { const L = getStrings(); if (!item || !item._isShareItem) return; setLoad(true); updateLoadTxt(L.loading); try { const resolved = await resolveSharePlayableFile(item); setLoad(false); if (isAudioLikeItem(resolved)) playAudio(resolved); else if (isVideoLikeItem(resolved)) playVideo(resolved); else if (isImageLikeItem(resolved)) showImage(resolved); } catch (e) { setLoad(false); const key = e && e.shareParseKey; showToast((key && L[key]) || getShareParseErrorMessage(e) || e.message || L.str_action_failed, 'error'); } } function handleShareParseFolderOpen(item) { if (!item || !item._isShareItem || item.kind !== 'drive#folder') return; loadShareParseFolder(item, { append: false }); } function handleShareParseCrumbClick(index) { if (!S.shareParseMode || S.shareParseInsightMode) return; const path = Array.isArray(S.shareParsePath) ? S.shareParsePath : []; if (index <= 0) { S.shareParseLatestChildId = null; resetShareParseListState(false); renderShareParseCurrentView(); return; } if (gmGet('pk_keep_pos', true) && index < path.length - 1) { S.shareParseLatestChildId = (path[index + 1] && path[index + 1].id) || null; } else { S.shareParseLatestChildId = null; } const targetPath = path.slice(0, index + 1); const target = targetPath[targetPath.length - 1]; if (!target) return; loadShareParseFolder({ id: target.id || '', name: target.name || '', parent_id: target.parent_id || '', _sharePath: targetPath }, { append: false }); } async function handleShareParseSubmit() { const L = getStrings(); if (S.shareParseLoading) return; const linkInput = UI.in ? UI.in.querySelector('#pk-share-parse-link') : null; const passInput = UI.in ? UI.in.querySelector('#pk-share-parse-pass') : null; const raw = linkInput ? linkInput.value : ''; const autoPassCode = parseSharePassCode(raw); const passCode = (passInput ? passInput.value.trim() : '') || autoPassCode; if (passInput && !passInput.value.trim() && autoPassCode) passInput.value = autoPassCode; S.shareParseDraft = { raw, pass_code: passCode }; if (!raw.trim()) { S.shareParseInfo = null; S.shareParseError = L.msg_share_parse_empty_link; resetShareParseListState(false); showToast(L.msg_share_parse_empty_link, 'warning'); renderShareParsePanel(); return; } const shareId = parseShareInput(raw); if (!shareId) { S.shareParseInfo = null; S.shareParseError = L.msg_share_parse_invalid_link; resetShareParseListState(false); showToast(L.msg_share_parse_invalid_link, 'error'); renderShareParsePanel(); return; } const reqId = (S.shareParseReqId || 0) + 1; S.shareParseReqId = reqId; S.shareParseLoading = true; S.shareParseError = ''; S.shareParseInfo = null; resetShareParseListState(false); renderShareParsePanel(); try { const rawInfo = await fetchShareParseInfo(shareId, passCode); if (!S.shareParseMode || S.shareParseReqId !== reqId) return; const info = buildShareParseInfo(shareId, passCode, rawInfo); if (!info.pass_code_token) throw makeShareParseError('msg_share_parse_bad_pass'); S.shareParseInfo = info; S.shareParseError = ''; S.shareParseLoading = false; showToast(L.msg_share_parse_success); await loadShareParseFolder(null, { append: false }); } catch(e) { if (!S.shareParseMode || S.shareParseReqId !== reqId) return; const msg = getShareParseErrorMessage(e); S.shareParseInfo = null; S.shareParseError = msg; resetShareParseListState(false); showToast(msg, 'error'); } finally { if (S.shareParseReqId === reqId) { S.shareParseLoading = false; if (S.shareParseMode) renderShareParseCurrentView(); } } } function renderShareParsePanel() { const L = getStrings(); const draft = S.shareParseDraft || { raw: '', pass_code: '' }; const info = S.shareParseInfo || null; const isLoading = !!S.shareParseLoading; if (!S.shareParseListActive) clearShareParseListMirror(); const disabledAttr = isLoading ? ' disabled' : ''; setSearchWrapVisible(false); if (UI.searchClear) UI.searchClear.style.display = 'none'; if (UI.lblSearchPath) UI.lblSearchPath.style.display = 'none'; if (UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if (UI.stat) UI.stat.style.display = 'none'; const resultHtml = (() => { if (S.shareParseError) return `
${esc(S.shareParseError)}
`; return ''; })(); if (UI.win) UI.win.classList.remove('pk-grid-view', 'pk-view-switching', 'pk-grid-resizing', 'pk-grid-scrolling', 'pk-mode-trash'); const hd = UI.win ? UI.win.querySelector('.pk-grid-hd') : null; if (hd) { hd.style.display = 'none'; hd.style.visibility = ''; hd.classList.remove('pk-grid-view-hd'); } if (UI.vp) { UI.vp.scrollTop = 0; UI.vp.scrollLeft = 0; } if (UI.in) { if (typeof clearGridStableRows === 'function') clearGridStableRows(UI.in); UI.in.style.height = '100%'; UI.in.style.transform = 'none'; UI.in.innerHTML = `
${resultHtml}
`; const linkInput = UI.in.querySelector('#pk-share-parse-link'); const passInput = UI.in.querySelector('#pk-share-parse-pass'); const btn = UI.in.querySelector('#pk-share-parse-submit'); const clipboardBtn = UI.in.querySelector('#pk-share-parse-clipboard'); const syncDraft = () => { S.shareParseDraft = { raw: linkInput ? linkInput.value : '', pass_code: passInput ? passInput.value : '' }; }; if (linkInput) { linkInput.oninput = syncDraft; linkInput.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); handleShareParseSubmit(); } }; } if (passInput) { passInput.oninput = syncDraft; passInput.onkeydown = (e) => { if (e.key === 'Enter') { e.preventDefault(); handleShareParseSubmit(); } }; } if (btn) btn.onclick = handleShareParseSubmit; if (clipboardBtn) clipboardBtn.onclick = handleShareParseClipboardImport; } if (UI.pop) { UI.pop.style.display = 'none'; UI.pop.innerHTML = ''; } if (UI.ctx) UI.ctx.style.display = 'none'; } function renderList() { if (S.shareParseMode && !S.shareParseListActive) { renderShareParsePanel(); return; } if (S.shareParseMode) { if (UI.win) { UI.win.classList.add('pk-share-parse-mode'); UI.win.classList.remove('pk-analyze-group-tools-on'); } if (UI.btnAnaSelect) UI.btnAnaSelect.style.display = 'none'; if (UI.btnAnaSort) UI.btnAnaSort.style.display = 'none'; syncShareParseMirror(); } else if (UI.win) { UI.win.classList.remove('pk-share-parse-mode'); } syncLayoutMetrics(); UI.win.classList.toggle('pk-grid-view', isGridView()); renderViewSwitch(); const headerEl = UI.win.querySelector('.pk-grid-hd'); if (headerEl) headerEl.style.display = ''; const isEmptyDisplay = S.display.length === 0; if (isGridView()) { const gridLayout = getGridLayout(); S._gridLayoutKey = getGridLayoutKey(); if (isEmptyDisplay) { clearGroupedGridMetaCache(); UI.in.style.height = '100%'; } else if (isGroupedGridView()) { const dupGridMeta = getGroupedGridMeta(true); UI.in.style.height = `${Math.max(0, dupGridMeta ? dupGridMeta.totalHeight : 0)}px`; scheduleGridRelayout(); } else { clearGroupedGridMetaCache(); UI.in.style.height = `${Math.ceil(S.display.length / gridLayout.cols) * CONF.rowHeight}px`; scheduleGridRelayout(); } } else { S._gridLayoutKey = ''; clearGroupedGridMetaCache(); clearGridStableRows(UI.in); if (UI.win) UI.win.classList.remove('pk-grid-view', 'pk-grid-resizing', 'pk-grid-scrolling'); UI.in.style.height = isEmptyDisplay ? '100%' : `${S.display.length * CONF.rowHeight}px`; } let colDef; const cur = S.path[S.path.length - 1]; const isAnalyzeRoot = S.analyzeMode && cur.id === 'analyze_root'; const isGlobalSearchRoot = cur.id === 'virtual_search_root' && UI.chkGlobal && UI.chkGlobal.checked; const showShareInsightPathCol = !!(S.shareParseMode && S.shareParseInsightMode); const showPathCol = S.shareParseMode ? showShareInsightPathCol : (S.dupMode || isAnalyzeRoot || S.isFlattened || isGlobalSearchRoot); const isMax = UI.win.classList.contains('pk-maximized'); if (S.offlineMode) { colDef = "36px 1fr 120px 240px 180px"; } else if (S.uploadMode) { colDef = "36px 1fr 100px 120px 180px"; } else if (S.shareMode) { colDef = isMax ? "36px 1fr 80px 80px 120px 130px" : "36px 1fr 80px 80px 80px 130px"; } else if (S.shareParseMode) { colDef = S.shareParseInsightMode ? "36px 2fr 1fr 80px 120px 130px" : "36px 1fr 80px 120px 130px"; } else if (S.historyMode) { colDef = "36px 30px 1fr 80px 80px 120px 160px"; } else if (S.trashMode) { colDef = "36px 30px 1fr 80px 120px 130px"; } else if (S.recentMode) { colDef = "36px 30px 1fr 80px 120px 130px"; } else if (isAnalyzeRoot) { colDef = "36px 30px 2fr 1fr 80px 130px"; } else if (showPathCol) { colDef = "36px 30px 2fr 1fr 80px 120px 130px"; } else { colDef = "36px 30px 1fr 80px 120px 130px"; } const hd = UI.win.querySelector('.pk-grid-hd'); if (hd) { if (!S._folderViewSyncHold && !S._locateGridSyncPending) hd.style.visibility = ''; if (isGridView()) { const gridMeta = getGridSortMeta(); const hideGroupedRootGridHeadTools = S.dupMode || (isAnalyzeRoot && S.analyzeSimGroups); const hideGridFolderFirst = S.historyMode || S.isFlattened || S.dupMode || isAnalyzeRoot || (S.shareParseMode && S.shareParseInsightMode); const keepHistoryGridHoverHead = S.historyMode && hd.classList.contains('pk-grid-view-hd') && !!hd.querySelector('.pk-grid-sort-wrap') && (hd.matches(':hover') || S.gridSortMenuOpen); if (keepHistoryGridHoverHead) { UI.chkAll = hd.querySelector('#pk-all'); if (UI.chkAll) UI.chkAll.onclick = S.handleSelectAll; const btnGridInv = hd.querySelector('#pk-grid-invert'); if (btnGridInv) { btnGridInv.style.display = (!S.dupMode && S.getSelectedCount() > 0) ? 'flex' : 'none'; btnGridInv.style.color = btnGridInv.matches(':hover') ? 'var(--pk-pri)' : 'var(--pk-fg)'; } UI.gridSortWrap = hd.querySelector('.pk-grid-sort-wrap'); if (UI.gridSortWrap) UI.gridSortWrap.classList.toggle('open', !!S.gridSortMenuOpen); } else { hd.classList.add('pk-grid-view-hd'); hd.style.gridTemplateColumns = ''; hd.innerHTML = `
${CONF.icons.invert} ${L.btn_invert}
${getGridSortOptions().map(opt => { const state = resolveGridSortOptionState(opt); return `
${state.icon}${state.label}
`; }).join('')}
`; UI.chkAll = hd.querySelector('#pk-all'); if (UI.chkAll) UI.chkAll.onclick = S.handleSelectAll; UI.btnGridFolderFirst = hd.querySelector('#pk-grid-folder-first'); if (UI.btnGridFolderFirst) { UI.btnGridFolderFirst.onclick = (e) => { e.stopPropagation(); toggleFolderFirst(); }; } const btnGridInv = hd.querySelector('#pk-grid-invert'); if (btnGridInv) { btnGridInv.onclick = (e) => { e.stopPropagation(); if (S.display.length === 0) return; S.invertSelection(); renderVisible(); updateStat(); }; btnGridInv.onmouseenter = () => btnGridInv.style.color = 'var(--pk-pri)'; btnGridInv.onmouseleave = () => btnGridInv.style.color = 'var(--pk-fg)'; } UI.gridSortWrap = hd.querySelector('.pk-grid-sort-wrap'); if (UI.gridSortWrap && S.gridSortMenuOpen) UI.gridSortWrap.classList.add('open'); const sortTrigger = hd.querySelector('#pk-grid-sort-trigger'); if (sortTrigger) { sortTrigger.onclick = (e) => { e.stopPropagation(); if (!UI.gridSortWrap) return; const willOpen = !UI.gridSortWrap.classList.contains('open'); S.gridSortMenuOpen = willOpen; UI.gridSortWrap.classList.toggle('open', willOpen); }; } bindGridSortOptions(hd); if (!S.gridSortDismissBound) { document.addEventListener('mousedown', closeGridSortMenu, true); document.addEventListener('scroll', closeGridSortMenu, true); window.addEventListener('resize', closeGridSortMenu); S.gridSortDismissBound = true; } } } else { hd.classList.remove('pk-grid-view-hd'); hd.style.gridTemplateColumns = colDef; UI.gridSortWrap = null; closeGridSortMenu(); } if (!isGridView() && S.offlineMode) { if (!hd.querySelector('[data-k="offline_status"]')) { hd.innerHTML = `
${L.col_name}
${L.col_size}
${L.col_task_status}
${L.col_task_progress}
`; const chk = hd.querySelector('#pk-all'); chk.onclick = S.handleSelectAll; UI.chkAll = chk; } } else if (!isGridView() && S.historyMode) { if (!hd.querySelector('[data-k="play_time"]')) { hd.innerHTML = `
${L.col_name}
${CONF.icons.invert} ${L.btn_invert}
${L.col_size}
${L.col_duration_only}
${L.col_progress}
${L.col_play_time}
`; const chk = hd.querySelector('#pk-all'); chk.onclick = S.handleSelectAll; UI.chkAll = chk; const btnInv = hd.querySelector('#pk-btn-invert'); if (btnInv) { btnInv.onclick = (e) => { e.stopPropagation(); if (S.display.length === 0) return; S.invertSelection(); renderVisible(); updateStat(); }; btnInv.onmouseenter = () => btnInv.style.color = 'var(--pk-pri)'; btnInv.onmouseleave = () => btnInv.style.color = 'var(--pk-fg)'; } } } else if (!isGridView() && S.uploadMode) { if (!hd.querySelector('[data-k="upload_speed"]')) { hd.innerHTML = `
${L.col_name}
${L.col_size}
${L.col_up_speed}
${L.col_up_status}
`; const chk = hd.querySelector('#pk-all'); chk.onclick = S.handleSelectAll; UI.chkAll = chk; } } else if (!isGridView() && S.shareMode) { if (!hd.querySelector('[data-k="share_status"]')) { hd.innerHTML = `
${L.col_name}
${L.col_view}
${L.col_save}
${L.col_share_status}
${L.col_share_time}
`; const chk = hd.querySelector('#pk-all'); chk.onclick = S.handleSelectAll; UI.chkAll = chk; const btnInv = hd.querySelector('#pk-btn-invert'); if(btnInv) { btnInv.onclick = (e) => { e.stopPropagation(); if (S.display.length === 0) return; S.invertSelection(); renderVisible(); updateStat(); }; btnInv.onmouseenter = () => btnInv.style.color = 'var(--pk-pri)'; btnInv.onmouseleave = () => btnInv.style.color = 'var(--pk-fg)'; } } } else if (!isGridView()) { const isAnalyzeRoot = S.analyzeMode && cur.id === 'analyze_root'; const hasDurationCol = !!hd.querySelector('[data-k="duration"]'); const hasHistoryCol = !!hd.querySelector('[data-k="play_time"]'); const hasOfflineCol = !!hd.querySelector('[data-k="offline_status"]'); const missingListFolderFirst = !S.shareParseMode && !S.trashMode && !S.shareMode && !S.offlineMode && !S.uploadMode && !S.historyMode && !isAnalyzeRoot && !S.isFlattened && !S.dupMode && !hd.querySelector('#pk-btn-folder-first'); if (S.shareParseMode || missingListFolderFirst || hasDurationCol === isAnalyzeRoot || !hd.querySelector('[data-k="name"]') || hasHistoryCol || hasOfflineCol) { hd.innerHTML = `
${L.col_name}
${(!isAnalyzeRoot && !S.isFlattened && !S.dupMode && !(S.shareParseMode && S.shareParseInsightMode)) ? `
${CONF.icons.folderFirst} ${L.lbl_folder_first}
` : ''}
${CONF.icons.invert} ${L.btn_invert}
${L.col_size}
${!isAnalyzeRoot ? `
${L.col_dur}
` : ''}
${L.col_date}
`; const chk = hd.querySelector('#pk-all'); chk.onclick = S.handleSelectAll; UI.chkAll = chk; const btnFF = hd.querySelector('#pk-btn-folder-first'); if (btnFF) { UI.btnFolderFirst = btnFF; S.renderFolderFirst = () => { if (UI.btnFolderFirst) UI.btnFolderFirst.style.color = S.folderFirst ? 'var(--pk-pri)' : '#666'; if (UI.btnGridFolderFirst) UI.btnGridFolderFirst.style.color = S.folderFirst ? 'var(--pk-pri)' : '#666'; }; btnFF.onclick = (e) => { e.stopPropagation(); toggleFolderFirst(); }; if(S.renderFolderFirst) S.renderFolderFirst(); } else { UI.btnFolderFirst = null; } const btnInv = hd.querySelector('#pk-btn-invert'); if(btnInv) { btnInv.onclick = (e) => { e.stopPropagation(); if (S.display.length === 0) return; S.invertSelection(); renderVisible(); updateStat(); }; btnInv.onmouseenter = () => btnInv.style.color = 'var(--pk-pri)'; btnInv.onmouseleave = () => btnInv.style.color = 'var(--pk-fg)'; } } const colPaths = hd.querySelector('[data-k="path"]'); const colStar = hd.querySelector('[data-k="starred"]'); const colDur = hd.querySelector('[data-k="duration"]'); const colSize = hd.querySelector('[data-k="size"]'); if (colPaths) colPaths.style.display = showPathCol ? 'flex' : 'none'; if (colStar) { colStar.style.display = S.shareParseMode ? 'none' : 'flex'; colStar.style.opacity = '1'; colStar.style.pointerEvents = 'auto'; } if (colDur) { colDur.style.display = isAnalyzeRoot ? 'none' : 'flex'; colDur.style.opacity = '1'; colDur.style.pointerEvents = 'auto'; } if (colSize) colSize.style.display = 'flex'; const dateHeader = hd.querySelector('[data-k="modified_time"]'); if (dateHeader) { dateHeader.childNodes[0].textContent = S.trashMode ? L.col_remaining : L.col_date; } } } const currentCols = isGridView() ? [] : hd.querySelectorAll('.pk-col'); currentCols.forEach(c => { const span = c.querySelector('span'); if (span) { span.style.whiteSpace = 'pre'; span.style.fontSize = '11px'; span.style.lineHeight = '1'; } const isSimFolderView = (isAnalyzeRoot && S.analyzeSimGroups); const sortPrefKey = getSortPrefKey(); const legalSorts = sortPrefKey ? getLegalSortsForContext(sortPrefKey) : null; const isIllegalContextSort = !!(sortPrefKey && !legalSorts.includes(c.dataset.k)); if (S.dupMode || S.offlineMode || S.uploadMode || isSimFolderView || isIllegalContextSort) { c.style.cursor = 'default'; c.style.pointerEvents = 'none'; c.style.color = 'var(--pk-fg)'; if(span) span.textContent = ''; if (c.dataset.k === 'name') { const nameWrap = c.querySelector('#pk-name-text-wrap'); if (nameWrap) nameWrap.style.color = 'var(--pk-fg)'; } } else { c.style.cursor = 'pointer'; c.style.pointerEvents = 'auto'; c.onclick = () => { const k = c.dataset.k; applyManualSortSelection(k); }; if (span) span.textContent = (c.dataset.k === S.sort) ? (S.dir === 1 ? ' ▼' : ' ▲') : ''; if (c.dataset.k === 'name') { c.style.color = ''; const nameWrap = c.querySelector('#pk-name-text-wrap'); if (nameWrap) { const syncNameHover = () => nameWrap.style.color = (S.sort === 'name') ? 'var(--pk-pri)' : (nameWrap.matches(':hover') ? 'var(--pk-fg)' : '#666'); syncNameHover(); nameWrap.onmouseenter = syncNameHover; nameWrap.onmouseleave = syncNameHover; } } else { c.style.color = (c.dataset.k === S.sort) ? 'var(--pk-pri)' : ''; } } }); UI.chkAll = hd.querySelector('#pk-all'); if (UI.chkAll) { UI.chkAll.onclick = S.handleSelectAll; const totalSelectable = S.getSelectableCount(); const selectedCount = S.getSelectedCount(); UI.chkAll.checked = totalSelectable > 0 && selectedCount === totalSelectable; UI.chkAll.indeterminate = selectedCount > 0 && selectedCount < totalSelectable; } requestAnimationFrame(() => { renderVisible(); if (!isGridView()) { requestAnimationFrame(() => { endFolderViewSync(); if (UI.win) UI.win.classList.remove('pk-view-switching'); }); } }); } function renderVisible() { const L = getStrings(); if (S.shareParseMode && !S.shareParseListActive) { renderShareParsePanel(); return; } if (S.shareParseMode) { if (typeof applyResolvedSortState === 'function') applyResolvedSortState(); syncShareParseMirror(); } const isGridMode = isGridView(); const gridLayout = isGridMode ? getGridLayout() : null; const winEl = el.querySelector('.pk-win'); const listIn = el.querySelector('#pk-in'); if (winEl && listIn) { const isMax = winEl.classList.contains('pk-maximized'); const cur = S.path[S.path.length - 1]; const isGroupedView = S.dupMode || (S.analyzeMode && cur.id === 'analyze_root' && S.analyzeSimGroups); const gap = isGroupedView ? (isMax ? 10 : 4) : 0; listIn.style.transform = gap > 0 ? `translateY(-${gap}px)` : 'none'; } if (S.display.length === 0) { clearGridStableRows(UI.in); if (S.shareParseMode && S.shareParseListActive) { if (S.shareParseInsightMode) { if (S.shareParseInsightError) { renderShareParseListStatus(S.shareParseInsightError, 'err'); return; } } if (S.shareParseListLoading) { renderShareParseListStatus(L.msg_share_parse_loading_files, 'loading'); return; } if (S.shareParseListError) { renderShareParseListStatus(S.shareParseListError, 'err'); return; } } if (S.loading) { const keepProgressiveSearchLoader = !!((S.offlineMode || S.recentMode) && S.search); const keepHistoryLoader = !!S.historyMode; const shouldKeepExistingLoader = keepProgressiveSearchLoader || keepHistoryLoader; if (shouldKeepExistingLoader && UI.in.querySelector('[data-pk-progressive-search-loading="1"]')) { UI.in.style.height = '100%'; UI.pop.style.display = 'none'; UI.pop.innerHTML = ''; if (UI.ctx) UI.ctx.style.display = 'none'; return; } UI.in.style.height = '100%'; UI.in.innerHTML = `
${L.loading_detail}
`; UI.pop.style.display = 'none'; UI.pop.innerHTML = ''; if (UI.ctx) UI.ctx.style.display = 'none'; return; } if (S.items.length > 0 && !S.search && !S.dupMode && !S.trashMode && !S.shareMode && !S.offlineMode && !S.uploadMode && !S.isFlattened && !(S.shareParseMode && S.shareParseInsightMode)) { return; } if (UI.in.querySelector('.pk-empty')) { if (UI.in.style.height !== '100%') UI.in.style.height = '100%'; return; } UI.in.style.height = '100%'; UI.in.innerHTML = `
${CONF.emptySVG}
${L.str_no_files}
`; UI.pop.style.display = 'none'; UI.pop.innerHTML = ''; if (UI.ctx) UI.ctx.style.display = 'none'; return; } const dupGridMeta = isGroupedGridView() ? getGroupedGridMeta() : null; if (isGridMode && gridLayout) { UI.in.style.height = `${Math.max(0, dupGridMeta ? dupGridMeta.totalHeight : Math.ceil(S.display.length / gridLayout.cols) * CONF.rowHeight + gridLayout.gapY)}px`; } else { UI.in.style.height = `${S.display.length * CONF.rowHeight}px`; } const top = UI.vp.scrollTop; const h = UI.vp.clientHeight; const buffer = CONF.buffer || 20; let start = 0; let end = 0; let visibleIndices = null; if (dupGridMeta) { const metaGridLayout = dupGridMeta.gridLayout || gridLayout || {}; const toFiniteNumber = (value) => { const n = Number(value); return Number.isFinite(n) ? n : null; }; const toPositiveNumber = (value) => { const n = toFiniteNumber(value); return n !== null && n > 0 ? n : 0; }; const clampInt = (value, min, max) => Math.max(min, Math.min(max, value)); const gridCardHeight = toPositiveNumber(metaGridLayout.cardHeight); const gridGapY = Math.max(0, toFiniteNumber(metaGridLayout.gapY) || 0); const baseGridRowHeight = toPositiveNumber(dupGridMeta.rowStride) || (gridCardHeight ? gridCardHeight + gridGapY : (toPositiveNumber(CONF.rowHeight) || 1)); const dupGridBufferRows = Math.max(3, Math.min(5, Math.ceil(buffer / 5))); const sectionBuffer = dupGridBufferRows * baseGridRowHeight; const rangeTop = top - sectionBuffer; const rangeBottom = top + h + sectionBuffer; visibleIndices = []; const appendDupGridItemWindow = (itemIndices, cols, rowStart, rowEnd) => { if (!itemIndices.length || rowEnd < rowStart) return; const localStart = Math.max(0, rowStart * cols); const localEnd = Math.min(itemIndices.length - 1, ((rowEnd + 1) * cols) - 1); for (let localIdx = localStart; localIdx <= localEnd; localIdx++) { visibleIndices.push(itemIndices[localIdx]); } }; const appendDupGridLimitedFallback = (section, itemIndices, cols, rowHeight, rows) => { const safeRowHeight = rowHeight || baseGridRowHeight; const safeRows = rows || Math.ceil(itemIndices.length / cols); if (!itemIndices.length || safeRows <= 0) return; const visibleRows = Math.max(1, Math.ceil(h / safeRowHeight)); const padRows = dupGridBufferRows; const maxRows = Math.min(safeRows, visibleRows + padRows * 2); const bodyTop = toFiniteNumber(section.bodyTop); const sectionTop = toFiniteNumber(section.top); const approxBodyTop = bodyTop !== null ? bodyTop : (sectionTop !== null ? sectionTop : top); const estimatedStart = Math.floor((top - approxBodyTop) / safeRowHeight) - padRows; const rowStart = clampInt(estimatedStart, 0, Math.max(0, safeRows - maxRows)); appendDupGridItemWindow(itemIndices, cols, rowStart, rowStart + maxRows - 1); }; const appendVisibleDupGridSectionItems = (section) => { const itemIndices = Array.isArray(section.itemIndices) ? section.itemIndices : []; if (!itemIndices.length) return; const cols = Math.max(1, Math.floor( toPositiveNumber(section.cols) || toPositiveNumber(dupGridMeta.cols) || toPositiveNumber(metaGridLayout.cols) || 1 )); const rows = Math.max(1, Math.floor(toPositiveNumber(section.rows) || Math.ceil(itemIndices.length / cols))); const bodyHeight = toPositiveNumber(section.bodyHeight); const bodyTop = toFiniteNumber(section.bodyTop); const rowHeightFromSection = toPositiveNumber(section.rowStride) || toPositiveNumber(section.rowHeight); const rowHeightFromBody = bodyHeight && rows ? bodyHeight / rows : 0; const rowHeightFromGrid = gridCardHeight ? gridCardHeight + gridGapY : 0; const rowHeight = rowHeightFromSection || rowHeightFromBody || rowHeightFromGrid || toPositiveNumber(CONF.rowHeight); const usedConfFallback = !rowHeightFromSection && !rowHeightFromBody && !rowHeightFromGrid; if (bodyTop === null || !rowHeight) { appendDupGridLimitedFallback(section, itemIndices, cols, rowHeight || baseGridRowHeight, rows); return; } const resolvedBodyHeight = bodyHeight || rows * rowHeight; const bodyBottom = bodyTop + resolvedBodyHeight; if (!(bodyTop < rangeBottom && bodyBottom > rangeTop)) return; let rowStart = clampInt(Math.floor((rangeTop - bodyTop) / rowHeight), 0, rows - 1); let rowEnd = clampInt(Math.ceil((rangeBottom - bodyTop) / rowHeight) - 1, 0, rows - 1); if (usedConfFallback) { const maxRows = Math.max(1, Math.ceil(h / rowHeight) + dupGridBufferRows * 2); rowEnd = Math.min(rowEnd, rowStart + maxRows - 1); } appendDupGridItemWindow(itemIndices, cols, rowStart, rowEnd); }; const sections = Array.isArray(dupGridMeta.sections) ? dupGridMeta.sections : []; const sectionWindow = getDupGridSectionWindow(sections, rangeTop, rangeBottom); for (let sectionIdx = sectionWindow.start; sectionIdx < sectionWindow.end; sectionIdx++) { const section = sections[sectionIdx]; if (!section) continue; const sectionTop = toFiniteNumber(section.top); const sectionBottom = toFiniteNumber(section.bottom); if (sectionBottom !== null && sectionBottom < rangeTop) continue; if (sectionTop !== null && sectionTop > rangeBottom) break; const headerTop = toFiniteNumber(section.top); const headerHeight = toPositiveNumber(section.headerHeight || (dupGridMeta.sectionMetrics && dupGridMeta.sectionMetrics.headerHeight)); if (section.headerIndex !== null && headerTop !== null && headerHeight > 0) { const headerBottom = headerTop + headerHeight; if (headerTop < rangeBottom && headerBottom > rangeTop) visibleIndices.push(section.headerIndex); } appendVisibleDupGridSectionItems(section); } end = visibleIndices.length; } else { start = isGridMode && gridLayout ? Math.max(0, (Math.floor(top / CONF.rowHeight) - Math.ceil(buffer / 2)) * gridLayout.cols) : Math.max(0, Math.floor(top / CONF.rowHeight) - buffer); end = isGridMode && gridLayout ? Math.min(S.display.length, (Math.ceil((top + h) / CONF.rowHeight) + Math.ceil(buffer / 2)) * gridLayout.cols) : Math.min(S.display.length, Math.ceil((top + h) / CONF.rowHeight) + buffer); } const isBlur = isBlurEnabledForView(isGridMode ? 'grid' : 'list'); const rootPathStr = S.path.map(p => p.name).join('/'); const firstVisiblePathIndex = !isGridMode ? Math.max(0, Math.floor(top / Math.max(1, CONF.rowHeight))) : 0; const lang = getLang(); let colDef; const cur = S.path[S.path.length - 1]; const isAnalyzeRoot = S.analyzeMode && cur.id === 'analyze_root'; const isGlobalSearchRoot = cur.id === 'virtual_search_root' && UI.chkGlobal && UI.chkGlobal.checked; const showShareInsightPathCol = !!(S.shareParseMode && S.shareParseInsightMode); const showPathCol = S.shareParseMode ? showShareInsightPathCol : (S.dupMode || isAnalyzeRoot || S.isFlattened || isGlobalSearchRoot); const isMax = UI.win.classList.contains('pk-maximized'); if (S.offlineMode) { colDef = "36px 1fr 120px 240px 180px"; } else if (S.uploadMode) { colDef = "36px 1fr 100px 120px 180px"; } else if (S.shareMode) { colDef = isMax ? "36px 1fr 80px 80px 120px 130px" : "36px 1fr 80px 80px 80px 130px"; } else if (S.shareParseMode) { colDef = S.shareParseInsightMode ? "36px 2fr 1fr 80px 120px 130px" : "36px 1fr 80px 120px 130px"; } else if (S.historyMode) { colDef = "36px 30px 1fr 80px 80px 120px 160px"; } else if (S.trashMode) { colDef = "36px 30px 1fr 80px 120px 130px"; } else if (S.recentMode) { colDef = "36px 30px 1fr 80px 120px 130px"; } else if (isAnalyzeRoot) { colDef = "36px 30px 2fr 1fr 80px 130px"; } else if (showPathCol) { colDef = "36px 30px 2fr 1fr 80px 120px 130px"; } else { colDef = "36px 30px 1fr 80px 120px 130px"; } UI.pop.style.display = 'none'; UI.pop.innerHTML = ''; cleanupNonPooledChildren(UI.in); const visibleCount = dupGridMeta ? visibleIndices.length : Math.max(0, end - start); const rowPool = ensureVisibleRowPool(UI.in, visibleCount); let poolPtr = 0; const useHistoryStableRows = !!(!isGridMode && !dupGridMeta); if (useHistoryStableRows) { if (!UI.in._pkHistoryRowMap) UI.in._pkHistoryRowMap = new Map(); UI.in._pkHistoryStableRowMode = true; rowPool.forEach(row => { if (row) row._pkUsedThisPass = false; }); } else { UI.in._pkHistoryStableRowMode = false; if (UI.in._pkHistoryRowMap) UI.in._pkHistoryRowMap.clear(); } const takeHistoryStableRow = (itemId) => { let row = UI.in._pkHistoryRowMap.get(itemId) || null; if (row && row._pkUsedThisPass) row = null; if (!row) { row = rowPool.find(r => r && !r._pkUsedThisPass && (r.dataset.pkBoundId || '') === itemId) || rowPool.find(r => r && !r._pkUsedThisPass && r.parentNode !== UI.in) || rowPool.find(r => r && !r._pkUsedThisPass); if (!row) { row = document.createElement('div'); row._pkPooledRow = true; row.style.position = 'absolute'; rowPool.push(row); } const oldId = row.dataset.pkBoundId || ''; if (oldId && oldId !== itemId && UI.in._pkHistoryRowMap.get(oldId) === row) { UI.in._pkHistoryRowMap.delete(oldId); } UI.in._pkHistoryRowMap.set(itemId, row); } row._pkUsedThisPass = true; return row; }; let nameColWidth = 400; let charCapacity = 50; const pathIds = S.path.map(p => p.id); const isAtGlobalSearchRoot = pathIds[pathIds.length - 1] === 'virtual_search_root'; const isGlobalSearchHistoryPresent = pathIds.includes('virtual_search_root'); const shouldShowHl = (!!S.search && (!isGlobalSearchHistoryPresent || isAtGlobalSearchRoot)); let pathColWidth = 200, pathCharCapacity = 30; if (UI.win) { const charWidth = isMax ? 9.2 : 8; const nameColEl = UI.win.querySelector('.pk-grid-hd .pk-col[data-k="name"]'); if (nameColEl) { nameColWidth = nameColEl.offsetWidth; charCapacity = Math.floor((nameColWidth - 80) / charWidth); } const pathColEl = UI.win.querySelector('.pk-grid-hd .pk-col[data-k="path"]'); if (pathColEl) { pathColWidth = pathColEl.offsetWidth; pathCharCapacity = Math.floor((pathColWidth - 20) / charWidth); } } const getTooltipHlHTML = (text, query) => { if (!query || !shouldShowHl) return esc(text); const lowText = text.toLowerCase(); const q = query.toLowerCase(); const idx = lowText.indexOf(q); if (idx === -1) return esc(text); return esc(text.substring(0, idx)) + `${esc(text.substring(idx, idx + q.length))}` + getTooltipHlHTML(text.substring(idx + q.length), query); }; const getSearchHlHTML = (name, query, capacity) => { const q = query.toLowerCase(); const idx = name.toLowerCase().indexOf(q); if (idx === -1) return esc(name); let start = 0, end = name.length, prefix = "", suffix = ""; if (name.length > capacity) { const preLimit = Math.floor(capacity * 0.3); start = Math.max(0, idx - preLimit); end = Math.min(name.length, start + capacity); if (start > 0) prefix = "..."; if (end < name.length) suffix = "..."; } const targetSlice = name.substring(start, end); return prefix + getTooltipHlHTML(targetSlice, query) + suffix; }; const getSharePathSearchHlHTML = (text, query, capacity) => { const raw = String(text || ''); const q = normalizeSharePathSearchText(query); if (!q) return esc(raw); let norm = '', map = [], slash = false; for (let i = 0; i < raw.length; i++) { const ch = raw[i]; if (/[\\//]/.test(ch)) { while (norm.endsWith(' ')) { norm = norm.slice(0, -1); map.pop(); } norm += '/'; map.push(i); slash = true; continue; } if (slash && /\s/.test(ch)) continue; slash = false; norm += ch.toLowerCase(); map.push(i); } const idx = norm.indexOf(q); if (idx === -1 || !map[idx] && map[idx] !== 0) return getSearchHlHTML(raw, query, capacity); const matchStart = map[idx]; const matchEnd = (map[idx + q.length - 1] ?? matchStart) + 1; let start = 0, end = raw.length, prefix = '', suffix = ''; if (raw.length > capacity) { const cap = Math.max(q.length + 8, Math.floor(capacity || 30)); const preLimit = Math.max(0, Math.floor((cap - (matchEnd - matchStart)) * 0.3)); start = Math.max(0, matchStart - preLimit); end = Math.min(raw.length, start + cap); if (end < matchEnd) { end = matchEnd; start = Math.max(0, end - cap); } if (start > 0) prefix = '...'; if (end < raw.length) suffix = '...'; } const hiStart = Math.max(matchStart, start); const hiEnd = Math.min(matchEnd, end); return prefix + esc(raw.substring(start, hiStart)) + `${esc(raw.substring(hiStart, hiEnd))}` + esc(raw.substring(hiEnd, end)) + suffix; }; const getGridSearchHlHTML = (name, query, capacity) => { const text = String(name || ''); const q = String(query || '').toLowerCase(); if (!q) return esc(text); const idx = text.toLowerCase().indexOf(q); if (idx === -1) return esc(text); const cap = Math.max(q.length + 8, Math.floor(capacity || 18)); if (text.length <= cap && idx + q.length <= cap - 1) return getTooltipHlHTML(text, query); const preLimit = Math.max(3, Math.floor((cap - q.length) * 0.45)); let start = Math.max(0, idx - preLimit); let end = Math.min(text.length, start + cap); if (end - start < cap) start = Math.max(0, end - cap); const prefix = start > 0 ? "..." : ""; const suffix = end < text.length ? "..." : ""; return prefix + getTooltipHlHTML(text.substring(start, end), query) + suffix; }; const _internalGetStarIcon = (isStarred) => { const color = isStarred ? '#FFC107' : '#ccc'; const fill = isStarred ? '#FFC107' : 'none'; return ` `; }; const getRemainingDays = (source) => { const DAY_MS = 1000 * 60 * 60 * 24; const now = getServerNow(); const directKeys = ['_trash_remaining_days', 'remaining_days', 'remain_days', 'days_left', 'left_days', 'expiration_days_left', 'expire_days_left', 'expires_in_days', 'trash_remaining_days', 'purge_remaining_days']; const expireKeys = ['_trash_expire_time', 'expires_at', 'expire_time', 'expired_time', 'expiration_at', 'delete_at', 'delete_time', 'purge_at', 'purge_time', 'trashed_expire_time', 'trash_expire_time']; const deletedKeys = ['_trash_deleted_time', 'deleted_time', 'deleted_at', 'trashed_time', 'trashed_at', 'trash_time', 'recycled_time', 'recycle_time', 'modified_time']; const retentionKeys = ['_trash_retention_days', 'retention_days', 'reserve_days', 'keep_days']; const readValue = (obj, keys) => { if (!obj || typeof obj !== 'object') return undefined; const sources = [obj, obj.params, obj.audit, obj.delete_info, obj.trash_info, obj.recycle_info].filter(Boolean); for (const src of sources) { for (const key of keys) { if (src && src[key] !== undefined && src[key] !== null && src[key] !== '') return src[key]; } } return undefined; }; const toNumber = (v) => { if (v === undefined || v === null || v === '') return null; const n = Number(v); return Number.isFinite(n) ? n : null; }; const toTime = (v) => { if (v === undefined || v === null || v === '') return 0; if (typeof v === 'number') return v < 100000000000 ? v * 1000 : v; if (typeof v === 'string' && /^\d+(\.\d+)?$/.test(v.trim())) { const n = Number(v.trim()); return n < 100000000000 ? n * 1000 : n; } const t = new Date(v).getTime(); return Number.isFinite(t) ? t : 0; }; const formatDays = (days, cap = null) => { if (!Number.isFinite(days)) return L.trash_remaining_unknown || "-"; const capped = cap ? Math.min(cap, days) : days; const left = Math.max(0, Math.ceil(capped)); if (left <= 0) return L.trash_remaining_today || (`0 ${L.unit_days}`); return left + " " + L.unit_days; }; if (source && typeof source === 'object') { const directDays = toNumber(readValue(source, directKeys)); if (directDays !== null) return formatDays(directDays); const expireTime = toTime(readValue(source, expireKeys)); if (expireTime && expireTime > now - DAY_MS) return formatDays((expireTime - now) / DAY_MS); const retentionDays = toNumber(readValue(source, retentionKeys)) || 15; const deleteTime = toTime(readValue(source, deletedKeys)); if (deleteTime) { const daysPassed = (now - deleteTime) / DAY_MS; return formatDays(retentionDays - daysPassed, retentionDays); } return L.trash_remaining_unknown || "-"; } const deleteTime = toTime(source); if (!deleteTime) return L.trash_remaining_unknown || "-"; const daysPassed = (now - deleteTime) / DAY_MS; return formatDays(15 - daysPassed, 15); }; const getDupStablePathKey = () => { if (!dupGridMeta) return ''; if (S.dupMode) return S.pinnedDupPath || (UI.selDupFolder ? UI.selDupFolder.value || '' : ''); if (S.analyzeMode && cur && cur.id === 'analyze_root' && S.analyzeSimGroups) return 'analyze_root'; return ''; }; const getDupStableFilterSig = () => { const includePath = UI.chkSearchPath && UI.chkSearchPath.checked ? 'path1' : 'path0'; if (S.dupMode) { const cfg = S.dupConfig || {}; const invertChk = document.getElementById('pk-dup-invert'); return [ UI.chkHash && UI.chkHash.checked ? 'hash1' : 'hash0', UI.chkSim && UI.chkSim.checked ? 'sim1' : 'sim0', UI.chkName && UI.chkName.checked ? 'name1' : 'name0', cfg.video ? 'video1' : 'video0', cfg.image ? 'image1' : 'image0', cfg.other ? 'other1' : 'other0', gmGet('pk_dup_strictness', 'strict'), S.pinnedDupPath || '', invertChk && invertChk.checked ? 'inv1' : 'inv0', includePath ].join(':'); } if (S.analyzeMode && S.analyzeSimGroups) { return [ gmGet('pk_analyze_last_algo', ''), gmGet('pk_analyze_last_sim', ''), S.groupSortOp || '', S.groupSortDir || '', includePath ].join(':'); } return includePath; }; const resolveGridStableScope = () => { if (!isGridMode || !gridLayout) return ''; if (dupGridMeta) { if (S.dupMode) return 'dup-file'; if (S.analyzeMode && cur && cur.id === 'analyze_root' && S.analyzeSimGroups) return 'dup-folder'; return ''; } if (S.trashMode || S.shareMode || S.offlineMode || S.uploadMode || S.dupMode) return ''; if (S.historyMode) return cur && cur.id === 'history_root' ? 'history' : ''; if (S.recentMode) return cur && cur.id === 'recent_root' ? 'recent' : ''; if (S.starredMode) { const curId = cur && cur.id ? String(cur.id) : ''; const hasVirtualPath = S.path.some(p => { const id = p && p.id ? String(p.id) : ''; return id.startsWith('virtual_') || id === 'analyze_root' || id === 'recent_root' || id === 'history_root' || id === 'offline_root' || id === 'upload_root'; }); if (hasVirtualPath || S.isFlattened || S.analyzeMode) return ''; const isStarredRoot = S.path.length === 1 && (curId === '' || curId === 'starred_root'); const isStarredRealPath = S.path.length > 1 && curId && !curId.startsWith('virtual_') && curId !== 'analyze_root'; return (isStarredRoot || isStarredRealPath) ? 'starred' : ''; } if (S.isFlattened && !S.analyzeMode) return 'flat'; if (S.analyzeMode) return S.analyzeSimGroups ? '' : 'analyze'; const curId = cur && cur.id ? String(cur.id) : ''; return curId && curId.startsWith('virtual_') ? '' : 'normal'; }; const gridStableScope = resolveGridStableScope(); const dupStableMapParts = dupGridMeta ? [ dupGridMeta.key || '', getDupStablePathKey(), getDupStableFilterSig(), S.groupSortOp || '', S.groupSortDir || '' ] : []; const gridStableMapKey = gridStableScope ? [ gridStableScope, S.viewMode || '', getGridLayoutKey(), S.path.map(p => `${p.id || ''}:${p.name || ''}`).join('/'), rootPathStr, lang, isMax ? 'max' : 'normal', S.search || '', shouldShowHl ? 'hl' : 'plain', showPathCol ? 'path' : 'nop', isBlur ? 'blur' : 'clear', typeof getBlurScope === 'function' ? getBlurScope() : '', S.sort || '', S.dir || '', S.folderFirst ? 'ff' : 'nf', UI.chkSearchPath && UI.chkSearchPath.checked ? 'searchPath' : 'searchName', ...dupStableMapParts ].join('|') : ''; const useGridStableRows = !!gridStableMapKey; if (useGridStableRows) { if (!UI.in._pkGridRowMap) UI.in._pkGridRowMap = new Map(); if (UI.in._pkGridRowMapKey !== gridStableMapKey) { UI.in._pkGridRowMap.clear(); UI.in._pkGridRowMapKey = gridStableMapKey; } UI.in._pkGridStableRowMode = true; rowPool.forEach(row => { if (row) row._pkGridUsedThisPass = false; }); } else { clearGridStableRows(UI.in); } const gridPassUsedIds = useGridStableRows ? new Set() : null; const gridPassUsedRows = useGridStableRows ? new Set() : null; const takeGridFallbackRow = () => { let row = rowPool.find(r => r && !r._pkGridUsedThisPass && !gridPassUsedRows.has(r) && r.parentNode !== UI.in) || rowPool.find(r => r && !r._pkGridUsedThisPass && !gridPassUsedRows.has(r)); if (!row) { row = document.createElement('div'); row._pkPooledRow = true; row.style.position = 'absolute'; rowPool.push(row); } row._pkGridUsedThisPass = true; gridPassUsedRows.add(row); poolPtr++; return row; }; const takeGridStableRow = (itemId) => { if (!itemId || gridPassUsedIds.has(itemId)) { return { row: takeGridFallbackRow(), duplicate: true }; } gridPassUsedIds.add(itemId); const rowMap = UI.in._pkGridRowMap; let row = rowMap.get(itemId) || null; const isReservedGridRow = (candidate) => { if (!candidate || !candidate.dataset) return false; const boundId = candidate.dataset.pkBoundId || ''; return !!(boundId && boundId !== itemId && rowMap.get(boundId) === candidate); }; if (row && (row._pkGridUsedThisPass || gridPassUsedRows.has(row) || !row._pkPooledRow)) { row = null; } if (!row) { row = rowPool.find(r => r && !r._pkGridUsedThisPass && !gridPassUsedRows.has(r) && (r.dataset.pkBoundId || '') === itemId) || rowPool.find(r => r && !r._pkGridUsedThisPass && !gridPassUsedRows.has(r) && r.parentNode !== UI.in && !isReservedGridRow(r)) || rowPool.find(r => r && !r._pkGridUsedThisPass && !gridPassUsedRows.has(r) && !isReservedGridRow(r)); } if (!row) { row = document.createElement('div'); row._pkPooledRow = true; row.style.position = 'absolute'; rowPool.push(row); } const oldId = row.dataset.pkBoundId || ''; if (oldId && oldId !== itemId && rowMap.get(oldId) === row) { rowMap.delete(oldId); } rowMap.set(itemId, row); row._pkGridUsedThisPass = true; gridPassUsedRows.add(row); poolPtr++; return { row, duplicate: false }; }; const getGridFolderCountSig = (item) => { if (!item || item.kind !== 'drive#folder' || S.trashMode) return ''; let count = item.file_count; if (count === undefined || count === null) count = item.usage?.file_count; if (count === undefined || count === null) count = item.params?.file_count; if (count === undefined || count === null) count = item.audit?.file_count; if ((count === undefined || count === null) && !item._isShareItem && typeof globalCache !== 'undefined') { const cachedData = globalCache.get(item.id); if (cachedData) { const list = Array.isArray(cachedData) ? cachedData : (cachedData.items || []); count = list.length; } } return count === undefined || count === null ? '' : String(count); }; const getGridPathSig = (item) => { if (!item) return ''; const lineage = item._lineage; if (Array.isArray(lineage)) return lineage.map(p => `${p.id || ''}:${p.name || ''}`).join('/'); if (lineage === null) return 'null'; return item._pathStr || item._dupFullPath || item.path || ''; }; const getGridRecentMetaSig = (item) => { if (!S.recentMode || !window.pkRecentMetaCache || !window.pkRecentMetaCache.has(item.id)) return ''; const meta = window.pkRecentMetaCache.get(item.id) || {}; return [ meta.thumbnail_link || '', meta.icon_link || '', meta.mime_type || '', meta.modified_time || '', meta.size || '', meta.params?.duration || '' ].join(':'); }; const getGridThumbSig = (item) => { const hasThumb = !!(item.thumbnail_link && item.thumbnail_link !== item.icon_link); if (!hasThumb) return 'none'; const stableMedia = item.kind === 'drive#folder' ? getStableFolderMediaState(item) : getStableFileMediaState(item); const state = stableMedia.state || 'unknown'; if (state === 'unknown') return ''; if (state === 'probing') return [state, stableMedia.src || item.thumbnail_link || ''].join(':'); return [state, stableMedia.src || ''].join(':'); }; const buildGridStableSig = (item, dupStableCtx = null) => { if (!useGridStableRows || !item || item.isHeader || !item.id || !item.kind) return ''; const isDupStableScope = gridStableScope === 'dup-file' || gridStableScope === 'dup-folder'; const dupSigSuffix = isDupStableScope ? ((dupStableCtx && dupStableCtx.sigSuffix) || '') : ''; if (isDupStableScope && !dupSigSuffix) return ''; const thumbSig = getGridThumbSig(item); if (!thumbSig) return ''; const isFolder = item.kind === 'drive#folder'; const folderStamp = isFolder && typeof getFolderEmbedCacheStamp === 'function' ? getFolderEmbedCacheStamp(item) : ''; const cleanName = (item.name || "").replace(/[\r\n\v\f\u2028\u2029]+/g, ' ').trim().toLowerCase(); const isBlMarked = isFolder ? (S.blFolderSet && S.blFolderSet.has(cleanName)) : (S.blSet && S.blSet.has(cleanName)); const isStarred = S.starredSet.has(item.id) || !!(item.starred || (item.tags && item.tags.some(t => t.name === 'STAR'))); const isHistoryGridStable = gridStableScope === 'history'; const isVideo = !isFolder && (((item.mime_type || '').toLowerCase().startsWith('video/')) || (item.params && item.params.duration > 0) || (isHistoryGridStable && item._history_duration > 0)); const displayDur = isHistoryGridStable ? (item._history_duration || item.params?.duration || 0) : (isVideo ? (item._history_duration || item.params?.duration || 0) : 0); return [ gridStableMapKey, item.id || '', item.kind || '', item.name || '', item.mime_type || '', item.size || '', item.modified_time || '', item.created_time || '', item.thumbnail_link || '', item.icon_link || '', S.shareMode ? (item.pass_code || '') : '', typeof makeGridMediaCacheKey === 'function' ? makeGridMediaCacheKey(item) : '', thumbSig, folderStamp, getGridFolderCountSig(item), displayDur, isStarred ? 'star' : 'unstar', isBlMarked ? 'bl' : 'nobl', isSystemItem(item) ? 'sys' : 'usr', item._coverResolved ? 'cover' : '', item._isFolderLike ? 'folderlike' : '', item._recent_event_id || '', item._recent_event_type || '', item._recent_event_time || '', isHistoryGridStable ? Math.round(Number(item._history_progress || 0)) : '', isHistoryGridStable ? Math.round(Number(item._history_duration || 0)) : '', isHistoryGridStable ? Number(item._history_ts || 0) : '', isHistoryGridStable ? (item._history_event_id || '') : '', shouldShowHl ? (S.search || '') : '', isDupStableScope ? '' : getGridRecentMetaSig(item), isDupStableScope ? '' : getGridPathSig(item), dupSigSuffix ].join('|'); }; const gridStableSigCache = useGridStableRows ? new Map() : null; const getGridStableSigCached = (item, dupStableCtx = null) => { if (!gridStableSigCache || !item || !item.id) return buildGridStableSig(item, dupStableCtx); const cacheKey = dupStableCtx && dupStableCtx.cacheKey ? `dup:${dupStableCtx.cacheKey}` : `base:${item.id}`; if (gridStableSigCache.has(cacheKey)) return gridStableSigCache.get(cacheKey); const sig = buildGridStableSig(item, dupStableCtx); gridStableSigCache.set(cacheKey, sig); return sig; }; const buildVisibleDupStableCtx = (item, layout, displayIndex) => { if (!dupGridMeta || !layout || layout.kind !== 'item' || !item || item.isHeader || !item.id || !item.kind) return null; if (gridStableScope === 'dup-file') { if (!S.dupMode || item.kind !== 'drive#file') return null; } else if (gridStableScope === 'dup-folder') { if (!(S.analyzeMode && S.analyzeSimGroups) || item.kind !== 'drive#folder') return null; } else { return null; } const section = dupGridMeta.indexToSection ? dupGridMeta.indexToSection.get(displayIndex) : null; const localIdx = Number(layout.localIndex); const itemId = String(item.id); if (!section || section.headerIndex === null || !section.dupGroupSig || !section.memberDigest || !Number.isFinite(localIdx) || localIdx < 0) return null; if (dupGridMeta.repeatedIds && dupGridMeta.repeatedIds.has(itemId)) return null; if (!Array.isArray(section.itemIds) || String(section.itemIds[localIdx] || '') !== itemId) return null; const header = S.display[section.headerIndex] || null; if (header && Array.isArray(header.groupIds) && header.groupIds.length) { let inHeader = false; for (let hIdx = 0; hIdx < header.groupIds.length; hIdx++) { if (String(header.groupIds[hIdx] || '') === itemId) { inHeader = true; break; } } if (!inHeader) return null; } const rawGroupReason = String(section.reason || (header && header.type) || ''); const itemReason = String(item._dupReason || item.dupReason || item.reason || (S.dupReasons && S.dupReasons.get(item.id)) || rawGroupReason || ''); const itemPath = String(item._dupFullPath || item._pathStr || item.path || ''); return { sigSuffix: [ section.dupGroupSig, `dupLocal:${localIdx}`, `dupReason:${getDupGridShortTextDigest(itemReason)}`, `dupSim:${String(item._sim ?? item.sim ?? item.similarity ?? '')}`, `dupContain:${String(item._contain ?? item.contain ?? item.containment ?? '')}`, `dupHit:${String(item._dupHitType || item._hitType || item.hitType || '')}`, `dupItemPath:${getDupGridShortTextDigest(itemPath)}`, `dupSameFolder:${item._isSameFolder ? '1' : '0'}` ].join('|'), cacheKey: `${itemId}|${section.key || ''}|${localIdx}|${section.memberDigest}` }; }; const renderIndices = dupGridMeta ? visibleIndices : null; const renderCount = dupGridMeta ? renderIndices.length : Math.max(0, end - start); const selectedCountForRender = isGridMode ? S.getSelectedCount() : -1; for (let seqIdx = 0; seqIdx < renderCount; seqIdx++) { const i = dupGridMeta ? renderIndices[seqIdx] : (start + seqIdx); const d = S.display[i]; if (!d) continue; const dupLayout = dupGridMeta ? dupGridMeta.indexLayout.get(i) : null; const dupStableCtx = dupGridMeta ? buildVisibleDupStableCtx(d, dupLayout, i) : null; const canTryGridStableRow = !!(useGridStableRows && d.id && !d.isHeader && (!dupGridMeta || dupStableCtx)); let row; let gridStableDuplicateId = false; if (canTryGridStableRow) { const gridStableTake = takeGridStableRow(d.id); row = gridStableTake.row; gridStableDuplicateId = gridStableTake.duplicate; } else if (useGridStableRows) { row = takeGridFallbackRow(); } else { row = useHistoryStableRows && d.id ? takeHistoryStableRow(d.id) : rowPool[poolPtr++]; if (useHistoryStableRows) poolPtr++; } const prevBoundId = row.dataset.pkBoundId || ''; const prevBoundKind = row.dataset.pkBoundKind || ''; const prevGridMediaMount = isGridMode ? row.querySelector('.pk-gv-media-mount') : null; const prevGridMediaNode = prevGridMediaMount ? prevGridMediaMount.firstElementChild : null; const prevGridMediaKey = prevGridMediaMount ? (prevGridMediaMount.dataset.pkMediaKey || '') : ''; const suppressGridRebindTransition = isGridMode && prevBoundId && prevBoundId !== d.id; const gridStableSig = (canTryGridStableRow && !gridStableDuplicateId) ? getGridStableSigCached(d, dupStableCtx) : ''; const listStableModeKey = useHistoryStableRows ? [ S.offlineMode ? 'offline' : '', S.uploadMode ? 'upload' : '', S.shareMode ? 'share' : '', S.historyMode ? 'history' : '', S.trashMode ? 'trash' : '', S.recentMode ? 'recent' : '', S.dupMode ? 'dup' : '', S.analyzeMode ? 'analyze' : '', S.isFlattened ? 'flat' : '', showPathCol ? 'path' : 'plain', colDef, rootPathStr ].join('|') : ''; const getListPathStableKey = (item) => { if (!item || item.isHeader) return 'header'; if (item._isShareInsightItem) return item._shareFilePathText || getSharePathText(item._sharePath) || L.label_share_parse_path_root || ''; if (isAnalyzeRoot) return item._pathStr || item.path || ''; if (S.dupMode) return item._dupFullPath || ''; if (item._lineage === null && item.parent_id && item.parent_id !== 'root') return `pending:${item.parent_id || ''}`; if (item._lineage && Array.isArray(item._lineage)) return item._lineage.map(x => x && x.name || '').join('/'); if (S.analyzeMode) return item._pathStr || item.path || ''; return ''; }; const listPathStableSig = (useHistoryStableRows && showPathCol) ? (() => { const curPathKey = getListPathStableKey(d); const prevItem = i > firstVisiblePathIndex ? S.display[i - 1] : null; const prevPathKey = (prevItem && !prevItem.isHeader) ? getListPathStableKey(prevItem) : ''; const sameState = (i > firstVisiblePathIndex && curPathKey && prevPathKey && curPathKey === prevPathKey) ? 'same' : 'show'; return [i, firstVisiblePathIndex, curPathKey, prevPathKey, sameState].join('~'); })() : ''; const historyStableSig = useHistoryStableRows ? [ listStableModeKey, listPathStableSig, d.id || '', d.name || '', d.size || '', d.mime_type || '', d.thumbnail_link || '', d.icon_link || '', S.shareMode ? (d.pass_code || '') : '', isGridMode ? 'grid' : 'list', UI.win && UI.win.classList.contains('pk-maximized') ? 'max' : 'normal', typeof getBlurScope === 'function' ? getBlurScope() : '', isBlur ? 'blur' : 'clear', (S.starredSet.has(d.id) || !!(d.starred || (d.tags && d.tags.some(t => t.name === 'STAR')))) ? 'star' : 'unstar', (d.kind === 'drive#folder' ? (S.blFolderSet && S.blFolderSet.has(getBlacklistCleanKey(d.name))) : (S.blSet && S.blSet.has(getBlacklistCleanKey(d.name)))) ? 'bl' : 'nobl', Math.round(Number(S.durationMap.get(d.id) || d.params?.duration || 0)), Math.round(Number(d._history_progress || 0)), Math.round(Number(d._history_duration || 0)), Number(d._history_ts || 0), d._history_event_id || '', shouldShowHl ? (S.search || '') : '' ].join('|') : ''; const canReuseGridRow = !!( useGridStableRows && gridStableSig && !gridStableDuplicateId && prevBoundId && prevBoundId === d.id && prevBoundKind === d.kind && row.dataset.pkGridStable === '1' && row.dataset.pkGridSig === gridStableSig && row.querySelector('.pk-grid-card-body') ); const canReuseHistoryRow = !!( useHistoryStableRows && prevBoundId && prevBoundId === d.id && row.dataset.pkHistoryStable === '1' && row.dataset.pkHistorySig === historyStableSig && row.firstElementChild ); if (isGridMode && !dupGridMeta && !canReuseGridRow && prevBoundId && prevGridMediaNode && typeof stashFrozenGridMediaNode === 'function') { stashFrozenGridMediaNode(prevBoundId, prevGridMediaKey, prevGridMediaNode, prevBoundKind); } if (canReuseHistoryRow || canReuseGridRow) { softResetPooledRow(row); } else { resetPooledRow(row); } row.dataset.pkHistoryStable = useHistoryStableRows ? '1' : '0'; row.dataset.pkHistorySig = historyStableSig; row.dataset.pkGridStable = (canTryGridStableRow && gridStableSig && !gridStableDuplicateId) ? '1' : '0'; row.dataset.pkGridSig = gridStableSig; if (suppressGridRebindTransition) { row.style.transition = 'none'; row.style.animation = 'none'; } if (dupLayout) { row.style.top = `${dupLayout.top}px`; row.style.left = `${dupLayout.left}px`; row.style.width = `${dupLayout.width}px`; row.style.height = `${dupLayout.height}px`; if (dupLayout.kind === 'header') { row.style.padding = isMax ? '0 20px' : '0 16px'; row.style.display = 'grid'; row.style.gridTemplateColumns = colDef; row.style.zIndex = '2'; row.style.boxSizing = 'border-box'; } else { row.style.padding = '0'; row.style.display = 'block'; row.style.gridTemplateColumns = ''; row.style.zIndex = '1'; } } else if (isGridMode && gridLayout) { const rowIdx = Math.floor(i / gridLayout.cols); const colIdx = i % gridLayout.cols; row.style.top = `${gridLayout.gapY + rowIdx * CONF.rowHeight}px`; row.style.left = `${gridLayout.padX + colIdx * (gridLayout.cardWidth + gridLayout.gapX)}px`; row.style.width = `${gridLayout.cardWidth}px`; row.style.height = `${gridLayout.cardHeight}px`; row.style.padding = '0'; row.style.display = 'block'; } else { row.style.top = `${i * CONF.rowHeight}px`; row.style.left = '0'; row.style.width = '100%'; row.style.gridTemplateColumns = colDef; row.style.height = ''; row.style.padding = ''; row.style.display = 'grid'; } if (d.isHeader) { row.className = 'pk-group-hd'; if (i === 0) { row.style.setProperty('border-top', 'none', 'important'); const borderW = isMax ? 10 : 4; row.style.setProperty('padding-top', borderW + 'px', 'important'); } else { row.style.removeProperty('border-top'); row.style.removeProperty('padding-top'); } if (dupGridMeta) row.style.gridColumn = ""; else if (S.trashMode) row.style.gridColumn = "1 / 7"; else if (S.dupMode || S.analyzeMode) row.style.gridColumn = "1 / 8"; else row.style.gridColumn = "1 / 7"; const gIdx = parseInt(d.id.replace('grp_', '')); const groupData = S.dupMode ? S.dupRawGroups[gIdx] : (S.analyzeMode ? S.analyzeSimGroups[gIdx] : null); const groupItemIds = Array.isArray(d.groupIds) ? d.groupIds.slice() : (groupData ? groupData.ids : []); row._pkGroupIds = groupItemIds.slice(); let selectedInGroup = 0; groupItemIds.forEach(id => { if (S.isSelected(id)) selectedInGroup++; }); const isAllSelected = groupItemIds.length > 0 && selectedInGroup === groupItemIds.length; const isIndeterminate = selectedInGroup > 0 && selectedInGroup < groupItemIds.length; let groupIcon = CONF.dupHashSVG; const isContain = d.name.includes(L.lbl_containment); const isSimLike = d.type === L.tag_sim || d.name.includes(L.lbl_sim_score); const isNameLike = d.type === L.tag_name || (S.analyzeMode && d.name.includes(' | ') && !isSimLike && !isContain); if (isContain) { groupIcon = CONF.dupContainSVG; } else if (isSimLike) { groupIcon = CONF.dupSimSVG; } else if (isNameLike) { groupIcon = CONF.dupNameSVG; } groupIcon = groupIcon.replace(/width:\s*\d+px;?/, 'width:18px;').replace(/height:\s*\d+px;?/, 'height:18px;').replace(/margin-right:\s*\d+px;?/, 'margin:0;'); let headerName = d.name; const headerTip = getTooltipHlHTML(d.name, S.search).replace(/"/g, '"'); row.style.display = 'flex'; row.innerHTML = `
${groupIcon}
${esc(headerName)}
`; const grpChk = row.querySelector('.pk-grp-chk'); if (grpChk) { grpChk.indeterminate = isIndeterminate; grpChk.onclick = (e) => { e.stopPropagation(); const targetState = grpChk.checked; groupItemIds.forEach(id => { S.setSelected(id, targetState); }); renderVisible(); updateStat(); }; } } else { const isSel = S.isSelected(d.id); const isFocused = S.activeId === d.id; const isMoving = S.movingIds && S.movingIds.has(d.id); row.className = getRowClassName(isSel, isFocused, isMoving, selectedCountForRender); row.ondragstart = (e) => e.preventDefault(); if (isFocused && !isSel) { row.style.backgroundColor = 'var(--pk-sel-bg)'; row.style.border = '1px solid var(--pk-pri)'; row.style.borderRadius = isGridMode ? `${getGridCardRadius()}px` : '4px'; } else { row.style.backgroundColor = ''; row.style.border = ''; row.style.borderRadius = ''; } if (isMoving) { row.style.opacity = '0.4'; row.style.filter = 'grayscale(100%)'; row.style.pointerEvents = 'none'; row.style.cursor = 'wait'; } else { row.style.opacity = ''; row.style.filter = ''; row.style.pointerEvents = ''; row.style.cursor = ''; } row.dataset.id = d.id; const isProtected = !S.trashMode && isSystemItem(d); const isMax = UI.win.classList.contains('pk-maximized'); const nameTip = getTooltipHlHTML(d.name, S.search).replace(/"/g, '"'); const getDynamicIcon = (item) => { let isBlacklisted = false; const cleanName = (item.name || "").replace(/[\r\n\v\f\u2028\u2029]+/g, ' ').trim().toLowerCase(); if (item.kind === 'drive#folder') { isBlacklisted = S.blFolderSet && S.blFolderSet.has(cleanName); } else { isBlacklisted = S.blSet && S.blSet.has(cleanName); } const blHtml = isBlacklisted ? `
${CONF.icons.blMarker}
` : ''; if (S.uploadMode) { if (item.status === 'DONE' && item.file && item.mime_type && item.mime_type.startsWith('image/')) { if (!item._localThumbUrl) { try { item._localThumbUrl = URL.createObjectURL(item.file); } catch(e) {} } if (item._localThumbUrl) item.thumbnail_link = item._localThumbUrl; } const hasReadyThumb = item.status === 'DONE' && item.thumbnail_link && item.thumbnail_link !== item.icon_link; if (!hasReadyThumb) { const scriptIcon = getIcon(item); if (isMax) { const boxStyle = "width:54px; min-width:54px; height:100%; display:flex; align-items:center; justify-content:flex-start !important; margin-right:12px; position:relative;"; return `
${scriptIcon}
${blHtml}
`; } return `
${scriptIcon}
${blHtml}
`; } } if (!window.pkGlobalThumbCache) window.pkGlobalThumbCache = new Set(); const isFolder = item.kind === 'drive#folder'; const isTask = item.kind === 'drive#task'; const isUploadTask = item.kind === 'pk#upload'; const lookupId = (isTask || isUploadTask) ? item.file_id : item.id; let hasValidCover = !!(item.thumbnail_link && item.thumbnail_link !== item.icon_link); if (!window.pkRecentMetaCache) window.pkRecentMetaCache = new Map(); if (S.recentMode && !isFolder) { if (window.pkRecentMetaCache.has(item.id)) { const meta = window.pkRecentMetaCache.get(item.id); item.thumbnail_link = meta.thumbnail_link || item.thumbnail_link; item.icon_link = meta.icon_link || item.icon_link; item.mime_type = meta.mime_type || item.mime_type; if (meta.medias) item.medias = meta.medias; item.params = Object.assign(item.params || {}, meta.params || {}); hasValidCover = !!(item.thumbnail_link && item.thumbnail_link !== item.icon_link); } else if (!hasValidCover && !item._metaFetching) { item._metaFetching = true; const ext = (item.name || '').split('.').pop().toLowerCase(); const mime = (item.mime_type || '').toLowerCase(); const isLikelyMedia = mime.startsWith('video/') || mime.startsWith('image/') ||['mp4','mkv','avi','mov','wmv','flv','webm','ts','m4v','3gp','jpg','jpeg','png','gif','bmp','webp','heic','svg','tif','tiff','ico'].includes(ext); if (isLikelyMedia) { apiGet(item.id).then(meta => { if (meta) { window.pkRecentMetaCache.set(item.id, meta); item.thumbnail_link = meta.thumbnail_link || item.thumbnail_link; item.icon_link = meta.icon_link || item.icon_link; item.mime_type = meta.mime_type || item.mime_type; if (meta.medias) item.medias = meta.medias; item.params = Object.assign(item.params || {}, meta.params || {}); requestAnimationFrame(() => { if (typeof renderVisible === 'function') renderVisible(); }); } }).catch(()=>{}); } } } let forceDeepScan = false; if (item._coverResolved && !hasValidCover) { forceDeepScan = true; } if ((isFolder || isTask || isUploadTask) && lookupId && !item._isShareItem && (!hasValidCover || forceDeepScan) && typeof globalCache !== 'undefined') { const normalize = (data) => (data && !Array.isArray(data) && data.items) ? data.items : data; const scanDeepCover = (targetId, depth) => { if (depth > 5) return null; const raw = globalCache.get(targetId); if (!raw) return null; const files = normalize(raw); if (!files || files.length === 0) return null; const vid = files.find(f => f.mime_type?.startsWith('video/') && f.thumbnail_link); if (vid) return vid.thumbnail_link; const img = files.find(f => f.mime_type?.startsWith('image/') && f.thumbnail_link); if (img) return img.thumbnail_link; const subFolders = files.filter(f => f.kind === 'drive#folder'); for (const sub of subFolders) { if (globalCache.has(sub.id)) { const childThumb = scanDeepCover(sub.id, depth + 1); if (childThumb) return childThumb; continue; } if (sub.thumbnail_link && sub.thumbnail_link !== sub.icon_link && !sub._coverResolved) { return sub.thumbnail_link; } const childThumb = scanDeepCover(sub.id, depth + 1); if (childThumb) return childThumb; } return null; }; const foundThumb = scanDeepCover(lookupId, 0); if (foundThumb) { item.thumbnail_link = foundThumb; item._coverResolved = true; item._isFolderLike = true; hasValidCover = true; if (typeof window.markStableFolderMediaState === 'function') { window.markStableFolderMediaState(item.id || lookupId, 'ok', foundThumb); } else { if (!window.pkThumbTriState) window.pkThumbTriState = { folder: Object.create(null) }; if (!window.pkGridMediaStore) window.pkGridMediaStore = { folder: Object.create(null) }; if (!window.pkGlobalThumbCache) window.pkGlobalThumbCache = new Set(); window.pkThumbTriState.folder[item.id || lookupId] = 'ok'; window.pkGridMediaStore.folder[item.id || lookupId] = foundThumb; window.pkGlobalThumbCache.add(item.id || lookupId); } } else if (item._coverResolved || (isFolder && globalCache.has(lookupId))) { item.thumbnail_link = null; item._coverResolved = false; item._isFolderLike = false; hasValidCover = false; if (typeof window.markStableFolderMediaState === 'function') { window.markStableFolderMediaState(item.id || lookupId, 'fail'); } else if (window.pkThumbTriState && window.pkThumbTriState.folder) { window.pkThumbTriState.folder[item.id || lookupId] = 'fail'; if (window.pkGridMediaStore && window.pkGridMediaStore.folder) delete window.pkGridMediaStore.folder[item.id || lookupId]; } } else if ((isFolder || isTask || isUploadTask) && typeof scannedFolderIds !== 'undefined' && !scannedFolderIds.has(lookupId)) { if (!S.loading && !S.scanning) { scannedFolderIds.add(lookupId); backgroundQueue.push({ id: lookupId, name: 'DeepCoverProbe', retryCount: 0 }); if (typeof runBackgroundCrawler === 'function') runBackgroundCrawler(); } } } const iconHtml = getIcon(item); const boxStyle = "width:54px; min-width:54px; height:100%; display:flex; align-items:center; justify-content:flex-start !important; margin-right:12px; position:relative;"; const placeholderStyle = `position: absolute; left: 0; top: 50%; transform: translateY(-50%); z-index: 1; width: 100%; display: flex; align-items: center; transition: opacity 0.3s; pointer-events: none;`; const imgStyle = "width: 48px; height: 48px; object-fit: cover; border-radius: 4px; margin: 0 !important; background: transparent; position: relative; z-index: 2; transition: opacity 0.3s;"; const isDarkTheme = document.body.classList.contains('dark') || document.documentElement.getAttribute('data-theme') === 'dark'; const badgeBg = isDarkTheme ? '#303134' : '#FFFFFF'; const badgeBorder = isDarkTheme ? '1px solid rgba(255,255,255,0.1)' : '1px solid rgba(0,0,0,0.08)'; const badgeShadow = isDarkTheme ? '0 2px 6px rgba(0,0,0,0.4)' : '0 2px 6px rgba(0,0,0,0.15)'; const badgeStyle = `position: absolute; bottom: -5px; right: -5px; z-index: 3; width: 24px; height: 24px; border-radius: 50%; background-color: ${badgeBg}; border: ${badgeBorder}; box-shadow: ${badgeShadow}; box-sizing: border-box; transition: opacity 0.3s; pointer-events: none; display: grid; place-items: center; line-height: 0;`; const isBlur = isBlurEnabledForView(isGridView() ? 'grid' : 'list'); if (isMax && isBlur) { const fallbackSvg = iconHtml.replace(/"/g, """).replace(/\n/g, ""); return `
${blHtml}
`; } if (isMax && item.thumbnail_link && !item.isHeader && item.thumbnail_link !== item.icon_link) { const isCached = window.pkGlobalThumbCache.has(item.id); const phOp = isCached ? '0' : '1'; const imgOp = isCached ? '1' : '0'; const isFolderLike = isFolder || item._isFolderLike || (item.icon_link && item.icon_link.includes('folder')); const badgeOp = (isFolderLike && isCached) ? '1' : '0'; let badgeHtml = ''; if (isFolderLike) { const innerContent = ``; badgeHtml = `
${innerContent}
`; } const isVideo = !isFolder && (typeof isGridVideoItem === 'function' ? isGridVideoItem(item) : (item.mime_type && item.mime_type.startsWith('video/'))); let videoOvHtml = ''; if (isVideo) { const vOp = isCached ? '1' : '0'; videoOvHtml = `
`; } return `
${badgeHtml} ${videoOvHtml} ${blHtml}
`; } if (isMax) { if (hasValidCover) { const showBadge = isFolder || item._isFolderLike || (item.icon_link && item.icon_link.includes('folder')); let badgeHtml = ''; if (showBadge) { const innerIcon = ``; badgeHtml = `
${innerIcon}
`; } return `
${badgeHtml} ${blHtml}
`; } const finalIconSrc = item.icon_link || item.thumbnail_link; if (finalIconSrc) { return `
'">${blHtml}
`; } return `
${iconHtml}
${blHtml}
`; } const mime = (item.mime_type || '').toLowerCase(); const isMedia = item.kind !== 'drive#folder' && (mime.startsWith('image/') || mime.startsWith('video/') || (item.params && item.params.duration > 0)); const hasRealThumb = isMedia && item.thumbnail_link && item.thumbnail_link !== item.icon_link; if (hasRealThumb) { if (!window.pkGlobalThumbCache) window.pkGlobalThumbCache = new Set(); const isCached = window.pkGlobalThumbCache.has(item.id); const phOp = isCached ? '0' : '1'; const imgOp = isCached ? '1' : '0'; const placeholder = item.icon_link ? `` : iconHtml; return `
${placeholder}
${blHtml}
`; } if (item.icon_link) { return `
${iconHtml}${blHtml}
`; } return `
${iconHtml}${blHtml}
`; }; const checkboxHtml = ``; let html = `
${checkboxHtml}
`; if (isGridMode && gridLayout) { getDynamicIcon(d); const isFolder = d.kind === 'drive#folder'; const isHistoryGrid = S.historyMode; const displayDate = S.trashMode ? getRemainingDays(d) : fmtDate(d.modified_time); const mimeLower = (d.mime_type || '').toLowerCase(); const knownDisplayDur = S.durationMap.get(d.id) || d._history_duration || d.params?.duration || 0; const isTimedMedia = !isFolder && (mimeLower.startsWith('video/') || mimeLower.startsWith('audio/') || knownDisplayDur > 0 || (isHistoryGrid && d._history_duration > 0)); const isVideo = !isFolder && (mimeLower.startsWith('video/') || (isHistoryGrid && d._history_duration > 0)); const displayDur = isHistoryGrid ? knownDisplayDur : (isTimedMedia ? knownDisplayDur : 0); const historyCurT = Math.max(0, Math.round(Number(d._history_progress || 0))); const historyTotalT = Math.max(0, Math.round(Number(displayDur || 0))); const historyPctRaw = historyTotalT > 0 ? Math.min(100, Math.max(0, (historyCurT / historyTotalT) * 100)) : 0; const historyPctText = historyTotalT > 0 ? `${Math.round(historyPctRaw)}%` : '-'; let historyPlayTimeStr = '-'; if (d._history_ts > 0) { historyPlayTimeStr = fmtDate(new Date(d._history_ts).toISOString()); } const historyDurText = displayDur > 0 ? fmtDur(displayDur) : '-'; let folderItemCount = null; if (isFolder && !S.trashMode) { let count = d.file_count; if (count === undefined || count === null) count = d.usage?.file_count; if (count === undefined || count === null) count = d.params?.file_count; if (count === undefined || count === null) count = d.audit?.file_count; if ((count === undefined || count === null) && !d._isShareItem && typeof globalCache !== 'undefined') { const cachedData = globalCache.get(d.id); if (cachedData) { const list = Array.isArray(cachedData) ? cachedData : (cachedData.items || []); count = list.length; } } if (count !== undefined && count !== null && count !== '') { const parsed = parseInt(count, 10); if (!Number.isNaN(parsed)) folderItemCount = Math.max(0, parsed); } } const displayFolderCount = folderItemCount === null ? '' : L.grid_folder_count.replace('{n}', folderItemCount); const hasThumb = !!(d.thumbnail_link && d.thumbnail_link !== d.icon_link); const isStarred = S.starredSet.has(d.id) || !!(d.starred || (d.tags && d.tags.some(t => t.name === 'STAR'))); const gridNameCapacity = Math.max(12, Math.min(26, Math.floor((gridLayout.cardWidth - (isProtected ? 78 : 18)) / (isMax ? 13 : 12)))); const nameDisplay = (S.search && shouldShowHl) ? getGridSearchHlHTML(d.name, S.search, gridNameCapacity) : esc(d.name); const coverCls = `pk-gv-cover ${isFolder ? 'pk-gv-folder' : 'pk-gv-file'}${hasThumb ? ' pk-gv-has-thumb' : ''}${isBlur && hasThumb ? ' pk-gv-blur' : ''}`; const iconFallback = getIcon(d).replace(/width="\d+"/g, 'width="108"').replace(/height="\d+"/g, 'height="108"'); const menuIcon = ``; ensureGridMediaStore(); const gridFileFallbackHtml = `
${iconFallback}
`.replace(/"/g, '"').replace(/\n/g, ''); const stableFolderMedia = hasThumb ? getStableFolderMediaState(d) : { state: 'none', src: '' }; let folderThumbState = stableFolderMedia.state; let folderResolvedSrc = stableFolderMedia.src || d.thumbnail_link || ''; if (hasThumb && d.kind === 'drive#folder' && folderResolvedSrc && d._coverResolved && folderThumbState !== 'ok') { if (typeof window.markStableFolderMediaState === 'function') { window.markStableFolderMediaState(d.id, 'ok', folderResolvedSrc); } else { if (!window.pkThumbTriState) window.pkThumbTriState = { folder: Object.create(null) }; if (!window.pkGridMediaStore) window.pkGridMediaStore = { folder: Object.create(null) }; if (!window.pkGlobalThumbCache) window.pkGlobalThumbCache = new Set(); window.pkThumbTriState.folder[d.id] = 'ok'; window.pkGridMediaStore.folder[d.id] = folderResolvedSrc; window.pkGlobalThumbCache.add(d.id); } folderThumbState = 'ok'; } const folderHasCoverThumb = hasThumb && folderThumbState === 'ok' && !!folderResolvedSrc; if (hasThumb && folderThumbState === 'unknown' && !folderResolvedSrc) { window.pkThumbTriState.folder[d.id] = 'probing'; } const gridIconFallbackHtml = iconFallback.replace(/"/g, '"').replace(/\n/g, ''); const showFolderAnalyzeSize = isFolder && S.analyzeMode && !S.analyzeSimGroups && d.size !== undefined && d.size !== null && d.size !== ''; const showFolderDedupeSize = isFolder && S.analyzeMode && !!S.analyzeSimGroups && d.size !== undefined && d.size !== null && d.size !== ''; const folderSizeHtml = (showFolderAnalyzeSize || showFolderDedupeSize) ? `${fmtSize(d.size)}` : ''; const metaHtml = isHistoryGrid ? `
${historyPctText}${historyPlayTimeStr}${historyDurText}
` : isFolder ? `
${folderSizeHtml}${displayDate}${displayFolderCount ? `${displayFolderCount}` : ''}
` : `
${fmtSize(d.size)}${displayDate}${displayDur > 0 ? `${fmtDur(displayDur)}` : ''}
`; const cleanName = (d.name || "").replace(/[\r\n\v\f\u2028\u2029]+/g, ' ').trim().toLowerCase(); const isBlMarked = isFolder ? (S.blFolderSet && S.blFolderSet.has(cleanName)) : (S.blSet && S.blSet.has(cleanName)); const gridBlHtml = isBlMarked ? `${CONF.icons.blMarker}` : ''; const videoPlayDisplay = 'none'; html = `
${isVideo && hasThumb ? `
` : ''} ${(!S.shareParseMode && isStarred) ? `${_internalGetStarIcon(true).replace('pk-star-toggle', 'pk-star-icon')}` : ''} ${gridBlHtml} ${isHistoryGrid ? `
` : ''}
${nameDisplay} ${isProtected ? `${L.tag_default}` : ''}
${metaHtml}
`; } else if (S.historyMode) { const isStarred = S.starredSet.has(d.id) || !!(d.starred || (d.tags && d.tags.some(t => t.name === 'STAR'))); const starColor = isStarred ? '#FFC107' : '#ccc'; const starFill = isStarred ? '#FFC107' : 'none'; html += `
`; let iconImg = getDynamicIcon(d); if (isMax) { if (iconImg.includes('margin-right:12px')) iconImg = iconImg.replace('margin-right:12px', 'margin-right:20px'); else if (!iconImg.includes('margin-right')) iconImg = iconImg.replace(/style=['"]/, '$&margin-right:20px; '); } const nameDisplay = (S.search && shouldShowHl) ? getSearchHlHTML(d.name, S.search, charCapacity) : esc(d.name); html += `
${iconImg} ${nameDisplay}
`; html += `
${fmtSize(d.size)}
`; const displayDur = d._history_duration || d.params?.duration || 0; html += `
${displayDur > 0 ? fmtDur(displayDur) : '-'}
`; let progressHtml = '-'; const curT = Math.max(0, Math.round(Number(d._history_progress || 0))); const totalT = Math.max(0, Math.round(Number(displayDur || 0))); if (totalT > 0) { const pctRaw = Math.min(100, Math.max(0, (curT / totalT) * 100)); const pctText = Math.round(pctRaw); progressHtml = `
${fmtDur(curT)} ${pctText}%
`; } html += `
${progressHtml}
`; let playTimeStr = '-'; if (d._history_ts > 0) { playTimeStr = fmtDate(new Date(d._history_ts).toISOString()); } else { playTimeStr = "-"; } html += `
${playTimeStr}
`; } else if (S.offlineMode) { let iconImg = getDynamicIcon(d); if (isMax) { if (iconImg.includes('margin-right:12px')) { iconImg = iconImg.replace('margin-right:12px', 'margin-right:20px'); } else if (!iconImg.includes('margin-right')) { iconImg = iconImg.replace(/style=['"]/, '$&margin-right:20px; '); } } const isFailed = d.phase === 'PHASE_TYPE_ERROR'; const isNavigable = !!d.file_id && !isFailed; const isTaskDone = d.phase === 'PHASE_TYPE_COMPLETE'; const nameStyle = isNavigable ? 'cursor:pointer; opacity:1;' : 'cursor:default; pointer-events:none; color:inherit; opacity:0.6;'; const isMedia = d.mime_type && (d.mime_type.startsWith('video/') || d.mime_type.startsWith('image/')); const hasResolvedCover = d._coverResolved && d.thumbnail_link; const thumbAttr = (isTaskDone && (isMedia || hasResolvedCover)) ? `data-pk-thumb="${d.thumbnail_link}"` : ''; const nameDisplay = S.search ? getSearchHlHTML(d.name, S.search, charCapacity) : esc(d.name); html += `
${iconImg} ${nameDisplay}
`; html += `
${fmtSize(d.size)}
`; let statusColor = 'var(--pk-fg)'; let statusText = d.phase; if (d.phase === 'PHASE_TYPE_COMPLETE') { statusColor = '#52c41a'; statusText = L.lbl_up_done; } else if (d.phase === 'PHASE_TYPE_RUNNING') { statusColor = '#1890ff'; statusText = L.lbl_up_downloading; } else if (d.phase === 'PHASE_TYPE_ERROR') { statusColor = '#ff4d4f'; statusText = L.str_failed; } else if (d.phase === 'PHASE_TYPE_PENDING') { statusText = L.msg_task_waiting; } else if (d.phase === 'PHASE_TYPE_PAUSED') { statusText = L.msg_task_paused; } if (d.phase === 'PHASE_TYPE_ERROR' && d.message) { statusText = d.message; } html += `
${statusText}
`; let progressHtml = ''; if (d.phase === 'PHASE_TYPE_COMPLETE') { progressHtml = `
100%
`; } else if (d.phase === 'PHASE_TYPE_ERROR') { progressHtml = `
-
`; } else { const pct = d.progress || 0; progressHtml = `
${pct}%
`; } html += `
${progressHtml}
`; } else if (S.uploadMode) { if (!d.mime_type && d.file) d.mime_type = d.file.type; const nameDisplay = shouldShowHl ? getSearchHlHTML(d.name, S.search, charCapacity) : esc(d.name); const isDone = d.status === 'DONE'; const nameStyle = isDone ? '' : 'cursor:default; pointer-events:none; color:inherit;'; if (isDone && d.file && d.mime_type && d.mime_type.startsWith('image/')) { if (!d._localThumbUrl) { try { d._localThumbUrl = URL.createObjectURL(d.file); } catch(e) {} } if (d._localThumbUrl) d.thumbnail_link = d._localThumbUrl; } const hasResolvedCover = isDone && d.thumbnail_link && d.thumbnail_link !== d.icon_link; const thumbAttr = hasResolvedCover ? `data-pk-thumb="${d.thumbnail_link}"` : ''; html += `
${getDynamicIcon(d)} ${nameDisplay}
`; html += `
${fmtSize(d.size)}
`; html += `
${(d.status === 'UPLOADING' && S.upMng) ? S.upMng.fmtSpeed(d.speed) : '-'}
`; let statusColor = '#888'; const activeStatus = ['UPLOADING', 'HASHING', 'WAITING', 'RUNNING']; if (activeStatus.includes(d.status)) statusColor = 'var(--pk-pri)'; else if (d.status === 'DONE') statusColor = '#52c41a'; else if (d.status === 'ERROR') statusColor = '#d93025'; else if (d.status === 'PAUSED') statusColor = '#faad14'; html += `
${esc(d.message)} ${Math.floor(d.progress)}%
`; } else if (S.shareMode) { const iconUrl = d.icon_link; let iconImg = ''; const isShareDisabled = (d.share_status === 'DELETED') || (d.limit_count > 0 && d.save_count >= d.limit_count); const lockHtml = d.pass_code ? `
${CONF.icons.lock}
` : ''; if (isShareDisabled) { iconImg = `
${CONF.typeIcons.file}
`; } else { const currentIcon = getDynamicIcon(d); const isUsingThumb = currentIcon.includes('pk-max-thumb'); if (isUsingThumb) { iconImg = `
${currentIcon}${lockHtml}
`; } else if (iconUrl) { iconImg = `
${lockHtml}
`; } else { const isFolder = d.kind === 'drive#folder'; iconImg = `
${getIcon(d)}
${lockHtml}
`; } } const nameDisplay = shouldShowHl ? getSearchHlHTML(d.name, S.search, charCapacity) : esc(d.name); html += `
${iconImg} ${nameDisplay}
`; html += `
${d.view_count || 0}
`; const saveVal = d.limit_count > 0 ? `${d.save_count || 0}/${d.limit_count}` : (d.save_count || 0); const saveTip = d.limit_count > 0 ? `${L.lbl_limit_tip}: ${d.limit_count}` : ''; const saveClass = d.limit_count > 0 ? "pk-force-tip" : ""; html += `
${saveVal}
`; let statusColor = 'inherit'; let statusText = ''; const timeLeft = d.expiration_left || ""; if (d.share_status !== 'OK') { statusColor = '#ff4d4f'; statusText = (d.save_count >= d.limit_count && d.limit_count > 0) ? (L.lbl_limit_reached) : (d.share_status_text || d.share_status); } else if (d.expiration_days === "-1" || timeLeft === "-1") { statusColor = '#52c41a'; statusText = L.share_perm; } else { statusText = timeLeft + (L.str_expire_suffix); const isUrgent = timeLeft.includes('小时') || timeLeft.includes('hour') || timeLeft.includes('时间') || timeLeft.includes('时间'); statusColor = isUrgent ? '#ff4d4f' : 'inherit'; } html += `
${statusText}
`; const shareDate = fmtDate(d.modified_time); html += `
${shareDate}
`; } else { const isRoot = S.path.length === 1 && S.path[0].id === ''; const isStarred = S.starredSet.has(d.id) || !!(d.starred || (d.tags && d.tags.some(t => t.name === 'STAR'))); const starColor = isStarred ? '#FFC107' : '#ccc'; const starFill = isStarred ? '#FFC107' : 'none'; let displayPath = rootPathStr; let shortPath = ""; if (S.trashMode) { if (d._lineage && Array.isArray(d._lineage)) { shortPath = d._lineage.map(p => p.name).join('/'); displayPath = shortPath; } } else if (d._lineage && Array.isArray(d._lineage)) { const relativePath = d._lineage.map(p => p.name).join('/'); shortPath = relativePath; if (isGlobalSearchRoot) { displayPath = L.btn_nav_home + (relativePath ? '/' + relativePath : ''); } else if (S.analyzeMode) { displayPath = relativePath || L.btn_nav_home; } else if (relativePath) { displayPath = rootPathStr + '/' + relativePath; } else { displayPath = rootPathStr; } } else if (isGlobalSearchRoot) { displayPath = L.btn_nav_home; shortPath = ""; } if (!S.shareParseMode) { if (S.trashMode) { html += `
`; } else { if (isProtected) html += `
`; else html += `
`; } } const isRealThumb = d.thumbnail_link && d.thumbnail_link !== d.icon_link; const thumbAttr = isRealThumb ? `data-pk-thumb="${d.thumbnail_link}"` : ''; const nameDisplay = (S.search && shouldShowHl) ? getSearchHlHTML(d.name, S.search, charCapacity) : esc(d.name); html += `
${getDynamicIcon(d)}${nameDisplay}${isProtected ? `${L.tag_default}` : ''}
`; if (showPathCol) { let pathHtml = ''; const shareInsightPathText = d._isShareInsightItem ? (d._shareFilePathText || getSharePathText(d._sharePath) || L.label_share_parse_path_root || '') : ''; const homeIcon = ``; const homeText = L.btn_nav_home; let homeQuery = S.search; let restQuery = S.search; let isSlashMatched = false; if (S.search) { const qLower = S.search.toLowerCase(); const hLower = homeText.toLowerCase(); if (qLower.startsWith(hLower + '/')) { homeQuery = S.search.substring(0, homeText.length); restQuery = S.search.substring(homeText.length + 1); isSlashMatched = true; } } const isHomeMatched = shouldShowHl && homeQuery && homeText.toLowerCase().includes(homeQuery.toLowerCase()); const homeDisplay = isHomeMatched ? getSearchHlHTML(homeText, homeQuery, 20) : esc(homeText); const prefix = homeText + '/'; const rootStyle = isMax? "display:inline-flex;align-items:baseline;flex-shrink:0;margin-right:2px;": "display:inline-flex;align-items:baseline;flex-shrink:0;line-height:1.2;padding-bottom:0;"; const contentStyle = isMax ? '' : 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.5;padding-bottom:2px;'; const containerStyle = isMax? "display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-all;line-height:1.4;white-space:normal;color:var(--pk-fg);": "display:flex;align-items:center;overflow:hidden;white-space:nowrap;color:var(--pk-fg);line-height:1.5;padding-bottom:2px;"; if (shareInsightPathText) { displayPath = shareInsightPathText; let isSameAsPrev = false; if (i > firstVisiblePathIndex) { const prevItem = S.display[i - 1]; if (prevItem && !prevItem.isHeader) { const prevSharePathText = prevItem._isShareInsightItem ? (prevItem._shareFilePathText || getSharePathText(prevItem._sharePath) || L.label_share_parse_path_root || '') : ''; if (prevSharePathText === shareInsightPathText) isSameAsPrev = true; } } if (isSameAsPrev) { pathHtml = `${L.str_same_folder}`; } else { const includePath = UI.chkSearchPath && UI.chkSearchPath.checked; const pathDisplay = (shouldShowHl && includePath && shareInsightPathText) ? getSharePathSearchHlHTML(shareInsightPathText, S.search, pathCharCapacity) : esc(shareInsightPathText); pathHtml = `
${pathDisplay}
`; } } else if (isAnalyzeRoot) { const pStr = d._pathStr || d.path || ""; displayPath = pStr; let isSameAsPrev = false; if (i > firstVisiblePathIndex) { const prevItem = S.display[i - 1]; if (!prevItem.isHeader) { const prevPStr = prevItem._pathStr || prevItem.path || ""; if (prevPStr === pStr) isSameAsPrev = true; else isSameAsPrev = false; } else { isSameAsPrev = false; } } else { isSameAsPrev = false; } if (isSameAsPrev) { pathHtml = `${L.str_same_folder}`; } else { let content = pStr; if (pStr === homeText) content = ""; else if (pStr.startsWith(prefix)) content = pStr.substring(prefix.length); let hq = restQuery; let sm = isSlashMatched; if (hq.endsWith('/')) hq = hq.slice(0, -1); if (hq.startsWith('/') && content.toLowerCase().startsWith(hq.substring(1).toLowerCase())) { sm = true; hq = hq.substring(1); } const sDisp = (shouldShowHl && sm) ? `/` : '/'; const sHtml = `${sDisp}`; const includePath = UI.chkSearchPath && UI.chkSearchPath.checked; const pathDisplay = (shouldShowHl && includePath && content) ? getSearchHlHTML(content, hq, pathCharCapacity) : esc(content); let innerHtml = `${homeIcon}${homeDisplay}`; if (content) { innerHtml += `${sHtml}${pathDisplay}`; } pathHtml = `
${innerHtml}
`; } } else if (S.dupMode) { let isSameAsPrev = false; const rawPath = d._dupFullPath || ""; if (i > firstVisiblePathIndex) { const prevItem = S.display[i - 1]; if (!prevItem.isHeader) { const prevRawPath = prevItem._dupFullPath || ""; if (prevRawPath === rawPath) isSameAsPrev = true; else isSameAsPrev = false; } else { isSameAsPrev = false; } } else { isSameAsPrev = false; } if (isSameAsPrev) { pathHtml = `${L.str_same_folder}`; } else { let realPath = rawPath; if (rawPath === L.current_dir) { realPath = rootPathStr; } else { if (!rawPath.startsWith(rootPathStr)) { realPath = rootPathStr + '/' + rawPath; } } let contentStr = realPath; if (realPath === homeText) { contentStr = ""; } else if (realPath.startsWith(prefix)) { contentStr = realPath.substring(prefix.length); } let hq = restQuery; let sm = isSlashMatched; if (hq.endsWith('/')) hq = hq.slice(0, -1); if (hq.startsWith('/') && contentStr.toLowerCase().startsWith(hq.substring(1).toLowerCase())) { sm = true; hq = hq.substring(1); } const sDisp = (shouldShowHl && sm) ? `/` : '/'; const sHtml = `${sDisp}`; const includePath = UI.chkSearchPath && UI.chkSearchPath.checked; const pathDisplay = (shouldShowHl && includePath && contentStr) ? getSearchHlHTML(contentStr, hq, pathCharCapacity) : esc(contentStr); let innerHtml = `${homeIcon}${homeDisplay}`; if (contentStr) { innerHtml += `${sHtml}${pathDisplay}`; } pathHtml = `
${innerHtml}
`; } } else { let fullPathStr = ""; if (d._lineage === null && d.parent_id && d.parent_id !== 'root') { pathHtml = `...`; displayPath = null; } else if (isGlobalSearchRoot) { fullPathStr = shortPath; } else { if (displayPath && displayPath.startsWith(prefix)) { fullPathStr = displayPath.substring(prefix.length); } else { fullPathStr = shortPath; } } let isSameAsPrev = false; if (i > firstVisiblePathIndex) { const prevItem = S.display[i - 1]; if (!prevItem.isHeader) { let prevPathStr = ""; if (S.analyzeMode) { prevPathStr = prevItem._pathStr || ""; } else { if (prevItem._lineage) prevPathStr = prevItem._lineage.map(x=>x.name).join('/'); } if (prevPathStr === shortPath) isSameAsPrev = true; } } if (displayPath !== null) { if (isSameAsPrev) { pathHtml = `${L.str_same_folder}`; } else { let hq = restQuery; let sm = isSlashMatched; if (hq.endsWith('/')) hq = hq.slice(0, -1); if (hq.startsWith('/') && fullPathStr.toLowerCase().startsWith(hq.substring(1).toLowerCase())) { sm = true; hq = hq.substring(1); } const sDisp = (shouldShowHl && sm) ? `/` : '/'; const sHtml = `${sDisp}`; const includePath = UI.chkSearchPath && UI.chkSearchPath.checked; const pathDisplay = (shouldShowHl && includePath && fullPathStr) ? getSearchHlHTML(fullPathStr, hq, pathCharCapacity) : esc(fullPathStr); let innerHtml = `${homeIcon}${homeDisplay}`; if (fullPathStr) { innerHtml += `${sHtml}${pathDisplay}`; } pathHtml = `
${innerHtml}
`; } } } let pathTipHtml = getTooltipHlHTML(displayPath || "", S.search); if (displayPath && displayPath.startsWith(homeText)) { const rest = displayPath.substring(homeText.length); const homeDisplayTip = getTooltipHlHTML(homeText, homeQuery); let restDisplayTip = ''; if (rest.startsWith('/')) { const pureRest = rest.substring(1); let hq = restQuery; let sm = isSlashMatched; if (hq.endsWith('/')) hq = hq.slice(0, -1); if (hq.startsWith('/') && pureRest.toLowerCase().startsWith(hq.substring(1).toLowerCase())) { sm = true; hq = hq.substring(1); } const slashTip = (shouldShowHl && sm) ? `/` : '/'; restDisplayTip = slashTip + getTooltipHlHTML(pureRest, hq); } else { restDisplayTip = getTooltipHlHTML(rest, restQuery); } const homeGroup = `${homeIcon}${homeDisplayTip}`; pathTipHtml = `
${homeGroup}${restDisplayTip}
`; } html += `
${pathHtml}
`; } const displaySize = (d.kind === 'drive#folder' && !S.analyzeMode) ? '-' : fmtSize(d.size); html += `
${displaySize}
`; if (!isAnalyzeRoot) { const isFolder = d.kind === 'drive#folder'; const displayDur = S.durationMap.get(d.id) || d.params?.duration || 0; const mime = (d.mime_type || '').toLowerCase(); const ext = (d.name || '').split('.').pop().toLowerCase(); const isVideoFormat =['mp4','mkv','avi','mov','wmv','flv','webm','ts'].includes(ext); const isVideo = !isFolder && (mime.startsWith('video/') || isVideoFormat || displayDur > 0); let durHtml = "-"; if (isFolder) { if (!S.trashMode) { let count = d.file_count; if ((count === undefined || count === null) && !d._isShareItem && typeof globalCache !== 'undefined') { const cachedData = globalCache.get(d.id); if (cachedData) { const list = Array.isArray(cachedData) ? cachedData : (cachedData.items ||[]); count = list.length; } } const hasCount = count !== undefined && count !== null; if (hasCount) { durHtml = `${count} ${L.str_items}`; } } } else if (isVideo && displayDur > 0) { durHtml = fmtDur(displayDur); } else { const rawName = d.name || ""; const lastDot = rawName.lastIndexOf('.'); if (lastDot > 0) { const extUpper = rawName.substring(lastDot + 1).toUpperCase(); const SETS = { vid: new Set(['MP4','MKV','AVI','MOV','WMV','FLV','WEBM','TS','M4V','3GP','MPG','MPEG','RM','RMVB','ASF','VOB','DAT','DIVX','F4V','M2TS','MTS','TP','TRP','OGV','MPE','M2V','M3U8']), aud: new Set(['MP3','WAV','FLAC','AAC','OGG','WMA','APE','M4A','AMR','OPUS','M4B','ALAC','AIFF','MID','MIDI','RA','DTS','AC3','DSF','DFF']), img: new Set(['JPG','JPEG','PNG','GIF','BMP','WEBP','SVG','TIF','TIFF','ICO','HEIC','HEIF','RAW','CR2','NEF','ARW','DNG','ORF','AVIF','PSD','AI','EPS','JFIF','JPE']), doc: new Set(['TXT','HTML','PDF','PPTX','CHM','DOCX','XLSX','HTM','DOC','DWG','MDB','PPT','XLS','RTF','ODT','ODS','ODP','EPUB','MOBI','AZW3','DJVU','CBZ','CBR','MD','LOG','CSV','XML','JSON']), app: new Set(['APK','EXE','IPA','DMG','RPM','DEB','MSI','PKG','XAPK','APKS','AAB','JAR','BIN','SH','BAT','CMD']), arc: new Set(['ZIP','RAR','7Z','TAR','GZ','ISO','CAB','BZ2','XZ','TGZ','WIM','ESD','IMG','ZST','LZH']), sub: new Set(['SRT','ASS','SSA','VTT','SMI','SUB','IDX','SUP','LRC']) }; if (SETS.img.has(extUpper)) durHtml = `${extUpper} ${L.type_img}`; else if (SETS.arc.has(extUpper) || /^(PART\d+|\d{3}|[RZ]\d{2})$/.test(extUpper)) durHtml = `${extUpper} ${L.type_archive}`; else if (SETS.doc.has(extUpper)) durHtml = `${extUpper} ${L.type_doc}`; else if (SETS.sub.has(extUpper)) durHtml = `${extUpper} ${L.type_sub}`; else if (SETS.app.has(extUpper)) durHtml = `${extUpper} ${L.type_app}`; else if (SETS.aud.has(extUpper)) durHtml = `${extUpper} ${L.cat_audio}`; else if (SETS.vid.has(extUpper)) durHtml = `${extUpper} ${L.cat_video}`; else durHtml = `${extUpper} ${L.type_suffix}`; } else { durHtml = L.type_suffix; } } html += `
${durHtml}
`; } const dateTxt = S.trashMode ? getRemainingDays(d) : fmtDate(d.modified_time); html += `
${dateTxt}
`; } if (!canReuseHistoryRow && !canReuseGridRow) { row.innerHTML = html; } row.dataset.pkBoundId = d.id || ''; row.dataset.pkBoundKind = d.kind || ''; if (suppressGridRebindTransition) { const reboundId = d.id || ''; requestAnimationFrame(() => { if (row.dataset.pkBoundId === reboundId) { row.style.transition = ''; row.style.animation = ''; } }); } if (isGridMode && !canReuseGridRow && typeof patchFrozenGridMedia === 'function') { patchFrozenGridMedia(row, d, prevGridMediaNode, prevGridMediaKey); } else if (canReuseGridRow && typeof syncGridVideoPlayState === 'function') { requestAnimationFrame(() => syncGridVideoPlayState(row, d)); } if (isGridMode && useGridStableRows && !canReuseGridRow && !gridStableDuplicateId) { const finalGridStableSig = canTryGridStableRow ? gridStableSig : ''; row.dataset.pkGridStable = finalGridStableSig ? '1' : '0'; row.dataset.pkGridSig = finalGridStableSig; } const thumbImg = row.querySelector('.pk-max-thumb, .pk-min-thumb'); if (thumbImg) { const markFrozenThumbState = (state) => { if (thumbImg.dataset.pkFrozenCover !== '1' || !thumbImg.dataset.pkId || typeof window.markStableFileMediaState !== 'function') return; window.markStableFileMediaState(thumbImg.dataset.pkId, state, state === 'ok' ? (thumbImg.dataset.pkSrc || thumbImg.currentSrc || thumbImg.src || '') : ''); }; const toggleThumb = () => { if(window.pkGlobalThumbCache) window.pkGlobalThumbCache.add(d.id); markFrozenThumbState('ok'); thumbImg.style.opacity = '1'; const placeholder = thumbImg.previousElementSibling; if (placeholder && (placeholder.classList.contains('pk-placeholder-layer') || placeholder.classList.contains('pk-min-ph'))) { placeholder.style.opacity = '0'; } const parent = thumbImg.parentElement; const badge = parent ? parent.querySelector('.pk-folder-badge') : null; if (badge) { badge.style.opacity = '1'; } const vidOv = parent ? parent.querySelector('.pk-video-ov') : null; if (vidOv) { vidOv.style.opacity = '1'; } if (typeof syncGridVideoPlayState === 'function') { syncGridVideoPlayState(row, d); } if (S.isSelected(d.id)) scheduleStatUpdate(); }; const handleThumbError = () => { thumbImg.style.display = 'none'; if (window.pkGlobalThumbCache) window.pkGlobalThumbCache.delete(d.id); markFrozenThumbState('fail'); const placeholder = thumbImg.previousElementSibling; if (placeholder && (placeholder.classList.contains('pk-placeholder-layer') || placeholder.classList.contains('pk-min-ph'))) { placeholder.style.opacity = '1'; } const parent = thumbImg.parentElement; const badge = parent ? parent.querySelector('.pk-folder-badge') : null; if (badge) { badge.style.opacity = '0'; } if (typeof syncGridVideoPlayState === 'function') { syncGridVideoPlayState(row, d); } if (S.isSelected(d.id)) scheduleStatUpdate(); }; if (thumbImg.complete) { if (thumbImg.naturalWidth > 0) toggleThumb(); else handleThumbError(); } else { thumbImg.onload = toggleThumb; thumbImg.onerror = handleThumbError; } } if (!d.isHeader && d.thumbnail_link) { const minIcon = row.querySelector('.pk-min-icon'); if (minIcon && !minIcon.querySelector('.pk-proxy-thumb')) { const proxyImg = document.createElement('img'); proxyImg.className = 'pk-proxy-thumb'; proxyImg.src = d.thumbnail_link; proxyImg.style.cssText = 'position: absolute; top: 0; left: 0; width: 0; height: 0; opacity: 0; pointer-events: none; visibility: hidden;'; minIcon.appendChild(proxyImg); } } const chk = row.querySelector('input'); if (chk && !d.isHeader) { const shouldChecked = S.isSelected(d.id); if (chk.checked !== shouldChecked) chk.checked = shouldChecked; if (chk.indeterminate) chk.indeterminate = false; } const gridMoreBtn = row.querySelector('.pk-gv-more'); if (gridMoreBtn) { gridMoreBtn.onclick = (evt) => { evt.preventDefault(); evt.stopPropagation(); const openMenu = () => { const liveRow = UI.in.querySelector(`.pk-row[data-id="${d.id}"]`) || row; const liveBtn = liveRow.querySelector('.pk-gv-more') || gridMoreBtn; const btnRect = liveBtn.getBoundingClientRect(); liveRow.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, cancelable: true, clientX: Math.max(btnRect.left + 8, btnRect.right - 8), clientY: btnRect.bottom + 6, button: 2 })); }; if (!S.isSelected(d.id) || S.activeId !== d.id) { S.setAllSelection(false); S.setSelected(d.id, true); S.activeId = d.id; S.lastSelIdx = i; renderVisible(); updateStat(); requestAnimationFrame(openMenu); return; } openMenu(); }; } const triggerOpen = () => { if (S.movingIds.has(d.id)) return; if (d._isShareItem) { if (d.kind === 'drive#folder') { if (!S.shareParseInsightMode) handleShareParseFolderOpen(d); } else if (isArchiveLike(d)) handleOpenShareArchive(d); else if (isAudioLikeItem(d) || isVideoLikeItem(d) || isImageLikeItem(d)) handleOpenShareFile(d); else if (isTxtFileLike(d)) handleOpenShareTextFile(d); else showUnsupportedOpenToast(); return; } if (S.trashMode) return; const checkType = (it) => { const m = (it.mime_type || '').toLowerCase(); const n = (it.name || '').toLowerCase(); const dur = (it.params && it.params.duration) || 0; const vExts = ['mp4','mkv','avi','mov','wmv','flv','webm','ts','m4v','3gp']; const aExts = ['zip','rar','7z','tar','gz','bz2','xz']; const isAudio = isAudioLikeItem(it); return { isAudio, isVideo: !isAudio && (m.startsWith('video/') || dur > 0 || vExts.some(e => n.endsWith('.' + e))), isImage: m.startsWith('image/'), isTorrent: n.endsWith('.torrent'), isArchive: m.includes('zip') || m.includes('rar') || m.includes('archive') || aExts.some(e => n.endsWith('.' + e)) }; }; if (S.uploadMode) { if (d.status !== 'DONE') { if (d.file_id) { S.setAllSelection(false); S.setSelected(d.id, true); S.activeId = d.id; const locateBtn = document.getElementById('ctx-locate'); if (locateBtn) locateBtn.click(); } return; } const t = checkType(d); if (t.isTorrent) handleTorrentFile(d); else if (t.isAudio || t.isVideo || t.isImage) { if (t.isAudio) playAudio(d); else if (t.isVideo) playVideo(d); else showImage(d); } else if (d.file_id) { S.setAllSelection(false); S.setSelected(d.id, true); S.activeId = d.id; const locateBtn = document.getElementById('ctx-locate'); if (locateBtn) locateBtn.click(); } return; } if (S.offlineMode) { if (!d.file_id || d.phase === 'PHASE_TYPE_ERROR') return; if (d.phase !== 'PHASE_TYPE_COMPLETE') { S.setAllSelection(false); S.setSelected(d.id, true); S.activeId = d.id; const locateBtn = document.getElementById('ctx-locate'); if (locateBtn) locateBtn.click(); return; } const t = checkType(d); if (t.isTorrent) handleTorrentFile(d); else if (t.isAudio || t.isVideo || t.isImage) { if (t.isAudio) playAudio(d); else if (t.isVideo) playVideo(d); else showImage(d); } else { S.setAllSelection(false); S.setSelected(d.id, true); S.activeId = d.id; const locateBtn = document.getElementById('ctx-locate'); if (locateBtn) { locateBtn.click(); } } return; } if (S.shareMode) { const isShareDisabled = (d.share_status === 'DELETED') || (d.limit_count > 0 && d.save_count >= d.limit_count); if (isShareDisabled) return; showShareDetail(d); return; } if (d.kind === 'drive#folder') { if (!S.trashMode) { const isExitMode = S.isFlattened || S.dupMode; if (isExitMode) { S.isFlattened = false; S.dupMode = false; UI.scan.style.display = 'flex'; UI.btnExit.style.display = 'none'; if(UI.dupTools) UI.dupTools.style.display = 'none'; if(UI.btnFolderFirst) UI.btnFolderFirst.style.display = 'flex'; if(UI.btnNewFolder) UI.btnNewFolder.style.display = 'flex'; if(UI.lblGlobal) UI.lblGlobal.style.display = 'flex'; if(UI.chkGlobal) UI.chkGlobal.checked = false; if (d._lineage && d._lineage.length > 0) { const newPath = [{ id: '', name: L.btn_nav_home }]; d._lineage.forEach(n => { if (n.id && n.id !== 'root') newPath.push({ id: n.id, name: n.name }); }); if (newPath.length === 0 || newPath[newPath.length-1].id !== d.id) newPath.push({ id: d.id, name: d.name }); S.path = newPath; } else { S.path = [{ id: '', name: L.btn_nav_home }, { id: d.id, name: d.name }]; } load(); } else { const lastNode = S.path[S.path.length - 1]; if (lastNode && lastNode.id === d.id) return; syncFolderFirstFromGlobal(); S.saveNavScrollTop(S.getNavBucketKey(), S.path); S.path.push(d); load(); } } } else { const t = checkType(d); if (t.isTorrent) handleTorrentFile(d); else if (t.isArchive) handleOpenArchive(d); else if (t.isAudio) playAudio(d); else if (t.isVideo) playVideo(d); else if (t.isImage) showImage(d); else if (isTxtFileLike(d)) handleOpenTextFile(d); else showUnsupportedOpenToast(); } }; row.onclick = async (e) => { if (UI.ctx && UI.ctx.style.display !== 'none') { UI.ctx.style.display = 'none'; } const nameText = e.target.closest('.pk-name .pk-name-txt'); const isAnalyzeRoot = S.analyzeMode && S.path[S.path.length - 1].id === 'analyze_root'; const isProtectedView = S.dupMode || (isAnalyzeRoot && S.analyzeSimGroups); if (!S.trashMode && nameText) { const selectedCount = S.getSelectedCount(); const isCurrentSelected = S.isSelected(d.id); if (isProtectedView && selectedCount > 5) { if (selectedCount > 1 || !isCurrentSelected) { if (!await confirmSelectionClear()) return; } } S.setAllSelection(false); S.setSelected(d.id, true); S.activeId = d.id; triggerOpen(); renderVisible(); updateStat(); return; } S.activeId = d.id; if (e.target === chk) { if (e.shiftKey && S.lastSelIdx !== -1) { const startIdx = Math.min(S.lastSelIdx, i); const endIdx = Math.max(S.lastSelIdx, i); const targetState = chk.checked; for (let k = startIdx; k <= endIdx; k++) { const item = S.display[k]; if (item && !item.isHeader && !S.movingIds.has(item.id)) { S.setSelected(item.id, targetState); } } } else { S.setSelected(d.id, chk.checked); } } else { if (e.shiftKey && S.lastSelIdx !== -1) { const startIdx = Math.min(S.lastSelIdx, i); const endIdx = Math.max(S.lastSelIdx, i); for (let k = startIdx; k <= endIdx; k++) { const item = S.display[k]; if (item && !item.isHeader && !S.movingIds.has(item.id)) S.setSelected(item.id, true); } } else if (e.ctrlKey || e.metaKey) { S.toggleSelected(d.id); } else { const selectedCount = S.getSelectedCount(); const isCurrentSelected = S.isSelected(d.id); if (isProtectedView && selectedCount > 5) { if (selectedCount > 1 || !isCurrentSelected) { if (!await confirmSelectionClear()) { renderVisible(); return; } } } S.setAllSelection(false); S.setSelected(d.id, true); } } if (S.activeId && !S.isSelected(S.activeId)) { S.activeId = null; } S.lastSelIdx = i; const selectedCountForClick = isGridView() ? S.getSelectedCount() : -1; const rows = UI.in.children; for (let k = 0; k < rows.length; k++) { const r = rows[k]; if (r.dataset.id) { const rid = r.dataset.id; const isSelected = S.isSelected(rid); const isActiveRow = (S.activeId === rid); const isFocused = isSelected && isActiveRow; if (!(isActiveRow && !isSelected)) { r.style.backgroundColor = ''; r.style.border = ''; r.style.borderRadius = ''; } if (isActiveRow && !isSelected) { r.style.backgroundColor = 'var(--pk-sel-bg)'; r.style.border = '1px solid var(--pk-pri)'; r.style.borderRadius = isGridView() ? `${getGridCardRadius()}px` : '4px'; } const cls = getRowClassName(isSelected, isFocused, r.classList.contains('pk-moving'), selectedCountForClick); if (r.className !== cls) r.className = cls; const cb = r.querySelector('input[type="checkbox"]'); if (cb && cb.checked !== isSelected) cb.checked = isSelected; } else if (r.classList.contains('pk-group-hd')) { const grpChk = r.querySelector('.pk-grp-chk'); if (grpChk) { const gNode = S.display[start + k]; if (gNode && gNode.isHeader) { const gIdx = parseInt(gNode.id.replace('grp_', '')); const gIds = Array.isArray(gNode.groupIds) ? gNode.groupIds : (S.dupMode ? (S.dupRawGroups[gIdx]?.ids || []) : (S.analyzeMode ? (S.analyzeSimGroups[gIdx]?.ids || []) : [])); let selCount = 0; gIds.forEach(id => { if (S.isSelected(id)) selCount++; }); const isAll = gIds.length > 0 && selCount === gIds.length; const isInd = selCount > 0 && selCount < gIds.length; if (grpChk.checked !== isAll) grpChk.checked = isAll; if (grpChk.indeterminate !== isInd) grpChk.indeterminate = isInd; } } } } updateStat(); }; row.ondblclick = (e) => { e.preventDefault(); if (d._isShareItem) { if (d.kind === 'drive#folder') { if (!S.shareParseInsightMode) handleShareParseFolderOpen(d); } else triggerOpen(); return; } if (S.trashMode) return; if (S.uploadMode) { if (d.status !== 'DONE') return; triggerOpen(); return; } if (S.shareMode) { const isShareDisabled = (d.share_status === 'DELETED') || (d.limit_count > 0 && d.save_count >= d.limit_count); if (!isShareDisabled) { showShareDetail(d); } return; } const isNameTxt = e.target.closest('.pk-name .pk-name-txt'); if (e.target === chk || isNameTxt) return; if (S.offlineMode) { triggerOpen(); return; } if (d.kind === 'drive#folder') { const isExitMode = S.isFlattened || S.dupMode; if (isExitMode) { S.isFlattened = false; S.dupMode = false; if (S.filterState) S.filterState = { active: false, cat: 'all', ext: 'all' }; UI.scan.style.display = 'flex'; UI.btnExit.style.display = 'none'; if(UI.dupTools) UI.dupTools.style.display = 'none'; if(UI.dupFilters) UI.dupFilters.style.display = 'none'; if(UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'flex'; if(UI.btnNewFolder) UI.btnNewFolder.style.display = 'flex'; if(UI.lblGlobal) UI.lblGlobal.style.display = 'flex'; if(UI.chkGlobal) UI.chkGlobal.checked = false; if (d._lineage && d._lineage.length > 0) { const newPath = [{ id: '', name: L.btn_nav_home }]; d._lineage.forEach(n => { if (n.id && n.id !== 'root') newPath.push({ id: n.id, name: n.name }); }); if (newPath.length === 0 || newPath[newPath.length-1].id !== d.id) { newPath.push({ id: d.id, name: d.name }); } S.path = newPath; } else { S.path = [{ id: '', name: L.btn_nav_home }, { id: d.id, name: d.name }]; } load(); } else { syncFolderFirstFromGlobal(); S.saveNavScrollTop(S.getNavBucketKey(), S.path); S.path.push(d); load(); } } else { const mime = (d.mime_type || "").toLowerCase(); const name = (d.name || "").toLowerCase(); if (name.endsWith('.torrent')) { handleTorrentFile(d); } else if (mime.includes('zip') || mime.includes('rar') || mime.includes('7z') || mime.includes('compressed') || mime.includes('archive') || name.endsWith('.zip') || name.endsWith('.rar') || name.endsWith('.7z') || name.endsWith('.tar') || name.endsWith('.gz')) { handleOpenArchive(d); } else if (isAudioLikeItem(d)) { playAudio(d); } else if (mime.startsWith('video')) { playVideo(d); } else if (mime.startsWith('image')) { showImage(d); } } }; row.oncontextmenu = (e) => { e.preventDefault(); if ((S.shareParseMode || d._isShareItem) && !(S.shareParseMode && d._isShareItem && S.shareParseListActive)) { if (UI.ctx) UI.ctx.style.display = 'none'; return; } if (!S.isSelected(d.id)) { S.setAllSelection(false); S.setSelected(d.id, true); S.activeId = d.id; S.lastSelIdx = i; renderVisible(); updateStat(); } const itms = { share: UI.ctx.querySelector('#ctx-share'), open: UI.ctx.querySelector('#ctx-open'), extPlay: UI.ctx.querySelector('#ctx-ext-play'), star: UI.ctx.querySelector('#ctx-star'), prop: UI.ctx.querySelector('#ctx-property'), locate: UI.ctx.querySelector('#ctx-locate'), down: UI.ctx.querySelector('#ctx-down'), cpName: UI.ctx.querySelector('#ctx-copy-name'), cut: UI.ctx.querySelector('#ctx-cut'), copy: UI.ctx.querySelector('#ctx-copy'), rename: UI.ctx.querySelector('#ctx-rename'), prune: UI.ctx.querySelector('#ctx-prune'), addBl: UI.ctx.querySelector('#ctx-add-bl'), del: UI.ctx.querySelector('#ctx-del'), restore: UI.ctx.querySelector('#ctx-restore'), delF: UI.ctx.querySelector('#ctx-del-forever'), shCancel: UI.ctx.querySelector('#ctx-share-cancel'), shDetail: UI.ctx.querySelector('#ctx-share-detail'), shCopy: UI.ctx.querySelector('#ctx-share-copy'), taskRetry: UI.ctx.querySelector('#ctx-task-retry') }; const seps = Array.from(UI.ctx.querySelectorAll('.pk-ctx-sep')); if (!itms.taskRetry) { const retryDiv = document.createElement('div'); retryDiv.className = 'pk-ctx-item'; retryDiv.id = 'ctx-task-retry'; retryDiv.innerHTML = `${CONF.icons.refresh} ${L.btn_retry_task}`; const ref = document.getElementById('ctx-down'); if (ref) ref.parentNode.insertBefore(retryDiv, ref); itms.taskRetry = retryDiv; } if (!UI.ctx.querySelector('#ctx-up-pause')) { const pBtn = document.createElement('div'); pBtn.className = 'pk-ctx-item'; pBtn.id = 'ctx-up-pause'; pBtn.innerHTML = `${CONF.icons.taskPause} ${L.btn_up_pause}`; const delRef = document.getElementById('ctx-del'); if (delRef) delRef.parentNode.insertBefore(pBtn, delRef); } if (!UI.ctx.querySelector('#ctx-up-start')) { const sBtn = document.createElement('div'); sBtn.className = 'pk-ctx-item'; sBtn.id = 'ctx-up-start'; sBtn.innerHTML = `${CONF.icons.taskStart} ${L.btn_up_start}`; const delRef = document.getElementById('ctx-del'); if (delRef) delRef.parentNode.insertBefore(sBtn, delRef); } const allCtxItems = UI.ctx.querySelectorAll('.pk-ctx-item'); const allCtxSeps = UI.ctx.querySelectorAll('.pk-ctx-sep'); allCtxItems.forEach(el => el.style.display = 'none'); allCtxSeps.forEach(el => el.style.display = 'none'); const sepShareExtra = UI.ctx.querySelector('#sep-share-extra'); if (S.shareParseMode && d._isShareItem && S.shareParseListActive) { const sp = { open: UI.ctx.querySelector('#ctx-open'), ext: UI.ctx.querySelector('#ctx-ext-play'), name: UI.ctx.querySelector('#ctx-copy-name'), save: UI.ctx.querySelector('#ctx-share-parse-save'), sep2: UI.ctx.querySelector('#sep-2'), sep3: UI.ctx.querySelector('#sep-3') }; const ids = S.getSelectedIds(); const isSingle = ids.length === 1; const item = isSingle ? ((S.shareParseInsightItemMap && S.shareParseInsightItemMap.get(ids[0])) || (S.shareParseItemMap && S.shareParseItemMap.get(ids[0])) || S.itemMap.get(ids[0]) || d) : null; const isVideo = !!(item && item.kind !== 'drive#folder' && isVideoLikeItem(item)); if (sp.open) sp.open.style.display = isSingle ? 'flex' : 'none'; if (sp.ext) sp.ext.style.display = isVideo ? 'flex' : 'none'; if (sp.sep2) sp.sep2.style.display = isSingle ? 'block' : 'none'; if (sp.name) sp.name.style.display = 'flex'; if (sp.sep3) sp.sep3.style.display = 'block'; if (sp.save) { sp.save.style.display = 'flex'; sp.save.onclick = (e) => { e.stopPropagation(); UI.ctx.style.display = 'none'; handleShareParseSaveSelected(); }; } } else if (S.shareMode) { const sh = { name: UI.ctx.querySelector('#ctx-copy-name'), cancel: UI.ctx.querySelector('#ctx-sh-cancel'), detail: UI.ctx.querySelector('#ctx-sh-detail'), copy: UI.ctx.querySelector('#ctx-sh-copy') }; const selectedIds = S.getSelectedIds(); const isSingle = (selectedIds.length === 1); const isShareDisabled = (d.share_status === 'DELETED') || (d.limit_count > 0 && d.save_count >= d.limit_count); if(seps[0]) seps[0].style.display = 'none'; if(seps[1]) seps[1].style.display = 'none'; if(sh.name) sh.name.style.display = 'flex'; if(seps[2]) seps[2].style.display = 'block'; if(sh.cancel) sh.cancel.style.display = 'flex'; const showDetails = isSingle && !isShareDisabled; if (showDetails) { if(sepShareExtra) sepShareExtra.style.display = 'block'; if(sh.detail) sh.detail.style.display = 'flex'; if(sh.copy) sh.copy.style.display = 'flex'; } if(seps[3]) seps[3].style.display = 'none'; } else if (S.uploadMode) { const upEls = { open: UI.ctx.querySelector('#ctx-open'), ext: UI.ctx.querySelector('#ctx-ext-play'), loc: UI.ctx.querySelector('#ctx-locate'), name: UI.ctx.querySelector('#ctx-copy-name'), del: UI.ctx.querySelector('#ctx-del'), pause: UI.ctx.querySelector('#ctx-up-pause'), start: UI.ctx.querySelector('#ctx-up-start'), sep1: UI.ctx.querySelector('#sep-1'), sep2: UI.ctx.querySelector('#sep-2'), sep3: UI.ctx.querySelector('#sep-3') }; const uploadSelectedIds = S.getSelectedIds(); if (uploadSelectedIds.length > 1) { const items = uploadSelectedIds.map(id => S.itemMap.get(id)).filter(Boolean); let hasActive = false, hasPaused = false, hasDone = false; items.forEach(t => { if (['UPLOADING', 'HASHING', 'WAITING', 'RUNNING'].includes(t.status)) hasActive = true; else if (['PAUSED', 'ERROR'].includes(t.status)) hasPaused = true; else if (t.status === 'DONE') hasDone = true; }); upEls.name.style.order = '10'; upEls.name.style.display = 'flex'; if (hasActive && !hasPaused && !hasDone) { if (upEls.sep1) { upEls.sep1.style.order = '15'; upEls.sep1.style.display = 'block'; } if (upEls.pause) { upEls.pause.style.order = '20'; upEls.pause.style.display = 'flex'; upEls.pause.onclick = (e) => { e.stopPropagation(); UI.ctx.style.display = 'none'; if(UI.btnUpPause) UI.btnUpPause.click(); }; } } else if (!hasActive && hasPaused && !hasDone) { if (upEls.sep1) { upEls.sep1.style.order = '15'; upEls.sep1.style.display = 'block'; } if (upEls.start) { upEls.start.style.order = '20'; upEls.start.style.display = 'flex'; upEls.start.onclick = (e) => { e.stopPropagation(); UI.ctx.style.display = 'none'; if(UI.btnUpStart) UI.btnUpStart.click(); }; } } upEls.sep2.style.order = '25'; upEls.sep2.style.display = 'block'; upEls.del.style.order = '30'; upEls.del.style.display = 'flex'; upEls.del.innerHTML = `${CONF.icons.del} ${L.btn_up_del}`; upEls.del.onclick = (e) => { e.stopPropagation(); UI.ctx.style.display = 'none'; if(UI.btnUpDel) UI.btnUpDel.click(); }; } else { const mime = (d.mime_type || '').toLowerCase(); const isAudio = isAudioLikeItem(d); const isImg = mime.startsWith('image/'); const isVideo = mime.startsWith('video/') || ['mp4','mkv','avi','mov','wmv','flv','webm','ts','m4v','3gp'].some(e => (d.name||'').toLowerCase().endsWith('.'+e)); const isActive = ['UPLOADING', 'HASHING', 'WAITING', 'RUNNING'].includes(d.status); const isDone = d.status === 'DONE'; if (isDone) { upEls.open.style.order = '10'; upEls.open.style.display = (isAudio || isVideo || isImg) ? 'flex' : 'none'; upEls.ext.style.order = '11'; upEls.ext.style.display = isVideo ? 'flex' : 'none'; upEls.loc.style.order = '12'; upEls.loc.style.display = d.file_id ? 'flex' : 'none'; upEls.sep1.style.order = '15'; upEls.sep1.style.display = 'block'; upEls.name.style.order = '20'; upEls.name.style.display = 'flex'; upEls.sep2.style.order = '25'; upEls.sep2.style.display = 'block'; upEls.del.style.order = '30'; upEls.del.style.display = 'flex'; upEls.del.innerHTML = `${CONF.icons.del} ${L.btn_up_del}`; upEls.del.onclick = (e) => { e.stopPropagation(); UI.ctx.style.display = 'none'; if(UI.btnUpDel) UI.btnUpDel.click(); }; } else if (isActive) { upEls.name.style.order = '10'; upEls.name.style.display = 'flex'; upEls.sep1.style.order = '15'; upEls.sep1.style.display = 'block'; if (upEls.pause) { upEls.pause.style.order = '20'; upEls.pause.style.display = 'flex'; upEls.pause.onclick = (e) => { e.stopPropagation(); UI.ctx.style.display = 'none'; if(UI.btnUpPause) UI.btnUpPause.click(); }; } upEls.sep2.style.order = '25'; upEls.sep2.style.display = 'block'; upEls.del.style.order = '30'; upEls.del.style.display = 'flex'; upEls.del.innerHTML = `${CONF.icons.del} ${L.btn_up_del}`; upEls.del.onclick = (e) => { e.stopPropagation(); UI.ctx.style.display = 'none'; if(UI.btnUpDel) UI.btnUpDel.click(); }; } else { upEls.name.style.order = '10'; upEls.name.style.display = 'flex'; upEls.sep1.style.order = '15'; upEls.sep1.style.display = 'block'; if (upEls.start) { upEls.start.style.order = '20'; upEls.start.style.display = 'flex'; upEls.start.onclick = (e) => { e.stopPropagation(); UI.ctx.style.display = 'none'; if(UI.btnUpStart) UI.btnUpStart.click(); }; } upEls.sep2.style.order = '25'; upEls.sep2.style.display = 'block'; upEls.del.style.order = '30'; upEls.del.style.display = 'flex'; upEls.del.innerHTML = `${CONF.icons.del} ${L.btn_up_del}`; upEls.del.onclick = (e) => { e.stopPropagation(); UI.ctx.style.display = 'none'; if(UI.btnUpDel) UI.btnUpDel.click(); }; } } } else if (S.offlineMode) { const els = { open: UI.ctx.querySelector('#ctx-open'), ext: UI.ctx.querySelector('#ctx-ext-play'), loc: UI.ctx.querySelector('#ctx-locate'), prop: UI.ctx.querySelector('#ctx-property'), name: UI.ctx.querySelector('#ctx-copy-name'), retry: UI.ctx.querySelector('#ctx-task-retry'), link: UI.ctx.querySelector('#ctx-copy-link'), del: UI.ctx.querySelector('#ctx-del'), bl: UI.ctx.querySelector('#ctx-add-bl'), sep1: UI.ctx.querySelector('#sep-1'), sep2: UI.ctx.querySelector('#sep-2'), sep3: UI.ctx.querySelector('#sep-3') }; const ids = S.getSelectedIds(); const isSingle = ids.length === 1; const firstItem = S.itemMap.get(ids[0]); const hasFailed = ids.some(id => S.itemMap.get(id)?.phase === 'PHASE_TYPE_ERROR'); let isAud = false, isVid = false, isImg = false; if (firstItem && firstItem.file_id) { const m = (firstItem.mime_type || '').toLowerCase(); const n = (firstItem.name || '').toLowerCase(); isAud = isAudioLikeItem(firstItem); isVid = m.startsWith('video/') || ['mp4','mkv','avi','mov','wmv','flv','webm','ts','m4v','3gp'].some(e => n.endsWith('.'+e)); isImg = m.startsWith('image/'); } let g1 = 0; if (els.open) { els.open.style.order = '10'; const isFolderLike = (firstItem.mime_type && (firstItem.mime_type.includes('folder') || firstItem.mime_type.includes('directory'))) || (firstItem.icon_link && firstItem.icon_link.includes('folder')); const isReadyMedia = firstItem.phase === 'PHASE_TYPE_COMPLETE' && (isAud || isVid || isImg); const canOpen = isSingle && firstItem?.file_id && firstItem.phase !== 'PHASE_TYPE_ERROR' && (isReadyMedia || isFolderLike); els.open.style.display = canOpen ? 'flex' : 'none'; if(canOpen) g1++; } if (els.ext) { els.ext.style.order = '11'; const canExt = isSingle && firstItem?.file_id && firstItem.phase === 'PHASE_TYPE_COMPLETE' && isVid; els.ext.style.display = canExt ? 'flex' : 'none'; if(canExt) g1++; } if (els.prop) { els.prop.style.order = '12'; els.prop.style.display = isSingle ? 'flex' : 'none'; if(isSingle) g1++; } if (els.loc) { els.loc.style.order = '13'; const canLocate = isSingle && firstItem?.file_id && firstItem.phase !== 'PHASE_TYPE_ERROR'; els.loc.style.display = canLocate ? 'flex' : 'none'; if(canLocate) g1++; } let g2 = 0; if (els.name) { els.name.style.order = '20'; els.name.style.display = 'flex'; g2++; } let g3 = 0; if (els.retry) { els.retry.style.order = '30'; els.retry.innerHTML = `${CONF.icons.retry} ${L.btn_retry_task}`; els.retry.onclick = (e) => { e.stopPropagation(); UI.ctx.style.display = 'none'; if (UI.btnRetryTask) UI.btnRetryTask.click(); }; els.retry.style.display = hasFailed ? 'flex' : 'none'; if(hasFailed) g3++; } if (els.link) { els.link.style.order = '31'; els.link.style.display = 'flex'; els.link.onclick = (e) => { e.stopPropagation(); UI.ctx.style.display = 'none'; const tasks = ids.map(id => S.itemMap.get(id)).filter(t => t && (t.source_url || t.params?.url)); if (tasks.length) { GM_setClipboard(tasks.map(t => t.source_url || t.params.url).join('\n')); showToast(L.msg_copy_success); } }; g3++; } let g4 = 0; if (els.del) { els.del.style.order = '40'; els.del.style.display = 'flex'; els.del.innerHTML = `${CONF.icons.del} ${L.btn_del}`; els.del.onclick = (e) => { e.stopPropagation(); UI.ctx.style.display = 'none'; UI.btnDel.click(); }; g4++; } if (els.bl) { els.bl.style.order = '41'; els.bl.style.display = 'flex'; setBlacklistContextItemState(els.bl); g4++; } if (els.sep1) { els.sep1.style.order = '15'; els.sep1.style.display = (g1 > 0 && (g2 > 0 || g3 > 0 || g4 > 0)) ? 'block' : 'none'; } if (els.sep2) { els.sep2.style.order = '25'; els.sep2.style.display = (g2 > 0 && (g3 > 0 || g4 > 0)) ? 'block' : 'none'; } if (els.sep3) { els.sep3.style.order = '35'; els.sep3.style.display = (g3 > 0 && g4 > 0) ? 'block' : 'none'; } } else { if(sepShareExtra) sepShareExtra.style.display = 'none'; if (S.trashMode) { const tr = { cpName: UI.ctx.querySelector('#ctx-copy-name'), restore: UI.ctx.querySelector('#ctx-restore'), delF: UI.ctx.querySelector('#ctx-del-forever'), addBl: UI.ctx.querySelector('#ctx-add-bl'), sep1: UI.ctx.querySelector('#sep-trash-1'), sep2: UI.ctx.querySelector('#sep-trash-2') }; if(tr.cpName) tr.cpName.style.display = 'flex'; if(tr.sep1) tr.sep1.style.display = 'block'; if(tr.restore) tr.restore.style.display = 'flex'; if(tr.sep2) tr.sep2.style.display = 'block'; if(tr.delF) tr.delF.style.display = 'flex'; if(tr.addBl) { tr.addBl.style.display = 'flex'; setBlacklistContextItemState(tr.addBl); } }else { if(itms.share) itms.share.style.display = 'flex'; if(seps[0]) seps[0].style.display = 'block'; let hasGroup2 = false; const ctxSelectedIds = S.getSelectedIds(); const ctxSelectedCount = ctxSelectedIds.length; if (ctxSelectedCount === 1) { [itms.open, itms.prop].forEach(el => { if(el) el.style.display = 'flex'; }); const isVideo = (d.mime_type || '').startsWith('video/') || (d.params && d.params.duration > 0); if (itms.extPlay) itms.extPlay.style.display = isVideo ? 'flex' : 'none'; hasGroup2 = true; } if (itms.star) { let hasUnstarred = false; let hasValidItem = false; for (const id of ctxSelectedIds) { const it = S.itemMap.get(id); if (it && !isSystemItem(it)) { hasValidItem = true; if (!(it.starred || (it.tags && it.tags.some(t => t.name === 'STAR')))) { hasUnstarred = true; break; } } } itms.star.style.display = hasValidItem ? 'flex' : 'none'; if (hasValidItem) { hasGroup2 = true; itms.star.innerHTML = hasUnstarred ? `${ctxIcons.star} ${L.ctx_star}` : `${ctxIcons.unstar} ${L.ctx_unstar}`; itms.star.setAttribute('data-action', hasUnstarred ? 'star' : 'unstar'); } } const isSearchRoot = S.path.length > 0 && S.path[S.path.length - 1].id === 'virtual_search_root'; const canLocateUpload = S.uploadMode && d.status === 'DONE' && d.file_id; const selectedCount = S.getSelectedCount(); if((S.starredMode || S.recentMode || S.historyMode || isSearchRoot || S.dupMode || S.isFlattened || S.analyzeMode || canLocateUpload) && selectedCount === 1) { itms.locate.style.display = 'flex'; hasGroup2 = true; } if(seps[1]) seps[1].style.display = hasGroup2 ? 'block' : 'none'; [itms.down, itms.cpName].forEach(el => { if(el) el.style.display = 'flex'; }); if (S.historyMode) { if(seps[2]) seps[2].style.display = 'none'; [itms.cut, itms.copy, itms.rename].forEach(el => { if(el) el.style.display = 'none'; }); } else { if(seps[2]) seps[2].style.display = 'block'; } if (S.historyMode) { if(seps[3]) seps[3].style.display = 'block'; } else { if (!isProtected) { if(itms.cut) itms.cut.style.display = 'flex'; if(itms.rename) { itms.rename.style.display = 'flex'; itms.rename.innerHTML = (selectedCount > 1) ? `${ctxIcons.renameBulk} ${L.btn_bulkrename}` : `${ctxIcons.rename} ${L.ctx_rename}`; } } if(itms.copy) itms.copy.style.display = 'flex'; let hasFolder = false; const selectedIds = S.getSelectedIds(); for (const id of selectedIds) { if (S.itemMap.get(id)?.kind === 'drive#folder') { hasFolder = true; break; } } if(itms.prune && hasFolder) itms.prune.style.display = 'flex'; if(seps[3]) seps[3].style.display = 'block'; } if (itms.addBl) { itms.addBl.style.display = 'flex'; setBlacklistContextItemState(itms.addBl); } if (itms.del) itms.del.style.display = 'flex'; } } const useFlexOrder = S.offlineMode || S.uploadMode; UI.ctx.style.display = useFlexOrder ? 'flex' : 'block'; if (useFlexOrder) UI.ctx.style.flexDirection = 'column'; let x = e.clientX, y = e.clientY, w = 160, h = UI.ctx.offsetHeight || 280; let winW = window.innerWidth, winH = window.innerHeight; if (x + w > winW) x = winW - w - 10; if (y + h > winH) y = winH - h - 10; UI.ctx.style.left = x + 'px'; UI.ctx.style.top = y + 'px'; }; } } flushVisibleRowPool(UI.in, poolPtr); if (window.pkRefreshTooltip) { requestAnimationFrame(() => { window.pkRefreshTooltip(); }); } maybeLoadShareParseNextPage(); } let isMarquee = false, mqStartX = 0, mqStartY = 0, startScroll = 0, lastMouseX = 0, lastMouseY = 0, scrollSpeed = 0, scrollRaf = null, marqueeRenderRaf = null, marqueeAutoScrollTs = 0; let lastRngS = -1, lastRngE = -1, cachedVpRect = null; const mqBox = document.createElement('div'); mqBox.className = 'pk-selection-box'; el.appendChild(mqBox); const scheduleMarqueeRefresh = () => { if (marqueeRenderRaf) return; marqueeRenderRaf = requestAnimationFrame(() => { marqueeRenderRaf = null; refreshVisibleSelectionState(); updateStat(); }); }; const updateMarqueeUIAndSelection = (targetX, targetY) => { if (!cachedVpRect) return; const cssTargetX = targetX; const cssTargetY = targetY; const cssRectTop = cachedVpRect.top; const cssRectBottom = cachedVpRect.bottom; const cssRectLeft = cachedVpRect.left; const cssMqStartX = mqStartX; const cssMqStartY = mqStartY; const curScroll = UI.vp.scrollTop; const clampedY = Math.max(cssRectTop, Math.min(cssRectBottom, cssTargetY)); const logicA = cssMqStartY - cssRectTop + startScroll; const logicB = clampedY - cssRectTop + curScroll; const logTop = Math.max(0, Math.min(logicA, logicB)); const logBot = Math.max(logicA, logicB); const visTop = logTop - curScroll + cssRectTop; const visBot = logBot - curScroll + cssRectTop; const clipT = Math.max(cssRectTop, visTop), clipB = Math.min(cssRectBottom, visBot); const drawH = Math.max(0, clipB - clipT); const safeRight = cssRectLeft + UI.vp.clientWidth; const clampedX = Math.max(cssRectLeft, Math.min(safeRight, cssTargetX)); const drawL = Math.min(cssMqStartX, clampedX); const drawW = Math.abs(clampedX - cssMqStartX); mqBox.style.borderTopWidth = (visTop < cssRectTop) ? '0' : '1px'; mqBox.style.borderBottomWidth = (visBot > cssRectBottom) ? '0' : '1px'; mqBox.style.borderLeftWidth = (cssTargetX < cssRectLeft) ? '0' : '1px'; mqBox.style.borderRightWidth = (cssTargetX > safeRight) ? '0' : '1px'; Object.assign(mqBox.style, { display: drawH > 0 ? 'block' : 'none', width: drawW + 'px', height: drawH + 'px', transform: `translate3d(${drawL}px, ${clipT}px, 0)` }); const drawR = drawL + drawW; const logicLeft = Math.max(0, drawL - cssRectLeft); const logicRight = Math.max(logicLeft, drawR - cssRectLeft); const sIdx = Math.floor(logTop / CONF.rowHeight), eIdx = Math.min(S.display.length - 1, Math.floor(logBot / CONF.rowHeight)); if (sIdx !== lastRngS || eIdx !== lastRngE || isGridView()) { lastRngS = sIdx; lastRngE = eIdx; if (!window.event?.ctrlKey && !window.event?.metaKey) { S.selMode = 'explicit'; S.sel.clear(); S.selEx.clear(); } if (isGridView()) { const gridLayout = getGridLayout(); const dupGridMeta = isGroupedGridView() ? getGroupedGridMeta() : null; if (dupGridMeta && dupGridMeta.indexLayout) { dupGridMeta.indexLayout.forEach((layout, itemIdx) => { if (!layout || layout.kind !== 'item') return; const item = S.display[itemIdx]; if (!item || item.isHeader || S.movingIds.has(item.id)) return; const cardLeft = layout.left; const cardRight = cardLeft + layout.width; const cardTop = layout.top; const cardBottom = cardTop + layout.height; if (cardLeft < logicRight && cardRight > logicLeft && cardTop < logBot && cardBottom > logTop) { S.setSelected(item.id, true); } }); } else { const adjustedTop = Math.max(0, logTop - gridLayout.gapY); const adjustedBot = Math.max(adjustedTop, logBot - gridLayout.gapY); const startRow = Math.max(0, Math.floor(adjustedTop / CONF.rowHeight)); const endRow = Math.max(startRow, Math.floor(adjustedBot / CONF.rowHeight)); for (let rowIdx = startRow; rowIdx <= endRow; rowIdx++) { for (let colIdx = 0; colIdx < gridLayout.cols; colIdx++) { const itemIdx = rowIdx * gridLayout.cols + colIdx; if (itemIdx >= S.display.length) break; const item = S.display[itemIdx]; if (!item || item.isHeader || S.movingIds.has(item.id)) continue; const cardLeft = gridLayout.padX + colIdx * (gridLayout.cardWidth + gridLayout.gapX); const cardRight = cardLeft + gridLayout.cardWidth; const cardTop = gridLayout.gapY + rowIdx * CONF.rowHeight; const cardBottom = cardTop + gridLayout.cardHeight; if (cardLeft < logicRight && cardRight > logicLeft && cardTop < logBot && cardBottom > logTop) { S.setSelected(item.id, true); } } } } } else { for (let k = sIdx; k <= eIdx; k++) { const item = S.display[k]; if (item && !item.isHeader && !S.movingIds.has(item.id)) S.setSelected(item.id, true); } } scheduleMarqueeRefresh(); } }; const runAutoScroll = (ts = 0) => { if (!isMarquee || scrollSpeed === 0) { scrollRaf = null; marqueeAutoScrollTs = 0; return; } const dt = marqueeAutoScrollTs ? Math.min(32, ts - marqueeAutoScrollTs) : 16; marqueeAutoScrollTs = ts || performance.now(); const prevTop = UI.vp.scrollTop; const maxTop = Math.max(0, UI.vp.scrollHeight - UI.vp.clientHeight); const nextTop = Math.max(0, Math.min(maxTop, prevTop + (scrollSpeed * (dt / 16)))); if (nextTop === prevTop) { scrollSpeed = 0; scrollRaf = null; marqueeAutoScrollTs = 0; return; } UI.vp.scrollTop = nextTop; updateMarqueeUIAndSelection(lastMouseX, lastMouseY); scrollRaf = requestAnimationFrame(runAutoScroll); }; const handleMarqueeMove = (e) => { if (!cachedVpRect) return; if (!isMarquee) { const moveX = Math.abs(e.clientX - mqStartX); const moveY = Math.abs(e.clientY - mqStartY); if (moveX > 5 || moveY > 5) { isMarquee = true; S.activeId = null; UI.win.classList.add('pk-is-seeking'); refreshVisibleSelectionState(); } else { return; } } lastMouseX = e.clientX; lastMouseY = e.clientY; const logicalClientY = e.clientY; const edgeZone = 28; if (logicalClientY > cachedVpRect.bottom - edgeZone) scrollSpeed = Math.min(26, 2 + Math.pow((logicalClientY - (cachedVpRect.bottom - edgeZone)) / 6, 1.2)); else if (logicalClientY < cachedVpRect.top + edgeZone) scrollSpeed = -Math.min(26, 2 + Math.pow(((cachedVpRect.top + edgeZone) - logicalClientY) / 6, 1.2)); else scrollSpeed = 0; if (scrollSpeed !== 0 && !scrollRaf) scrollRaf = requestAnimationFrame(runAutoScroll); updateMarqueeUIAndSelection(e.clientX, e.clientY); }; const stopMarquee = () => { isMarquee = false; scrollSpeed = 0; marqueeAutoScrollTs = 0; if (UI.win) UI.win.classList.remove('pk-is-seeking'); lastRngS = -1; lastRngE = -1; cachedVpRect = null; if (mqBox) { mqBox.style.display = 'none'; mqBox.style.transform = 'none'; mqBox.style.width = '0px'; mqBox.style.height = '0px'; } if (scrollRaf) cancelAnimationFrame(scrollRaf); if (marqueeRenderRaf) cancelAnimationFrame(marqueeRenderRaf); scrollRaf = null; marqueeRenderRaf = null; window.removeEventListener('mousemove', handleMarqueeMove); window.removeEventListener('mouseup', stopMarquee); }; let isFileDragging = false; let fileDragGhost = null; let dropTargetId = null; let dropTargetType = null; let autoOpenTimer = null; let lastHoverSep = null; const handleFileDragMove = (e) => { if (!isFileDragging) { const moveX = Math.abs(e.clientX - mqStartX); const moveY = Math.abs(e.clientY - mqStartY); if (moveX > 5 || moveY > 5) { isFileDragging = true; document.body.classList.add('pk-dragging'); fileDragGhost = document.createElement('div'); fileDragGhost.className = 'pk-drag-ghost'; if (el.classList.contains('pk-dark')) fileDragGhost.classList.add('pk-dark'); const selectedIds = S.getSelectedIds(); const count = selectedIds.length; const firstId = selectedIds[0]; const firstItem = S.itemMap.get(firstId); let text = firstItem ? firstItem.name : 'Selected Items'; if (count > 1) { text += L.str_drag_files.replace('{n}', count); } else if (!firstItem) { text = L.str_selected_items; } let dragIcon = CONF.typeIcons.folder.replace('30','20').replace('30','20'); if (firstItem && firstItem.icon_link) { dragIcon = ``; } fileDragGhost.innerHTML = `${dragIcon}${esc(text)}`; document.body.appendChild(fileDragGhost); } else { return; } } if (fileDragGhost) { fileDragGhost.style.left = e.clientX + 'px'; fileDragGhost.style.top = e.clientY + 'px'; } const prevTargets = document.querySelectorAll('.pk-drop-target'); prevTargets.forEach(el => el.classList.remove('pk-drop-target')); dropTargetId = null; dropTargetType = null; if (fileDragGhost) fileDragGhost.style.display = 'none'; const elUnder = document.elementFromPoint(e.clientX, e.clientY); if (fileDragGhost) fileDragGhost.style.display = 'flex'; if (!elUnder) return; const menuItem = elUnder.closest('.pk-crumb-item'); if (menuItem && menuItem.dataset.id) { const tid = menuItem.dataset.id; const currentFolderId = S.path[S.path.length - 1]?.id || ''; if (tid !== currentFolderId && tid !== 'loading' && tid !== 'error') { dropTargetId = tid; dropTargetType = 'menu_item'; menuItem.classList.add('pk-drop-target'); return; } } const sep = elUnder.closest('.pk-crumb-sep'); if (sep) { if (lastHoverSep !== sep) { lastHoverSep = sep; if (autoOpenTimer) clearTimeout(autoOpenTimer); autoOpenTimer = setTimeout(() => { if (isFileDragging) sep.click(); }, 500); } } else { lastHoverSep = null; if (autoOpenTimer) { clearTimeout(autoOpenTimer); autoOpenTimer = null; } } const row = elUnder.closest('.pk-row'); if (row && row.dataset.id) { const tid = row.dataset.id; const item = S.itemMap.get(tid); if (item && item.kind === 'drive#folder' && !S.isSelected(tid)) { dropTargetId = tid; dropTargetType = 'row'; row.classList.add('pk-drop-target'); return; } } const crumbItem = elUnder.closest('#pk-crumb span'); if (crumbItem && !crumbItem.classList.contains('pk-crumb-sep') && crumbItem.dataset.id) { const tid = crumbItem.dataset.id === 'root' ? '' : crumbItem.dataset.id; const currentFolderId = S.path[S.path.length - 1]?.id || ''; const normalize = (id) => (!id || id === 'root') ? 'root' : id; if (normalize(tid) !== normalize(currentFolderId)) { dropTargetId = tid; dropTargetType = 'crumb'; crumbItem.classList.add('pk-drop-target'); } } }; const executeFileTransfer = async (items, opType, sourcePid, targetPid, targetNameForLog) => { if (!items || items.length === 0) return; if (sourcePid !== '__VIRTUAL__' && sourcePid === targetPid) { showToast(L.err_paste_descendant, 'error'); return; } const foldersToMoveIds = items.filter(it => it.kind === 'drive#folder').map(it => it.id); if (foldersToMoveIds.length > 0) { const isDescendant = (targetId, ancestorIds) => { let currentId = targetId; let safety = 100; while (currentId && currentId !== 'root' && safety > 0) { if (ancestorIds.includes(currentId)) return true; const parent = globalParentIndex.get(currentId); currentId = parent ? parent.id : null; safety--; } return false; }; if (isDescendant(targetPid, foldersToMoveIds)) { showToast(L.err_paste_descendant, 'error'); return; } } S.movingSourceId = sourcePid || 'root'; S.movingDestId = targetPid || 'root'; const allIds = items.map(it => it.id); allIds.forEach(id => S.movingIds.add(id)); if (S.broadcast) S.broadcast.postMessage({ type: 'LOCK_ADD', ids: allIds, src: S.movingSourceId, dst: S.movingDestId }); if (typeof updateGlobalLockCSS === 'function') updateGlobalLockCSS(); isGUISensitive = true; const progressTask = FloatBarManager.create(`${opType === 'move' ? L.str_moving : L.str_copying}...`); const updateFloat = progressTask.update; const BATCH_SIZE = 500; let processedCount = 0; const total = items.length; const endpoint = opType === 'move' ? 'files:batchMove' : 'files:batchCopy'; const actionText = opType === 'move' ? L.str_moving : L.str_copying; refresh(); try { for (let i = 0; i < total; i += BATCH_SIZE) { const chunk = items.slice(i, i + BATCH_SIZE); const chunkIds = chunk.map(it => it.id); updateFloat(`${actionText} ${processedCount}/${total} -> ${esc(targetNameForLog || L.str_target_folder)}`); const apiTargetPid = (targetPid === 'root') ? '' : targetPid; const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/${endpoint}`, { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: chunkIds, to: { parent_id: apiTargetPid } }) }); const data = await res.json().catch(() => ({})); if (!res.ok) throw new Error(data.error_description || `API ${res.status}`); if (data.task_id) { await new Promise(resolve => { const checkTask = async () => { try { const tRes = await fetch(`https://api-drive.mypikpak.com/drive/v1/tasks/${data.task_id}`, { headers: getHeaders() }); const tData = await tRes.json(); if (tData.phase === 'PHASE_TYPE_COMPLETE' || tData.phase === 'PHASE_TYPE_ERROR') resolve(); else setTimeout(checkTask, 800); } catch { resolve(); } }; checkTask(); }); } else if (opType === 'move' && chunkIds.length > 0) { const lastId = chunkIds[chunkIds.length - 1]; let retry = 0; while (retry < 20) { try { const meta = await apiGet(lastId); if (meta.parent_id === targetPid || meta.trashed) break; } catch(e) { break; } await sleep(500); retry++; } } chunkIds.forEach(id => S.movingIds.delete(id)); if (S.broadcast) S.broadcast.postMessage({ type: 'LOCK_REM', ids: chunkIds }); if (opType === 'move') { const movedSet = new Set(chunkIds); S.items = S.items.filter(it => !movedSet.has(it.id)); if (typeof globalCache !== 'undefined') { const cleanListChunk = (raw) => { if (Array.isArray(raw)) return raw.filter(f => !movedSet.has(f.id)); if (raw && Array.isArray(raw.items)) { raw.items = raw.items.filter(f => !movedSet.has(f.id)); return raw; } return raw; }; for (const key of globalCache.keys()) { globalCache.set(key, cleanListChunk(globalCache.get(key))); } for (const key of S.cache.keys()) { S.cache.set(key, cleanListChunk(S.cache.get(key))); } } if (typeof pkState !== 'undefined' && pkState && pkState.lastGlobalResults) { pkState.lastGlobalResults = pkState.lastGlobalResults.filter(x => !movedSet.has(x.id)); } } else { if (targetPid === (S.path[S.path.length-1].id||'')) { const newItems = (data.files || []).map(f => minifyFile(f, false)); S.items.unshift(...newItems); } } if (typeof updateGlobalLockCSS === 'function') updateGlobalLockCSS(); refresh(); processedCount += chunk.length; } if (targetPid) gmSet('pk_fmod_' + targetPid, new Date(getServerNow()).toISOString()); if (S.analyzeMap) { const deltaSize = items.reduce((acc, it) => acc + parseInt(it.size || 0), 0); if (deltaSize > 0) { const updateAnalyzeChain = (startId, isAdd) => { let currId = startId; let safety = 50; while (currId && S.analyzeMap.has(currId) && safety > 0) { const node = S.analyzeMap.get(currId); const oldSize = parseInt(node.size || 0); node.size = isAdd ? (oldSize + deltaSize) : Math.max(0, oldSize - deltaSize); currId = node.parentId; safety--; } }; if (S.analyzeMap.has(targetPid)) updateAnalyzeChain(targetPid, true); if (opType === 'move' && S.analyzeMap.has(sourcePid)) updateAnalyzeChain(sourcePid, false); if (S.analyzeResultItems) { S.analyzeResultItems.forEach(resItem => { if (S.analyzeMap.has(resItem.id)) { resItem.size = S.analyzeMap.get(resItem.id).size.toString(); } }); } } } const keysToClear = [sourcePid || 'root', targetPid || 'root']; keysToClear.forEach(k => { const normalizedK = (k === '' || k === 'root') ? 'root' : k; const altK = (normalizedK === 'root') ? '' : normalizedK; S.cache.delete(normalizedK); S.cache.delete(altK); if (typeof globalCache !== 'undefined') { globalCache.delete(normalizedK); globalCache.delete(altK); if (typeof scannedFolderIds !== 'undefined') { scannedFolderIds.delete(altK); } } if (typeof globalDirtyFolders !== 'undefined') { globalDirtyFolders.add(altK); } }); if (opType === 'move') { const purgeMovedDescendants = (fid) => { if (typeof globalLineageMap !== 'undefined') globalLineageMap.delete(fid); if (typeof scannedFolderIds !== 'undefined') scannedFolderIds.delete(fid); const data = (typeof globalCache !== 'undefined' ? globalCache.get(fid) : null) || (S.cache ? S.cache.get(fid) : null); if (data) { const list = Array.isArray(data) ? data : (data.items || []); list.forEach(child => { if (child.kind === 'drive#folder') { purgeMovedDescendants(child.id); } }); if (typeof globalCache !== 'undefined') globalCache.delete(fid); if (S.cache) S.cache.delete(fid); } }; items.forEach(it => { if (it.kind === 'drive#folder') purgeMovedDescendants(it.id); }); } if (typeof globalNeedsSync !== 'undefined') globalNeedsSync = true; if (typeof runBackgroundCrawler === 'function') runBackgroundCrawler(); if (window.pkSmartRefreshTrigger) window.pkSmartRefreshTrigger(true); showToast(opType === 'move' ? L.msg_move_done : L.msg_copy_success); } catch (e) { showToast(`${L.str_error}: ${e.message}`, 'error'); load(false, true); } finally { S.movingIds.clear(); S.movingSourceId = null; S.movingDestId = null; if (S.broadcast) S.broadcast.postMessage({ type: 'LOCK_CLR', ids: [] }); if (typeof updateGlobalLockCSS === 'function') updateGlobalLockCSS(); isGUISensitive = false; if (progressTask) progressTask.destroy(); updateStat(); } }; const stopFileDrag = async () => { if (autoOpenTimer) { clearTimeout(autoOpenTimer); autoOpenTimer = null; } lastHoverSep = null; window.removeEventListener('mousemove', handleFileDragMove); window.removeEventListener('mouseup', stopFileDrag); document.body.classList.remove('pk-dragging'); if (fileDragGhost) fileDragGhost.remove(); fileDragGhost = null; let targetName = L.lbl_type_folder; if (dropTargetType === 'menu_item') { const activeItem = document.querySelector('.pk-crumb-item.pk-drop-target span'); if (activeItem) targetName = activeItem.textContent; } else if (dropTargetType === 'row') { targetName = S.itemMap.get(dropTargetId)?.name || L.lbl_type_folder; } else if (dropTargetType === 'crumb') { const pathNode = S.path.find(p => (p.id || 'root') === (dropTargetId || 'root')); targetName = pathNode ? pathNode.name : L.str_target_folder; } const targets = document.querySelectorAll('.pk-drop-target'); targets.forEach(t => t.classList.remove('pk-drop-target')); document.querySelectorAll('.pk-crumb-pop').forEach(pop => { if (typeof pop._cleanup === 'function') pop._cleanup(); else pop.remove(); }); UI.crumb.querySelectorAll('.pk-crumb-sep').forEach(sep => { sep.classList.remove('pk-active'); sep.innerHTML = CONF.crumbIcons.right; }); if (!isFileDragging || dropTargetId === null) { isFileDragging = false; return; } isFileDragging = false; const currentFolderId = S.path[S.path.length - 1]?.id || ''; const normalize = (id) => (!id || id === 'root') ? 'root' : id; if (normalize(dropTargetId) === normalize(currentFolderId)) return; let hasSystem = false; const dragSelectedIds = S.getSelectedIds(); for (const id of dragSelectedIds) { const item = S.itemMap.get(id); if (item && isSystemItem(item)) { hasSystem = true; break; } } if (hasSystem) { showToast(`${L.msg_sys_error}`, 'error'); return; } const rawItems = []; const validItems = []; let skipLocked = 0; let skipSelf = 0; let skipConflict = 0; const isDescendantConflict = (candidateId) => { if (!S.movingIds || S.movingIds.size === 0) return false; const hotspots = [S.movingSourceId, S.movingDestId].filter(x => x && x !== 'root'); if (hotspots.length === 0) return false; for (const startPoint of hotspots) { let curr = startPoint; let safety = 50; while (curr && curr !== 'root' && safety > 0) { if (curr === candidateId) return true; const parentInfo = globalParentIndex.get(curr); curr = parentInfo ? parentInfo.id : null; safety--; } } return false; }; dragSelectedIds.forEach(id => { const item = S.itemMap.get(id); if (!item) return; rawItems.push(item); if (S.movingIds.has(id)) { skipLocked++; return; } const targetId = normalize(dropTargetId); const selfId = normalize(item.id); const parentId = normalize(item.parent_id); if (targetId === selfId || targetId === parentId) { skipSelf++; return; } if (item.kind === 'drive#folder' && isDescendantConflict(item.id)) { skipConflict++; return; } validItems.push(item); }); if (skipLocked > 0 || skipSelf > 0 || skipConflict > 0) { const reasons = []; if (skipLocked) reasons.push(L.msg_skip_locked.replace('{n}', skipLocked)); if (skipSelf) reasons.push(L.msg_skip_self.replace('{n}', skipSelf)); if (skipConflict) reasons.push(L.msg_skip_conflict.replace('{n}', skipConflict)); showToast(`${L.msg_skip_invalid} ${reasons.join(', ')}`, 'warning'); } S.clearSelection(); refresh(); if (validItems.length > 0) { await executeFileTransfer( validItems, 'move', normalize(currentFolderId), normalize(dropTargetId), targetName ); } }; UI.vp.addEventListener('mousedown', (e) => { if (e.button !== 0 || e.detail > 1) return; if (e.target.closest('.pk-btn, .pk-star-icon, input')) return; const row = e.target.closest('.pk-row'); const isClickingSelected = row && row.dataset.id && S.isSelected(row.dataset.id); if (isClickingSelected && !S.trashMode && !S.shareMode && !S.shareParseMode && !S.offlineMode && !S.historyMode && !S.starredMode && !S.recentMode && !S.isFlattened && !S.dupMode && !S.analyzeMode) { isFileDragging = false; mqStartX = e.clientX; mqStartY = e.clientY; window.addEventListener('mousemove', handleFileDragMove); window.addEventListener('mouseup', stopFileDrag); return; } if (S.shareParseMode && !S.shareParseListActive) return; cachedVpRect = getLogicalRect(UI.vp); const safeRight = cachedVpRect.left + UI.vp.clientWidth; const safeBottom = cachedVpRect.top + UI.vp.clientHeight; const logicalClientX = e.clientX; const logicalClientY = e.clientY; if (logicalClientX > safeRight || logicalClientY > safeBottom) return; let startItem = null; if (isGridView()) { const gridLayout = getGridLayout(); const logicalOffsetX = logicalClientX - cachedVpRect.left; const logicalOffsetY = logicalClientY - cachedVpRect.top + UI.vp.scrollTop; let startIdx = -1; if (isGroupedGridView()) { startIdx = findDupGridHitIndex(logicalOffsetX, logicalOffsetY); } else { const totalRows = Math.max(1, Math.ceil(S.display.length / Math.max(1, gridLayout.cols))); if (logicalOffsetX >= gridLayout.padX && logicalOffsetY >= 0 && logicalOffsetY < totalRows * CONF.rowHeight) { const colStride = gridLayout.cardWidth + gridLayout.gapX; const relX = logicalOffsetX - gridLayout.padX; const rowIdx = Math.floor(logicalOffsetY / CONF.rowHeight); const colIdx = Math.floor(relX / Math.max(1, colStride)); const withinColX = relX - colIdx * colStride; const withinRowY = logicalOffsetY - rowIdx * CONF.rowHeight; if (colIdx >= 0 && colIdx < gridLayout.cols && withinColX >= 0 && withinColX <= gridLayout.cardWidth && withinRowY >= 0 && withinRowY <= gridLayout.cardHeight) { startIdx = rowIdx * gridLayout.cols + colIdx; } } } if (startIdx >= 0 && startIdx < S.display.length) { const hitItem = S.display[startIdx]; startItem = (hitItem && !hitItem.isHeader) ? hitItem : null; } } else { const startOffsetY = logicalClientY - cachedVpRect.top + UI.vp.scrollTop; const startIdx = Math.floor(startOffsetY / CONF.rowHeight); if (startIdx >= 0 && startIdx < S.display.length) { const hitItem = S.display[startIdx]; startItem = (hitItem && !hitItem.isHeader) ? hitItem : null; } } mqStartX = e.clientX; mqStartY = e.clientY; const prevActiveId = S.activeId; S.activeId = startItem ? startItem.id : null; if (!startItem && prevActiveId && S.getSelectedCount() === 0) { renderVisible(); requestAnimationFrame(() => { if (UI.chkAll) UI.chkAll.checked = false; }); } isMarquee = false; mqStartX = lastMouseX = e.clientX; mqStartY = lastMouseY = e.clientY; startScroll = UI.vp.scrollTop; window.addEventListener('mousemove', handleMarqueeMove); window.addEventListener('mouseup', stopMarquee); }); let isScrollScheduled = false; let gridScrollEndTimer = 0; const markGridScrolling = () => { if (!isGridView() || !UI.win) return; UI.win.classList.add('pk-grid-scrolling'); if (gridScrollEndTimer) clearTimeout(gridScrollEndTimer); gridScrollEndTimer = setTimeout(() => { gridScrollEndTimer = 0; if (UI.win) UI.win.classList.remove('pk-grid-scrolling'); }, 90); }; const tryLoadRecentRootNextPage = async () => { if (!S.recentMode || S.path.length !== 1) return; const cur = S.path[S.path.length - 1]; if (!cur || cur.id !== 'recent_root') return; if (S.loading || S.scanning || S.recentLoadingNextPage || S.recentRefreshFirstPageOnly) return; if (!UI.vp || UI.vp.scrollHeight <= UI.vp.clientHeight + 20) return; if (UI.vp.scrollTop + UI.vp.clientHeight < UI.vp.scrollHeight - 300) return; const session = (typeof globalCache !== 'undefined') ? globalCache.get('recent_session') : null; const nextToken = session && session.nextToken && !session.completed ? session.nextToken : null; if (!nextToken) return; const tempCache = { items: [...S.items], nextToken }; S.cache.set('recent_root', tempCache); if (typeof globalCache !== 'undefined') { globalCache.set('recent_root', tempCache); globalCache.set('recent_session', { phase: 'active', nextToken, completed: false, count: S.items.length }); } S.recentLoadingNextPage = true; S.recentLoadNextPageOnly = true; S.pagingLoading = true; updateStat(); try { await load(false, false); } catch (e) { if (!e || e.name !== 'AbortError') console.warn('[Recent] Failed to load next page:', e); } finally { S.recentLoadNextPageOnly = false; S.recentLoadingNextPage = false; S.pagingLoading = false; updateStat(); } }; const isDupGridScrollRafMode = () => !!(UI.vp && isGridView() && isGroupedGridView()); const flushDupGridScrollRender = (force = false) => { S.dupGridScrollRaf = 0; if (!isDupGridScrollRafMode()) { S.dupGridScrollDirty = false; return false; } const latestTop = UI.vp.scrollTop; const shouldRender = force || S.dupGridScrollDirty || Math.abs(latestTop - S.dupGridLastRenderedTop) >= 1; S.dupGridScrollDirty = false; S.dupGridLastScrollTop = latestTop; if (shouldRender) { S.dupGridLastRenderedTop = latestTop; renderVisible(); } if (typeof isMarquee !== 'undefined' && isMarquee && !scrollRaf) { updateMarqueeUIAndSelection(lastMouseX, lastMouseY); } return shouldRender; }; const scheduleDupGridScrollRender = () => { if (!isDupGridScrollRafMode()) return false; S.dupGridScrollDirty = true; S.dupGridLastScrollTop = UI.vp.scrollTop; if (!S.dupGridScrollRaf) { S.dupGridScrollRaf = requestAnimationFrame(() => flushDupGridScrollRender(false)); } if (S.dupGridScrollEndTimer) clearTimeout(S.dupGridScrollEndTimer); S.dupGridScrollEndTimer = setTimeout(() => { S.dupGridScrollEndTimer = 0; if (!isDupGridScrollRafMode()) return; S.dupGridScrollDirty = true; if (!S.dupGridScrollRaf) { S.dupGridScrollRaf = requestAnimationFrame(() => flushDupGridScrollRender(true)); } }, 90); return true; }; UI.vp.onscroll = () => { if (UI.ctx && UI.ctx.style.display !== 'none') UI.ctx.style.display = 'none'; markGridScrolling(); if (scheduleDupGridScrollRender()) { tryLoadRecentRootNextPage(); return; } if (!isScrollScheduled) { requestAnimationFrame(() => { renderVisible(); if (typeof isMarquee !== 'undefined' && isMarquee && !scrollRaf) { updateMarqueeUIAndSelection(lastMouseX, lastMouseY); } isScrollScheduled = false; }); isScrollScheduled = true; } tryLoadRecentRootNextPage(); }; UI.vp.addEventListener('wheel', () => { if (UI.ctx && UI.ctx.style.display !== 'none') { UI.ctx.style.display = 'none'; } markGridScrolling(); scheduleDupGridScrollRender(); }, { capture: true, passive: true }); const handleBlankClick = async (e) => { if (UI.ctx && UI.ctx.style.display !== 'none') { UI.ctx.style.display = 'none'; } if (e.target.closest('.pk-nav-btn') || e.target.closest('.pk-btn') || e.target.closest('.pk-btn-toggle') || e.target.closest('input') || e.target.closest('textarea') || e.target.closest('select') || e.target.closest('.pk-nav span') || e.target.closest('.pk-crumb-sep') || e.target.closest('.pk-search') || e.target.closest('label') || e.target.closest('.pk-dropdown-wrap') || e.target.closest('.pk-sort-opt') || e.target.closest('a') ) return; const selectedCount = S.getSelectedCount(); if (selectedCount > 0 || S.activeId) { const isAnalyzeRoot = S.analyzeMode && S.path[S.path.length - 1].id === 'analyze_root'; if ((S.dupMode || (isAnalyzeRoot && S.analyzeSimGroups)) && selectedCount > 5) { if (!await confirmSelectionClear()) return; } S.clearSelection(); S.activeId = null; renderVisible(); requestAnimationFrame(() => { if (UI.chkAll) UI.chkAll.checked = false; }); } }; const sidebarArea = el.querySelector('.pk-sidebar'); if (sidebarArea) sidebarArea.onclick = handleBlankClick; const footerArea = el.querySelector('.pk-ft'); if (footerArea) footerArea.onclick = handleBlankClick; const headerArea = el.querySelector('.pk-hd'); if (headerArea) headerArea.onclick = handleBlankClick; const topBarArea = el.querySelector('#pk-top-bar'); if (topBarArea) topBarArea.onclick = handleBlankClick; const actionArea = el.querySelector('#pk-actionbar'); if (actionArea) actionArea.onclick = handleBlankClick; const trashArea = el.querySelector('#pk-trash-bar'); if (trashArea) trashArea.onclick = handleBlankClick; el.addEventListener('click', (e) => { if (!S.activeId) return; if (e.target.closest('.pk-row')) return; if (e.target.closest('#pk-theme')) return; if (e.target.closest('#pk-view-switch') || e.target.closest('.pk-view-switch') || e.target.closest('#pk-view-list') || e.target.closest('#pk-view-grid') || e.target.closest('.pk-view-btn')) return; S.activeId = null; renderVisible(); }, { capture: true }); if (UI.vp) { UI.vp.addEventListener('click', (e) => { if (e.target.closest('.pk-row')) return; const rect = getLogicalRect(UI.vp); if (e.clientX > rect.left + UI.vp.clientWidth || e.clientY > rect.top + UI.vp.clientHeight) return; if (Math.abs(e.clientX - mqStartX) > 5 || Math.abs(e.clientY - mqStartY) > 5) return; handleBlankClick(e); }); } const showCrumbDropdown = async (e, parentId, triggerEl) => { const getLiveTriggerEl = () => { if (triggerEl && triggerEl.isConnected) return triggerEl; if (!UI.crumb) return triggerEl; const idx = S.path.findIndex(p => p.id === parentId); const seps = UI.crumb.querySelectorAll('.pk-crumb-sep'); return (idx >= 0 && seps[idx]) ? seps[idx] : triggerEl; }; const activeTriggerEl = getLiveTriggerEl(); if (activeTriggerEl && activeTriggerEl.classList.contains('pk-active')) { const oldPop = document.getElementById('pk-main-crumb-pop'); if (oldPop) oldPop.remove(); activeTriggerEl.classList.remove('pk-active'); activeTriggerEl.innerHTML = CONF.crumbIcons.right; return; } let targetId = parentId || 'root'; if (targetId === 'root' || targetId === '') { if (S.shareMode) targetId = 'share_root'; else if (S.starredMode) targetId = 'starred_root'; else targetId = 'root'; } let folders = []; if (targetId === 'virtual_search_root' || targetId === 'analyze_root' || targetId === 'recent_root') { let sourceItems = []; const isAnalyzeSimMode = targetId === 'analyze_root' && S.analyzeSimGroups; if (isAnalyzeSimMode) { sourceItems = (S.analyzeResultItems && S.analyzeResultItems.length > 0) ? S.analyzeResultItems : S.display.filter(item => !item.isHeader); } else if (targetId === 'analyze_root') { sourceItems = (S.analyzeResultItems && S.analyzeResultItems.length > 0) ? S.analyzeResultItems : S.items; } else if (targetId === 'recent_root') { if (S.recentResultItems && S.recentResultItems.length > 0) { sourceItems = S.recentResultItems; } else if (typeof globalCache !== 'undefined' && globalCache.has('recent_root')) { const cached = globalCache.get('recent_root'); sourceItems = Array.isArray(cached) ? cached : (cached.items || []); } else { sourceItems = S.items || []; } } else { sourceItems = (S.lastGlobalResults && S.lastGlobalResults.length > 0) ? S.lastGlobalResults : S.items; } folders = sourceItems.filter(f => f.kind === 'drive#folder'); if (folders.length === 0) { folders = [{ id: 'error', name: L.str_no_files, kind: 'drive#folder', _empty: true }]; } } else { const listId = targetId === 'root' ? '' : targetId; const cacheKey = targetId === 'root' ? 'root' : targetId; const isDirty = typeof globalDirtyFolders !== 'undefined' && globalDirtyFolders.has(listId); const rawCached = !isDirty ? ((typeof globalCache !== 'undefined' && globalCache.has(cacheKey)) ? globalCache.get(cacheKey) : S.cache.get(cacheKey)) : null; const cachedItems = (rawCached && !Array.isArray(rawCached) && rawCached.items) ? rawCached.items : (Array.isArray(rawCached) ? rawCached : null); const cachedFolders = cachedItems ? cachedItems.filter(f => f.kind === 'drive#folder') : null; if (cachedFolders && cachedFolders.length > 0) { folders = cachedFolders; } else { folders = [{ id: 'loading', name: L.loading, kind: 'drive#folder' }]; apiList(listId, 500, null, null, false, true).then(res => { if (typeof globalCache !== 'undefined') globalCache.set(cacheKey, res); if (S.cache) S.cache.set(cacheKey, res); if (typeof globalDirtyFolders !== 'undefined') globalDirtyFolders.delete(listId); const freshFolders = Array.isArray(res) ? res.filter(f => f.kind === 'drive#folder') : []; const pop = document.getElementById('pk-main-crumb-pop'); if (!pop || pop.dataset.targetId !== targetId) return; renderMenu(freshFolders.length > 0 ? freshFolders : [{ id: 'error', name: L.str_no_files, kind: 'drive#folder', _empty: true }]); }).catch(() => { const pop = document.getElementById('pk-main-crumb-pop'); if (pop && pop.dataset.targetId === targetId) { renderMenu([{ id: 'error', name: L.str_load_failed, kind: 'drive#folder' }]); } }); } } const renderMenu = (list) => { let s = S.sort, d = S.dir; const resolvedSort = (typeof resolvePreferredSortState === 'function') ? resolvePreferredSortState(targetId || null, { allowMedia: false }) : null; if (resolvedSort) { s = resolvedSort.sort; d = resolvedSort.dir; } const isAnalyzeSimMode = S.analyzeMode && targetId === 'analyze_root' && S.analyzeSimGroups; if (!isAnalyzeSimMode) { const collator = new Intl.Collator(({ 'zh': 'zh-CN', 'tc': 'zh-TW', 'ja': 'ja', 'ko': 'ko' })[getLang()] || 'en', { numeric: true, sensitivity: 'base' }); const parseSize = n => parseInt(n || 0); const parseTime = t => t ? new Date(t).getTime() : 0; const getCharWeight = (str) => { if (!str) return 0; const c = str.charAt(0); if (/[0-9]/.test(c)) return 20; if (/[\u4e00-\u9fa5]/.test(c)) return 30; if (/[a-zA-Z]/.test(c)) return 40; return 10; }; const compareNames = (nameA, nameB) => { const rankA = getCharWeight(nameA); const rankB = getCharWeight(nameB); if (rankA !== rankB) return rankA - rankB; return collator.compare(nameA, nameB); }; list.sort((a, b) => { const isSysA = a.name === CONF.SYSTEM_FOLDER_NAME && (!a.parent_id || a.parent_id === '' || a.parent_id === 'root'); const isSysB = b.name === CONF.SYSTEM_FOLDER_NAME && (!b.parent_id || b.parent_id === '' || b.parent_id === 'root'); if (isSysA !== isSysB) return isSysA ? -1 : 1; if (s === 'play_time') { const tA = a._history_ts || 0; const tB = b._history_ts || 0; if (tA !== tB) return (tB - tA) * d; } else if (s === 'modified_time') { const tA = parseTime(a.modified_time); const tB = parseTime(b.modified_time); if (tA !== tB) return (tB - tA) * d; } else if (s === 'created_time') { const tA = parseTime(a.created_time); const tB = parseTime(b.created_time); if (tA !== tB) return (tB - tA) * d; } else if (s === 'size') { const sizeA = parseSize(a.size); const sizeB = parseSize(b.size); if (sizeA !== sizeB) return (sizeB - sizeA) * d; } else if (s === 'play_time') { const ptA = a._history_ts || 0; const ptB = b._history_ts || 0; if (ptA !== ptB) return (ptB - ptA) * d; } else if (s === 'view_count' || s === 'save_count') { const valA = s === 'view_count' ? parseSize(a.view_count) : parseSize(a.save_count); const valB = s === 'view_count' ? parseSize(b.view_count) : parseSize(b.save_count); if (valA !== valB) return (valB - valA) * d; } else if (s === 'starred') { const isItemStarred = (item) => S.starredSet.has(item.id) || !!(item.starred || (item.tags && item.tags.some(t => t.name === 'STAR'))); const stA = isItemStarred(a) ? 1 : 0; const stB = isItemStarred(b) ? 1 : 0; if (stA !== stB) return stB - stA; const tA = parseTime(a.modified_time); const tB = parseTime(b.modified_time); return (tB - tA) * d; } else if (s === 'path') { const getPath = (item) => { if (S.analyzeMode) return item._pathStr || ""; if (item._lineage) return item._lineage.map(x => x.name).join('/'); return ""; }; const pA = getPath(a); const pB = getPath(b); const pathCmp = compareNames(pA, pB); if (pathCmp !== 0) return pathCmp * d; } return compareNames(a.name, b.name) * d; }); } let pop = document.getElementById('pk-main-crumb-pop'); if (!pop) { pop = document.createElement('div'); pop.id = 'pk-main-crumb-pop'; pop.className = 'pk-crumb-pop pk-scroll'; if (document.querySelector('.pk-ov')?.classList.contains('pk-dark')) pop.classList.add('pk-dark'); pop.style.top = '-9999px'; document.body.appendChild(pop); } pop.dataset.targetId = targetId; pop.onwheel = (evt) => { evt.stopPropagation(); }; pop.innerHTML = ''; const movingIds = (typeof pkState !== 'undefined') ? pkState.movingIds : new Set(); list.forEach(f => { const item = document.createElement('div'); const isMoving = movingIds.has(f.id); item.className = 'pk-crumb-item' + (isMoving ? ' pk-moving' : ''); item.dataset.id = f.id; if (f._empty) { item.className = 'pk-crumb-item pk-crumb-empty'; item.innerHTML = `${esc(f.name)}`; pop.appendChild(item); return; } const iconSrc = f.icon_link || ''; const fallbackSvg = CONF.typeIcons.folder.replace(/width="\d+"/, 'width="18"').replace(/height="\d+"/, 'height="18"'); const iconHtml = iconSrc ? `${fallbackSvg}` : fallbackSvg; const isInPath = S.path.some(pathNode => pathNode.id === f.id); const textStyle = isInPath ? 'font-weight:bold; color:var(--pk-pri);' : ''; const isProtected = typeof isSystemItem === 'function' && isSystemItem(f); const tagHtml = isProtected ? `${L.tag_default}` : ''; item.innerHTML = `${iconHtml}${esc(f.name)}${tagHtml}`; if (f.id !== 'loading' && f.id !== 'error') { item.onclick = (ev) => { ev.stopPropagation(); closeMenu(); if (targetId === 'virtual_search_root' || targetId === 'analyze_root' || targetId === 'recent_root') { let rootName = L.str_search_results; if (targetId === 'analyze_root') rootName = L.str_analyze_results; else if (targetId === 'recent_root') rootName = L.btn_nav_recent; const newPath = []; if (targetId === 'virtual_search_root') { newPath.push({ id: '', name: L.btn_nav_home }); } newPath.push({ id: targetId, name: rootName }); newPath.push({ id: f.id, name: f.name }); S.path = newPath; } else { const idx = S.path.findIndex(p => p.id === parentId); if (idx !== -1) { S.path = S.path.slice(0, idx + 1); S.path.push({ id: f.id, name: f.name }); } } load(); }; } pop.appendChild(item); }); requestAnimationFrame(() => { const liveTriggerEl = getLiveTriggerEl(); if (!liveTriggerEl || !liveTriggerEl.isConnected) return; const rect = getLogicalRect(liveTriggerEl); const popRect = pop.getBoundingClientRect(); let left = rect.left - 4; const popW = popRect.width; const winW = window.innerWidth; if (left + popW > winW) left = winW - popW - 15; pop.style.left = Math.max(10, left) + 'px'; pop.style.top = (rect.bottom + 6) + 'px'; pop.classList.add('pk-show'); liveTriggerEl.classList.add('pk-active'); liveTriggerEl.innerHTML = CONF.crumbIcons.down; }); const closeMenu = () => { if (pop.parentNode) pop.remove(); const liveTriggerEl = getLiveTriggerEl(); if (liveTriggerEl) { liveTriggerEl.classList.remove('pk-active'); liveTriggerEl.innerHTML = CONF.crumbIcons.right; const svg = liveTriggerEl.querySelector('svg'); if (svg) { svg.style.width = '14px'; svg.style.height = '14px'; svg.style.display = 'block'; svg.style.opacity = '0.6'; } } document.removeEventListener('mousedown', onOutsideClick); document.removeEventListener('wheel', onOutsideWheel, true); window.removeEventListener('resize', closeMenu); UI.vp.removeEventListener('scroll', closeMenu); }; const onOutsideClick = (evt) => { const liveTriggerEl = getLiveTriggerEl(); if (!pop.contains(evt.target) && !(liveTriggerEl && liveTriggerEl.contains(evt.target))) closeMenu(); }; const onOutsideWheel = (evt) => { const liveTriggerEl = getLiveTriggerEl(); if (pop.contains(evt.target) || (liveTriggerEl && liveTriggerEl.contains(evt.target))) return; closeMenu(); }; document.addEventListener('wheel', onOutsideWheel, { capture: true, passive: true }); window.addEventListener('resize', closeMenu); UI.vp.addEventListener('scroll', closeMenu, { passive: true }); setTimeout(() => document.addEventListener('mousedown', onOutsideClick), 10); }; renderMenu(folders || []); }; function renderCrumb() { const oldMainCrumbPop = document.getElementById('pk-main-crumb-pop'); if (oldMainCrumbPop) oldMainCrumbPop.remove(); UI.crumb.innerHTML = ''; UI.crumb.style.display = ''; const pathModeTipFontSize = '13px'; if (S.offlineMode) { UI.crumb.style.pointerEvents = 'none'; UI.crumb.innerHTML = `
${L.title_offline}
`; return; } if (S.uploadMode) { UI.crumb.style.pointerEvents = 'none'; UI.crumb.innerHTML = `
${L.btn_nav_upload}
`; return; } if (S.shareMode) { UI.crumb.style.pointerEvents = 'none'; UI.crumb.innerHTML = `
${L.btn_nav_share}
`; return; } if (S.shareParseMode) { if (!S.shareParseListActive) { UI.crumb.style.pointerEvents = 'none'; UI.crumb.innerHTML = `
${L.title_share_parse} ${L.desc_share_parse_space_notice}
`; return; } if (S.shareParseInsightMode) { UI.crumb.style.pointerEvents = 'none'; UI.crumb.style.display = 'none'; UI.crumb.innerHTML = ''; return; } UI.crumb.style.pointerEvents = 'auto'; const path = (Array.isArray(S.shareParsePath) && S.shareParsePath.length) ? S.shareParsePath : buildShareParseRootPath(); const hasCurrentShareParseFolder = Array.isArray(S.shareParseItems) && S.shareParseItems.some(item => item && item.kind === 'drive#folder'); const resolveShareParseCrumbFolders = async (crumbIndex) => { const basePath = path.slice(0, Math.max(1, crumbIndex + 1)); if (crumbIndex <= 0) { const root = path[1]; if (!root) return []; const rootParentId = getShareParseRootParentId(); const rootPath = path.slice(0, 1); const makeRootFolder = (raw, idx = 0) => { const rawKind = String(raw && raw.kind || '').toLowerCase(); const mime = raw && (raw.mime_type || raw.mimeType || raw.content_type || ''); const isFolder = raw && (raw.kind === 'drive#folder' || rawKind === 'folder' || rawKind.includes('folder') || mime === 'application/x-directory'); if (!isFolder) return null; const id = String((raw.id || raw.file_id || raw.fileId || raw.resource_id || raw.resourceId || `share_${rootParentId || 'root'}_${idx}`)); const name = raw.name || raw.file_name || raw.title || ''; if (String(id || '') !== String(root.id || '') && String(name || '') !== String(root.name || '')) return null; const displayName = L.picker_all || L.label_share_parse_root; return { ...raw, id, name: displayName, kind: 'drive#folder', parent_id: rootParentId, icon_link: raw.icon_link || raw.iconLink || raw.icon || root.icon_link || root.iconLink || root.icon || '', _isShareItem: true, _shareRoot: true, _sharePath: rootPath.concat([{ id, name: displayName, parent_id: rootParentId, _shareRoot: true }]) }; }; const cached = (Array.isArray(S.shareParseItems) && String(S.shareParseCurParentId || '') === String(rootParentId || '')) ? S.shareParseItems.map(makeRootFolder).filter(Boolean) : []; if (cached.length) return cached; try { const raw = await fetchShareDetail(rootParentId || '', ''); const folders = getShareDetailFiles(raw).map(makeRootFolder).filter(Boolean); if (folders.length) return folders; } catch(e) {} return [{ id: root.id || '', name: L.picker_all || L.label_share_parse_root, kind: 'drive#folder', icon_link: root.icon_link || root.iconLink || root.icon || '', _isShareItem: true, _shareRoot: true, _sharePath: path.slice(0, 2).map(n => n && n._shareRoot ? { ...n, name: L.picker_all || L.label_share_parse_root } : n) }]; } const target = basePath[basePath.length - 1]; const parentId = target && !target._sharePanel ? (target.id || '') : getShareParseRootParentId(); const toFolderItem = (raw, idx) => { const rawKind = String(raw && raw.kind || '').toLowerCase(); const mime = raw && (raw.mime_type || raw.mimeType || raw.content_type || ''); const isFolder = raw && (raw.kind === 'drive#folder' || rawKind === 'folder' || rawKind.includes('folder') || mime === 'application/x-directory'); if (!isFolder) return null; const id = String((raw.id || raw.file_id || raw.fileId || raw.resource_id || raw.resourceId || `share_${parentId || 'root'}_${idx}`)); const name = raw.name || raw.file_name || raw.title || ''; return { ...raw, id, name, kind: 'drive#folder', parent_id: parentId, icon_link: raw.icon_link || raw.iconLink || raw.icon || '', _isShareItem: true, _sharePath: basePath.concat([{ id, name, parent_id: parentId }]) }; }; if (crumbIndex === path.length - 1 && String(parentId || '') === String(S.shareParseCurParentId || '')) { return (Array.isArray(S.shareParseItems) ? S.shareParseItems : []).filter(item => item && item.kind === 'drive#folder'); } try { const raw = await fetchShareDetail(parentId || '', ''); return getShareDetailFiles(raw).map(toFolderItem).filter(Boolean); } catch(e) { return []; } }; const showShareParseCurrentCrumbDropdown = async (e, triggerEl, crumbIndex = path.length - 1) => { const oldPop = document.getElementById('pk-main-crumb-pop'); if (triggerEl.classList.contains('pk-active')) { if (oldPop) oldPop.remove(); triggerEl.classList.remove('pk-active'); triggerEl.innerHTML = CONF.crumbIcons.right; return; } if (oldPop) oldPop.remove(); UI.crumb.querySelectorAll('.pk-crumb-sep').forEach(sep => { sep.classList.remove('pk-active'); sep.innerHTML = CONF.crumbIcons.right; }); const folders = await resolveShareParseCrumbFolders(crumbIndex); if (!folders.length || !S.shareParseMode || !S.shareParseListActive || !triggerEl.isConnected || !UI.crumb || !UI.crumb.contains(triggerEl)) return; const pop = document.createElement('div'); pop.id = 'pk-main-crumb-pop'; pop.className = 'pk-crumb-pop pk-scroll pk-show'; if (document.querySelector('.pk-ov')?.classList.contains('pk-dark')) pop.classList.add('pk-dark'); pop.onwheel = (evt) => evt.stopPropagation(); folders.forEach(f => { const item = document.createElement('div'); item.className = 'pk-crumb-item'; item.dataset.id = f.id || ''; const iconSrc = f.icon_link || ''; const fallbackSvg = CONF.typeIcons.folder.replace(/width="\d+"/, 'width="18"').replace(/height="\d+"/, 'height="18"'); const iconHtml = iconSrc ? `${fallbackSvg}` : fallbackSvg; item.innerHTML = `${iconHtml}${esc(f.name || '')}`; item.onclick = (ev) => { ev.stopPropagation(); closeMenu(); handleShareParseFolderOpen(f); }; pop.appendChild(item); }); document.body.appendChild(pop); const closeMenu = () => { if (pop.parentNode) pop.remove(); triggerEl.classList.remove('pk-active'); triggerEl.innerHTML = CONF.crumbIcons.right; document.removeEventListener('mousedown', onOutsideClick); window.removeEventListener('resize', closeMenu); if (UI.vp) UI.vp.removeEventListener('scroll', closeMenu); }; const onOutsideClick = (evt) => { if (!pop.contains(evt.target) && !triggerEl.contains(evt.target)) closeMenu(); }; requestAnimationFrame(() => { if (!S.shareParseMode || !S.shareParseListActive || !triggerEl.isConnected || !UI.crumb || !UI.crumb.contains(triggerEl)) { closeMenu(); return; } const rect = getLogicalRect(triggerEl); if (!rect || (rect.left === 0 && rect.top === 0 && rect.width === 0 && rect.height === 0)) { closeMenu(); return; } const popRect = pop.getBoundingClientRect(); let left = rect.left - 4; if (left + popRect.width > window.innerWidth) left = window.innerWidth - popRect.width - 15; pop.style.left = Math.max(10, left) + 'px'; pop.style.top = (rect.bottom + 6) + 'px'; triggerEl.classList.add('pk-active'); triggerEl.innerHTML = CONF.crumbIcons.down; }); window.addEventListener('resize', closeMenu); if (UI.vp) UI.vp.addEventListener('scroll', closeMenu, { passive: true }); setTimeout(() => document.addEventListener('mousedown', onOutsideClick), 10); }; path.forEach((p, i) => { const s = document.createElement('span'); s.textContent = (S.shareParseMode && p && p._shareRoot) ? (L.picker_all || p.name || L.label_share_parse_root) : (p.name || L.label_share_parse_root); s.dataset.id = p.id || 'share_root'; s.className = i === path.length - 1 ? 'act' : ''; s.style.cssText = "display:inline-flex; align-items:center; padding:2px 6px; border-radius:4px; flex-shrink:0; transition:background 0.2s; white-space:nowrap; margin: auto 2px;"; s.onclick = (e) => { e.stopPropagation(); if (i < path.length - 1) handleShareParseCrumbClick(i); }; UI.crumb.appendChild(s); const showArrow = i < path.length - 1 || (i === path.length - 1 && hasCurrentShareParseFolder); if (showArrow) { const sep = document.createElement('span'); sep.className = 'pk-crumb-sep'; sep.innerHTML = CONF.crumbIcons.right; sep.onclick = (e) => { e.stopPropagation(); showShareParseCurrentCrumbDropdown(e, sep, i); }; UI.crumb.appendChild(sep); } }); return; } if (S.historyMode) { UI.crumb.style.pointerEvents = 'none'; UI.crumb.innerHTML = `
${L.btn_nav_history}
`; return; } if (S.trashMode) { UI.crumb.style.pointerEvents = 'none'; UI.crumb.innerHTML = `
${L.trash_title} ${L.trash_notice}
`; return; } UI.crumb.style.pointerEvents = 'auto'; if (S.isFlattened || S.dupMode) { UI.crumb.style.pointerEvents = 'none'; UI.crumb.style.display = 'none'; UI.crumb.innerHTML = ''; return; } const svgStyle = 'width:14px;height:14px;vertical-align:-4px;margin-right:4px;display:inline-block;'; const ICON_HOME = ``; const ICON_SEARCH = ``; const ICON_TRASH = ``; const ICON_STAR = ``; S.path.forEach((p, i) => { const s = document.createElement('span'); const isHome = (p.id === '' || p.id === 'root'); const isSearch = (p.id === 'virtual_search_root'); const isAnalyze = (p.id === 'analyze_root'); const isRecentRoot = (p.id === 'recent_root'); if (isHome) { let icon = ICON_HOME; if (S.trashMode) icon = ICON_TRASH; else if (S.starredMode) icon = ICON_STAR; s.innerHTML = `${icon}${p.name}`; } else if (isSearch || isAnalyze) { s.innerHTML = `${ICON_SEARCH}${p.name}`; } else if (isRecentRoot) { const ICON_RECENT = CONF.icons.recent.replace(' { e.stopPropagation(); if (i !== S.path.length - 1) { if (i < S.path.length - 1) { S.latestChildId = S.path[i + 1].id; } syncFolderFirstFromGlobal(); S.path = S.path.slice(0, i + 1); load(); } else { if (UI.btnRefresh && !UI.btnRefresh.disabled) UI.btnRefresh.click(); } }; UI.crumb.appendChild(s); const isLast = i === S.path.length - 1; let showArrow = !isLast; if (isLast) { const isUnsupportedVirtual = p.id === 'history_root' || p.id === 'offline_root' || p.id === 'upload_root'; if (!S.isFlattened && !S.dupMode && !isUnsupportedVirtual) { const crumbSourceItems = (p.id === 'virtual_search_root' && S.lastGlobalResults && S.lastGlobalResults.length > 0) ? S.lastGlobalResults : S.items; if (crumbSourceItems && crumbSourceItems.some(item => item.kind === 'drive#folder')) { showArrow = true; } } } if (showArrow) { const sep = document.createElement('span'); sep.className = 'pk-crumb-sep'; sep.innerHTML = CONF.crumbIcons.right; sep.onclick = (e) => { e.stopPropagation(); showCrumbDropdown(e, p.id, sep); }; UI.crumb.appendChild(sep); } }); S._crumbIdx = S.path.length - 1; UI.crumb.onwheel = (e) => { e.preventDefault(); const existingPop = document.getElementById('pk-main-crumb-pop'); if (existingPop) { existingPop.remove(); UI.crumb.querySelectorAll('.pk-crumb-sep').forEach(sep => { sep.classList.remove('pk-active'); sep.innerHTML = CONF.crumbIcons.right; }); } const nodes = [...UI.crumb.querySelectorAll('span:not(.pk-crumb-sep)')]; if (!nodes.length) return; const now = Date.now(); if (now - (S._lastCrumbScroll || 0) < 120) return; S._lastCrumbScroll = now; if (e.deltaY < 0) S._crumbIdx = Math.max(0, S._crumbIdx - 1); else S._crumbIdx = Math.min(nodes.length - 1, S._crumbIdx + 1); const targetNode = nodes[S._crumbIdx]; const containerWidth = UI.crumb.offsetWidth; const centerOffset = targetNode.offsetLeft + (targetNode.offsetWidth / 2) - (containerWidth / 2); UI.crumb.scrollTo({ left: centerOffset, behavior: 'smooth' }); }; setTimeout(() => { if (UI.crumb) { UI.crumb.scrollTo({ left: UI.crumb.scrollWidth, behavior: 'smooth' }); } }, 0); } el.tabIndex = 0; el.focus(); const clearAllPlayHistory = async () => { if (!S.historyMode) return; if (!await showConfirm(L.warn_clear_all_history)) return; const ok = await clearOfficialPlayHistory(); if (!ok) { showToast(L.err_official_history_clear, 'error'); return; } S.items = []; S.itemMap.clear(); S.clearSelection(); if (typeof globalCache !== 'undefined') { globalCache.delete('history_root'); globalCache.delete('history_session'); } S.cache.delete('history_root'); refresh(); updateStat(); showToast(L.msg_clear_all_history_done); }; const keyHandler = (e) => { const win = document.querySelector('.pk-ov'); if (!win || win.style.display === 'none') return; const tag = e.target.tagName; if (tag === 'TEXTAREA' || (tag === 'INPUT' && e.target.type !== 'checkbox' && e.target.type !== 'button' && e.target.type !== 'submit')) return; if (e.key === 'Escape') { if (typeof isSearchHistOpen === 'function' && isSearchHistOpen()) { closeSearchHist(); return; } const pTip = document.getElementById('pk_p_plist_tip_global'); if (pTip) pTip.style.display = 'none'; if (S.closePlayerOverlay()) return; if (S.closeAudioOverlay()) return; if (S.closeImageOverlay()) return; if (S.closeTopModalOverlay()) return; if (UI.ctx.style.display === 'block') { UI.ctx.style.display = 'none'; return; } if (S.getSelectedCount() > 0) { S.clearSelection(); refresh(); } else if (UI.win.classList.contains('pk-maximized')) { if (!isTurbo && btnMax) btnMax.click(); } else { if (!isTurbo) UI.btnClose.click(); } return; } const hasMediaOverlay = document.querySelector('.pk-img-ov, #pk-player-ov, #pk-audio-ov'); if (hasMediaOverlay) return; if (e.key === ' ' && !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) { const t = e.target; const tag = String((t && t.tagName) || '').toUpperCase(); const editable = t && (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || tag === 'BUTTON' || t.closest?.('[contenteditable="true"],[contenteditable=""]')); if (!editable) { e.preventDefault(); e.stopPropagation(); return; } } if (!e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey) { if (e.key === 'f' || e.key === 'F') { if (UI.btnImgSearch && !UI.btnImgSearch.disabled && UI.btnImgSearch.style.display !== 'none') { e.preventDefault(); UI.btnImgSearch.click(); return; } } } if (e.key === 'Delete' && e.altKey) { e.preventDefault(); if (S.shareMode || S.uploadMode) return; const existingModal = Array.from(document.querySelectorAll('.pk-modal-ov')).find(m => { const title = m.querySelector('h3'); return title && title.textContent.includes(L.title_blacklist); }); if (existingModal) { existingModal.remove(); } else { showBlacklistModal(); } } const hasActiveOverlay = document.querySelector('.pk-modal-ov'); if (hasActiveOverlay) return; if (e.key === 'F2') { e.preventDefault(); if (S.trashMode || S.shareMode || S.offlineMode || S.uploadMode || S.historyMode) return; const selectedCount = S.getSelectedCount(); if (selectedCount === 1) { if (UI.btnRename.disabled) return; UI.btnRename.click(); } else if (selectedCount > 1) { if (UI.btnBulkRename.disabled) return; UI.btnBulkRename.click(); } } if (e.key === 'F5') { e.preventDefault(); if (S.uploadMode) load(false, true); else if (S.trashMode) load(false, true); else UI.btnRefresh.click(); } if (e.key === 'F8') { e.preventDefault(); const cur = S.path[S.path.length - 1]; const isStarredRoot = S.starredMode && S.path.length === 1; const isRecentRoot = S.recentMode && S.path.length === 1; const isHistoryRoot = S.historyMode && S.path.length === 1; const isBlocked = S.isFlattened || S.dupMode || S.shareMode || S.offlineMode || S.uploadMode || S.historyMode || isStarredRoot || isRecentRoot || isHistoryRoot || cur.id === 'analyze_root' || cur.id === 'virtual_search_root'; if (isBlocked) return; UI.btnNewFolder.click(); } if (e.key === 'r' || e.key === 'R') { if (S.trashMode && !e.ctrlKey && !e.altKey && !e.metaKey) { e.preventDefault(); if (UI.btnRestore && !UI.btnRestore.disabled) UI.btnRestore.click(); return; } if (S.offlineMode && !e.ctrlKey && !e.altKey && !e.metaKey) { e.preventDefault(); if (UI.btnRetryTask && !UI.btnRetryTask.disabled) UI.btnRetryTask.click(); return; } } if (e.altKey && (e.key === 'm' || e.key === 'M')) { e.preventDefault(); if (UI.btnMigrate && !UI.btnMigrate.disabled && UI.btnMigrate.style.display !== 'none') { UI.btnMigrate.click(); } return; } if (e.altKey && (e.key === 'l' || e.key === 'L')) { e.preventDefault(); if (UI.btnExportM3U && !UI.btnExportM3U.disabled && UI.btnExportM3U.style.display !== 'none') { UI.btnExportM3U.click(); } return; } if ((e.key === 'm' || e.key === 'M') && !e.altKey && !e.ctrlKey && !e.metaKey) { const imgPlayer = document.querySelector('.pk-img-ov'); const vidPlayer = document.getElementById('pk-player-ov'); if (!imgPlayer && !vidPlayer) { const btnMax = document.querySelector('#pk-maximize'); if (!isTurbo && btnMax) btnMax.click(); } } if (e.key === 'Delete' && e.shiftKey) { if (S.historyMode) { e.preventDefault(); clearAllPlayHistory(); return; } if (S.trashMode && UI.btnEmptyTrash) { e.preventDefault(); UI.btnEmptyTrash.click(); return; } if (S.uploadMode && UI.btnUpClearAll) { e.preventDefault(); UI.btnUpClearAll.click(); return; } } if (e.key === 'Delete' && !e.ctrlKey && !e.altKey && !e.shiftKey) { if (S.shareMode) { if (UI.btnCancelShare && !UI.btnCancelShare.disabled) UI.btnCancelShare.click(); return; } if (S.uploadMode) { if (UI.btnUpDel && !UI.btnUpDel.disabled) UI.btnUpDel.click(); return; } if (S.trashMode) { if(UI.btnDelForever && !UI.btnDelForever.disabled) UI.btnDelForever.click(); } else { if (!UI.btnDel.disabled) UI.btnDel.click(); } } if (e.key === 'Delete' && e.ctrlKey) { e.preventDefault(); if (S.trashMode) return; if (S.isFlattened || S.dupMode || S.uploadMode) return; if (!UI.btnPrune.disabled) UI.btnPrune.click(); } if (e.ctrlKey || e.metaKey) { if (e.key === 'a' || e.key === 'A') { e.preventDefault(); if (S.handleSelectAll) S.handleSelectAll(); } if (e.key === 'c' || e.key === 'C') { if (window.getSelection().toString()) return; e.preventDefault(); if (S.trashMode || S.shareMode || S.offlineMode || S.uploadMode || S.historyMode) return; if (!UI.btnCopy.disabled) UI.btnCopy.click(); } if (e.key === 'x' || e.key === 'X') { e.preventDefault(); if (S.trashMode || S.shareMode || S.offlineMode || S.uploadMode || S.historyMode) return; if (!UI.btnCut.disabled) UI.btnCut.click(); } if (e.key === 'v' || e.key === 'V') { e.preventDefault(); if (S.trashMode || S.shareMode || S.offlineMode || S.uploadMode || S.historyMode) return; const cur = S.path[S.path.length - 1]; const isStarredRoot = S.starredMode && S.path.length === 1; const isRecentRoot = S.recentMode && S.path.length === 1; const isHistoryRoot = S.historyMode && S.path.length === 1; const isPasteBlocked = S.isFlattened || S.dupMode || S.offlineMode || S.historyMode || isStarredRoot || isRecentRoot || isHistoryRoot || cur.id === 'analyze_root' || cur.id === 'virtual_search_root'; if (isPasteBlocked) return; if (!UI.btnPaste.disabled) UI.btnPaste.click(); } } if (e.altKey) { if (S.offlineMode && (e.key === 'c' || e.key === 'C')) { e.preventDefault(); if (UI.btnCopyLinkOffline && !UI.btnCopyLinkOffline.disabled) UI.btnCopyLinkOffline.click(); return; } if (e.key === 's' || e.key === 'S') { e.preventDefault(); openSettingsModal(); } if (e.key === 'g' || e.key === 'G') { e.preventDefault(); if (S.uploadMode) { if (UI.btnUpStart && !UI.btnUpStart.disabled) UI.btnUpStart.click(); } } if (e.key === 'p' || e.key === 'P') { e.preventDefault(); if (S.uploadMode) { if (UI.btnUpPause && !UI.btnUpPause.disabled) UI.btnUpPause.click(); } } if (e.key === 'u' || e.key === 'U') { e.preventDefault(); const cur = S.path[S.path.length - 1]; const isRoot = S.path.length === 1; const isUnzipBlocked = S.trashMode || S.shareMode || S.offlineMode || S.uploadMode || S.historyMode || S.isFlattened || S.dupMode || (S.analyzeMode && cur.id === 'analyze_root') || (cur.id === 'virtual_search_root') || (S.recentMode && isRoot) || (S.starredMode && isRoot); if (!isUnzipBlocked && !UI.btnUnzip.disabled) UI.btnUnzip.click(); } if (e.key === 'd' || e.key === 'D') { e.preventDefault(); if (S.shareMode || S.uploadMode || S.trashMode || S.offlineMode) return; if (UI.btnDown && !UI.btnDown.disabled) UI.btnDown.click(); } if (e.key === 'a' || e.key === 'A') { e.preventDefault(); if (S.shareMode || S.uploadMode || S.trashMode || S.offlineMode) return; if (UI.btnAria2 && !UI.btnAria2.disabled) UI.btnAria2.click(); } if (e.key === 'e' || e.key === 'E') { e.preventDefault(); if (S.shareMode || S.trashMode) return; if (UI.btnExt && !UI.btnExt.disabled) UI.btnExt.click(); } if (e.key === 't' || e.key === 'T') { e.preventDefault(); if (UI.btnTheme) UI.btnTheme.click(); } } }; document.addEventListener('keydown', keyHandler); if (document._pkPlayerCloseClickHandler) { document.removeEventListener('click', document._pkPlayerCloseClickHandler, true); } document._pkPlayerCloseClickHandler = (e) => { const btn = e.target && e.target.closest ? e.target.closest('#pk_p_close') : null; if (!btn) return; e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation(); S.closePlayerOverlay(); }; document.addEventListener('click', document._pkPlayerCloseClickHandler, true); if (document._pkImageCloseClickHandler) { document.removeEventListener('click', document._pkImageCloseClickHandler, true); } document._pkImageCloseClickHandler = (e) => { const btn = e.target && e.target.closest ? e.target.closest('#pk_img_close') : null; if (!btn) return; e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation(); S.closeImageOverlay(); }; document.addEventListener('click', document._pkImageCloseClickHandler, true); let sideMouseBusy = false; let lastSideMouseBtn = -1; let lastSideMouseTs = 0; const mouseSideNavHandler = async (e) => { const btn = e.button; if (btn !== 3 && btn !== 4) return; const win = document.querySelector('.pk-ov'); if (!win || win.style.display === 'none') return; const now = Date.now(); if (lastSideMouseBtn === btn && (now - lastSideMouseTs) < 250) { e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation(); return; } lastSideMouseBtn = btn; lastSideMouseTs = now; const hasFrontOverlay = !!document.querySelector('.pk-modal-ov, .pk-img-ov, #pk-player-ov, #pk-audio-ov'); if (!hasFrontOverlay) { const t = e.target; if (t && (t.closest('textarea') || t.closest('input:not([type="checkbox"]):not([type="button"]):not([type="submit"])') || t.closest('[contenteditable="true"]'))) { return; } } e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation(); if (sideMouseBusy) { return; } sideMouseBusy = true; try { closeTransientDropdowns(false, true); if (btn === 3) { await S.handleMouseSideBack(); } else { await S.handleMouseSideForward(); } } catch (err) { throw err; } finally { setTimeout(() => { sideMouseBusy = false; }, 0); } }; if (window._pkMouseSideNavHandler) { window.removeEventListener('mousedown', window._pkMouseSideNavHandler, true); window.removeEventListener('mouseup', window._pkMouseSideNavHandler, true); window.removeEventListener('auxclick', window._pkMouseSideNavHandler, true); window.removeEventListener('click', window._pkMouseSideNavHandler, true); } window._pkMouseSideNavHandler = mouseSideNavHandler; window.addEventListener('mousedown', mouseSideNavHandler, { capture: true, passive: false }); window.addEventListener('mouseup', mouseSideNavHandler, { capture: true, passive: false }); window.addEventListener('auxclick', mouseSideNavHandler, { capture: true, passive: false }); window.addEventListener('click', mouseSideNavHandler, { capture: true, passive: false }); const showProperty = (item) => { const L = getStrings(); let rows = []; rows.push({ k: L.lbl_prop_name, v: item.name }); rows.push({ k: L.lbl_prop_size, v: fmtSize(item.size) }); rows.push({ k: L.lbl_prop_ctime, v: fmtDate(item.created_time) }); if (item.modified_time) rows.push({ k: L.lbl_prop_mtime, v: fmtDate(item.modified_time) }); let source = L.str_prop_unknown; if (item.kind === 'drive#task') source = L.str_prop_offline; else if (item.kind === 'drive#folder') source = L.lbl_type_folder; else if (item.from === 'share') source = L.str_prop_share; else source = L.str_prop_user; rows.push({ k: L.lbl_prop_source, v: source }); let link = item.source_url || (item.params && item.params.url) || item.web_content_link || item.url; if (!link && item.params && typeof item.params === 'object') { for (const key in item.params) { if (key.toLowerCase() === 'url') { link = item.params[key]; break; } } } if (link) { rows.push({ k: L.lbl_prop_link, v: link, isLink: true }); } let pathStr = "Root"; if (S.offlineMode) pathStr = L.title_offline; else if (S.shareMode) pathStr = L.btn_nav_share; else if (item._lineage) pathStr = item._lineage.map(x => x.name).join('/'); rows.push({ k: L.lbl_prop_path, v: pathStr }); let html = `
`; rows.forEach(r => { let valHtml = `
${esc(r.v)}
`; if (r.isLink) { valHtml = `
${esc(r.v)}
`; } html += `
${r.k}:
${valHtml}
`; }); html += `
`; const m = showModal(html); m.querySelector('.pk-modal h3').textContent = L.title_property; m.querySelector('.pk-modal').style.width = "520px"; }; const btnProp = document.getElementById('ctx-property'); if (btnProp) { btnProp.onclick = () => { const item = S.itemMap.get(S.activeId); if (item) showProperty(item); UI.ctx.style.display = 'none'; }; } const mouseHandler = (e) => { if (!document.querySelector('.pk-ov') || !UI || !UI.ctx) return; if (UI.ctx.style.display !== 'none' && !UI.ctx.contains(e.target)) { UI.ctx.style.display = 'none'; UI.ctx.style.flexDirection = ''; const isRoot = S.path.length === 1 && S.path[0].id === ''; if (!S.trashMode && isRoot && S.getSelectedCount() === 1) { const id = S.getSelectedIds()[0]; const item = S.itemMap.get(id); if (item && item.kind === 'drive#folder' && item.name === CONF.SYSTEM_FOLDER_NAME) { S.clearSelection(); renderVisible(); updateStat(); } } } }; document.addEventListener('mouseup', mouseHandler); function isShareParsePagingPending() { return !!(S.shareParseMode && S.shareParseListActive && !S.shareParseInsightMode && (S.shareParseNextPageToken || S.shareParseAutoLoadRunning)); } function getPagingLoadingSuffix() { return (S.pagingLoading || isShareParsePagingPending()) ? ' (Loading...)' : ''; } function updateStat() { syncLocalUploadVisibility(); let total = 0; const visibleIdSet = new Set(); const len = S.display.length; for (let i = 0; i < len; i++) { const item = S.display[i]; if (item && !item.isHeader && !(S.movingIds && S.movingIds.has(item.id))) { total++; visibleIdSet.add(item.id); } } const useLoadingAllSelectionStat = S.selMode === 'all' && S.loading && !S.search && !S.shareParseMode && !S.dupMode && !S.analyzeMode && !S.isFlattened && Array.isArray(S.items) && S.items.length > total; const loadingAllSelectionTotal = useLoadingAllSelectionStat ? S.items.reduce((cnt, item) => cnt + (item && !item.isHeader && !(S.movingIds && S.movingIds.has(item.id)) ? 1 : 0), 0) : total; if (S.selMode === 'all') { const validExcludedIds = new Set(); for (const id of S.selEx) { if (visibleIdSet.has(id)) validExcludedIds.add(id); } if (validExcludedIds.size !== S.selEx.size) { S.selEx = validExcludedIds; } } else { const validSelectedIds = S.getSelectedIds().filter(id => visibleIdSet.has(id)); const hadShrink = validSelectedIds.length !== S.getSelectedCount(); if (hadShrink) { S.setExplicitSelection(validSelectedIds); } } const selectedIds = S.getSelectedIds(); let n = selectedIds.length; const statTotal = useLoadingAllSelectionStat ? loadingAllSelectionTotal : total; const statSelectedCount = useLoadingAllSelectionStat ? Math.max(0, statTotal - (S.selEx ? S.selEx.size : 0)) : n; const btnInvert = document.getElementById('pk-btn-invert'); if (btnInvert) { btnInvert.style.display = (!S.dupMode && n > 0) ? 'flex' : 'none'; btnInvert.style.color = btnInvert.matches(':hover') ? 'var(--pk-pri)' : 'var(--pk-fg)'; } const btnGridInvert = document.getElementById('pk-grid-invert'); if (btnGridInvert) { btnGridInvert.style.display = (!S.dupMode && n > 0) ? 'flex' : 'none'; btnGridInvert.style.color = btnGridInvert.matches(':hover') ? 'var(--pk-pri)' : 'var(--pk-fg)'; } if (S.shareParseMode) { const inShareParsePanel = !S.shareParseListActive; const inShareInsight = !!(S.shareParseListActive && S.shareParseInsightMode); const shareParseInvertVisible = !inShareParsePanel && n > 0; if (btnInvert) btnInvert.style.display = shareParseInvertVisible ? 'flex' : 'none'; if (btnGridInvert) btnGridInvert.style.display = shareParseInvertVisible ? 'flex' : 'none'; setSearchWrapVisible(!inShareParsePanel); if (UI.searchClear) UI.searchClear.style.display = (!inShareParsePanel && S.search) ? 'flex' : 'none'; if (UI.lblSearchPath) UI.lblSearchPath.style.display = inShareInsight ? 'flex' : 'none'; if (UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if (UI.chkGlobal) UI.chkGlobal.checked = false; if (UI.stat) UI.stat.style.display = inShareParsePanel ? 'none' : ''; let shareParseSelectedSize = 0; let hasShareParseSelectedFolder = false; let shareParseSingleVideo = false; let shareParseSingleMediaWithCover = false; let shareParseSingleItem = null; if (n > 0) { for (const id of selectedIds) { const item = (S.shareParseInsightItemMap && S.shareParseInsightItemMap.get(id)) || (S.shareParseItemMap && S.shareParseItemMap.get(id)) || (S.itemMap && S.itemMap.get(id)); if (!item) continue; if (n === 1) shareParseSingleItem = item; if (item.kind === 'drive#folder') { hasShareParseSelectedFolder = true; continue; } shareParseSelectedSize += parseInt(item.size || 0, 10) || 0; } } if (!inShareParsePanel && n === 1 && shareParseSingleItem && shareParseSingleItem.kind !== 'drive#folder') { const isVid = isVideoLikeItem(shareParseSingleItem); const isImg = isImageLikeItem(shareParseSingleItem); if (isVid) shareParseSingleVideo = true; if (isVid || isImg) { if (shareParseSingleItem.thumbnail_link && shareParseSingleItem.thumbnail_link !== shareParseSingleItem.icon_link) { const isMax = UI.win.classList.contains('pk-maximized'); const isBlur = typeof isBlurEnabledForView === 'function' ? isBlurEnabledForView(isGridView() ? 'grid' : 'list') : (typeof gmGet !== 'undefined' ? gmGet('pk_blur_thumb', false) : false); if (isMax && isBlur) { shareParseSingleMediaWithCover = true; } else if (window.pkGlobalThumbCache && window.pkGlobalThumbCache.has(shareParseSingleItem.id)) { shareParseSingleMediaWithCover = true; } } } } let statText = L.status_ready.replace('{n}', total) + getPagingLoadingSuffix(); if (n > 0) { statText += `\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0${(L.label_share_parse_selected_count || L.sel_count).replace('{n}', n)}`; if (!hasShareParseSelectedFolder) statText += `\u00A0\u00A0${fmtSize(shareParseSelectedSize)}`; } if (UI.stat) UI.stat.textContent = statText; const hasAll = total > 0 && n === total; const hasSome = n > 0 && n < total; if (UI.chkAll) { UI.chkAll.checked = hasAll; UI.chkAll.indeterminate = hasSome; } [UI.btnAria2, UI.btnDown, UI.btnDel, UI.btnRename, UI.btnBulkRename, UI.btnCut, UI.btnCopy, UI.btnPaste, UI.btnPrune, UI.btnUnzip, UI.btnNewFolder, UI.btnRefresh, UI.btnBlacklistManager, UI.btnMigrate].forEach(b => { if (b) { b.disabled = true; b.style.display = 'none'; } }); if (UI.btnExt) { UI.btnExt.style.display = inShareParsePanel ? 'none' : 'inline-flex'; UI.btnExt.disabled = !shareParseSingleVideo; } if (UI.btnExportM3U) { const selectedVideosForM3U = getM3UExportSelectedVideos(); UI.btnExportM3U.style.display = UI.btnExt ? UI.btnExt.style.display : (inShareParsePanel ? 'none' : 'inline-flex'); UI.btnExportM3U.disabled = !selectedVideosForM3U.length; UI.btnExportM3U.style.cursor = UI.btnExportM3U.disabled ? 'not-allowed' : 'pointer'; UI.btnExportM3U.style.opacity = UI.btnExportM3U.disabled ? '0.4' : '1'; } if (UI.btnImgSearch) { UI.btnImgSearch.style.display = inShareParsePanel ? 'none' : 'inline-flex'; UI.btnImgSearch.disabled = !shareParseSingleMediaWithCover; } if (UI.btnExport) UI.btnExport.style.display = 'none'; if (UI.actionBar) { UI.actionBar.style.display = S.shareParseListActive ? 'flex' : 'none'; UI.actionBar.querySelectorAll('.pk-sep').forEach(sep => { sep.style.display = 'none'; }); } if (UI.bottomGrp) UI.bottomGrp.style.display = inShareParsePanel ? 'none' : 'flex'; if (UI.ctx) UI.ctx.style.display = 'none'; updateShareParseSaveButton(); updateShareParseInsightButtons(); return; } [UI.btnRefresh, UI.btnBlacklistManager].forEach(b => { if (b) b.disabled = false; }); if (UI.stat) UI.stat.style.display = ''; updateShareParseInsightButtons(); let hasProtectedItem = false; let hasSelFolder = false; let selSize = 0; if (n > 0) { for (const id of selectedIds) { const item = S.itemMap.get(id); if (!item) continue; const sz = parseInt(item.size || 0); if (item.kind === 'drive#folder') { hasSelFolder = true; if (S.analyzeMode) { selSize += sz; } } else { selSize += sz; } if (!S.trashMode && isSystemItem(item)) hasProtectedItem = true; } } let statText = L.status_ready.replace('{n}', statTotal) + getPagingLoadingSuffix(); if (statSelectedCount > 0) { statText += `\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0${L.sel_count.replace('{n}', statSelectedCount)}`; if (!useLoadingAllSelectionStat && !S.shareMode && (S.analyzeMode || !hasSelFolder)) { statText += `\u00A0\u00A0${fmtSize(selSize)}`; } } UI.stat.textContent = statText; const hasSel = n > 0; if (UI.btnAria2) UI.btnAria2.disabled = S.trashMode || !hasSel; if (UI.btnDown) UI.btnDown.disabled = S.trashMode || !hasSel; if (UI.chkAll) { let isAll = false; let isInd = false; if (statSelectedCount > 0 && statTotal > 0) { isAll = (statSelectedCount === statTotal); isInd = (statSelectedCount < statTotal); } else { isAll = false; isInd = false; } UI.chkAll.checked = isAll; UI.chkAll.indeterminate = isInd; } const isShare = S.shareMode; const isOffline = S.offlineMode; const isHistory = S.historyMode; const isUpload = S.uploadMode; let isSingleVideo = false; let isSingleMediaWithCover = false; if (n === 1) { const id = selectedIds[0]; const item = S.itemMap.get(id); if (item && item.kind !== 'drive#folder') { const mime = (item.mime_type || '').toLowerCase(); const ext = (item.name || '').split('.').pop().toLowerCase(); const isVid = mime.startsWith('video/') || ['mp4','mkv','avi','mov','wmv','flv','webm','ts'].includes(ext); const isImg = mime.startsWith('image/') || ['jpg','jpeg','png','gif','bmp','webp','heic'].includes(ext); let isTaskReady = true; if (S.offlineMode) isTaskReady = item.phase === 'PHASE_TYPE_COMPLETE'; if (S.uploadMode) isTaskReady = item.status === 'DONE'; if (isVid && isTaskReady) isSingleVideo = true; if ((isVid || isImg) && isTaskReady) { if (item.thumbnail_link && item.thumbnail_link !== item.icon_link) { const isMax = UI.win.classList.contains('pk-maximized'); const isBlur = typeof isBlurEnabledForView === 'function' ? isBlurEnabledForView(isGridView() ? 'grid' : 'list') : (typeof gmGet !== 'undefined' ? gmGet('pk_blur_thumb', false) : false); if (isMax && isBlur) { isSingleMediaWithCover = true; } else if (window.pkGlobalThumbCache && window.pkGlobalThumbCache.has(item.id)) { isSingleMediaWithCover = true; } } } } } if (UI.btnExt) UI.btnExt.disabled = S.trashMode || !isSingleVideo; if (UI.btnExportM3U) { const selectedVideosForM3U = getM3UExportSelectedVideos(); UI.btnExportM3U.style.display = UI.btnExt ? UI.btnExt.style.display : 'none'; UI.btnExportM3U.disabled = S.trashMode || !selectedVideosForM3U.length; UI.btnExportM3U.style.cursor = UI.btnExportM3U.disabled ? 'not-allowed' : 'pointer'; UI.btnExportM3U.style.opacity = UI.btnExportM3U.disabled ? '0.4' : '1'; } if (UI.btnImgSearch) UI.btnImgSearch.disabled = S.trashMode || !isSingleMediaWithCover; if (isUpload) { if (hasSel) { let hasRunning = false; let hasStopped = false; for (const id of selectedIds) { const task = S.itemMap.get(id); if (task) { const s = task.status; if (['UPLOADING', 'HASHING', 'WAITING', 'RUNNING'].includes(s)) hasRunning = true; if (['PAUSED', 'ERROR'].includes(s)) hasStopped = true; } if (hasRunning && hasStopped) break; } if (UI.btnUpPause) UI.btnUpPause.disabled = !hasRunning; if (UI.btnUpStart) UI.btnUpStart.disabled = !hasStopped; if (UI.btnUpDel) UI.btnUpDel.disabled = false; } else { if (UI.btnUpPause) UI.btnUpPause.disabled = true; if (UI.btnUpStart) UI.btnUpStart.disabled = true; if (UI.btnUpDel) UI.btnUpDel.disabled = true; } if (UI.btnUpClearAll) UI.btnUpClearAll.disabled = (S.uploadTasks.length === 0); } if (UI.btnRetryTask) { UI.btnRetryTask.style.display = isOffline ? 'inline-flex' : 'none'; const hasFailedTask = selectedIds.some(id => S.itemMap.get(id)?.phase === 'PHASE_TYPE_ERROR'); UI.btnRetryTask.disabled = !hasFailedTask; } if (UI.btnCopyLinkOffline) { UI.btnCopyLinkOffline.style.display = isOffline ? 'inline-flex' : 'none'; UI.btnCopyLinkOffline.disabled = !hasSel; } UI.btnCopy.style.display = (isShare || isOffline || isHistory || isUpload) ? 'none' : 'inline-flex'; UI.btnCut.style.display = (isShare || isOffline || isHistory || isUpload) ? 'none' : 'inline-flex'; UI.btnRename.style.display = (isShare || isOffline || isHistory || isUpload) ? 'none' : 'inline-flex'; UI.btnBulkRename.style.display = (isShare || isOffline || isHistory || isUpload) ? 'none' : 'inline-flex'; UI.btnDel.style.display = (isShare || isUpload) ? 'none' : 'inline-flex'; const delBtnSpan = UI.btnDel.querySelector('span'); if (delBtnSpan) { delBtnSpan.textContent = isHistory ? L.btn_clear_history : L.btn_del; UI.btnDel.setAttribute('data-pk-tip', isHistory ? L.tip_clear_history : L.tip_del); } if (UI.btnClearHistoryAll) { UI.btnClearHistoryAll.style.display = isHistory ? 'inline-flex' : 'none'; UI.btnClearHistoryAll.disabled = !isHistory || S.items.length === 0; UI.btnClearHistoryAll.style.opacity = UI.btnClearHistoryAll.disabled ? '0.45' : '1'; UI.btnClearHistoryAll.style.cursor = UI.btnClearHistoryAll.disabled ? 'not-allowed' : 'pointer'; } if (isShare && UI.btnCancelShare) { UI.btnCancelShare.disabled = !hasSel; } if (UI.btnMigrate) { UI.btnMigrate.disabled = !hasSel; UI.btnMigrate.style.cursor = UI.btnMigrate.disabled ? 'not-allowed' : 'pointer'; UI.btnMigrate.style.opacity = UI.btnMigrate.disabled ? '0.4' : '1'; } const hasClipData = S.clipItems && S.clipItems.length > 0; const isPasteLocked = S.movingIds && S.movingIds.size > 0; if (UI.btnPaste) { UI.btnPaste.disabled = !hasClipData || isPasteLocked; const cur = S.path[S.path.length - 1]; const isStarredRoot = S.starredMode && S.path.length === 1; const isRecentRoot = S.recentMode && S.path.length === 1; const isHistoryRoot = S.historyMode && S.path.length === 1; const shouldHidePaste = S.trashMode || S.shareMode || S.offlineMode || S.uploadMode || S.historyMode || S.isFlattened || S.dupMode || isStarredRoot || isRecentRoot || isHistoryRoot || cur.id === 'analyze_root' || cur.id === 'virtual_search_root'; UI.btnPaste.style.display = shouldHidePaste ? 'none' : 'inline-flex'; UI.btnPaste.style.cursor = isPasteLocked ? 'wait' : (UI.btnPaste.disabled ? 'not-allowed' : 'pointer'); } UI.btnCopy.disabled = !hasSel || isPasteLocked ; UI.btnCut.disabled = !hasSel || isPasteLocked || hasProtectedItem; if (UI.btnNewFolder) { UI.btnNewFolder.disabled = isPasteLocked; if (isPasteLocked) UI.btnNewFolder.style.cursor = 'wait'; else UI.btnNewFolder.style.cursor = ''; } UI.btnDel.disabled = !hasSel || (n === 1 && hasProtectedItem); const isStandardRenameView = !(isShare || isOffline || isHistory || isUpload); if (isStandardRenameView) { if (n <= 1) { UI.btnRename.style.display = 'inline-flex'; UI.btnBulkRename.style.display = 'none'; UI.btnRename.disabled = (n === 0) || hasProtectedItem; } else { UI.btnRename.style.display = 'none'; UI.btnBulkRename.style.display = 'inline-flex'; UI.btnBulkRename.disabled = false; } } if (UI.btnRestore) UI.btnRestore.disabled = !hasSel; if (UI.btnDelForever) UI.btnDelForever.disabled = !hasSel; if (UI.btnPrune) { const isHiddenMode = S.isFlattened || S.dupMode || S.shareMode || S.offlineMode || S.uploadMode || S.historyMode; UI.btnPrune.style.display = isHiddenMode ? 'none' : 'inline-flex'; UI.btnPrune.disabled = isHiddenMode || !hasSelFolder; } if (UI.btnUnzip) { const cur = S.path[S.path.length - 1]; const isRoot = S.path.length === 1; const isUnzipHidden = S.shareMode || S.offlineMode || S.uploadMode || S.historyMode || S.isFlattened || S.dupMode || (S.analyzeMode && cur.id === 'analyze_root') || (cur.id === 'virtual_search_root') || (S.recentMode && isRoot) || (S.starredMode && isRoot); UI.btnUnzip.style.display = isUnzipHidden ? 'none' : 'inline-flex'; const isArchive = (it) => { if (!it || it.kind === 'drive#folder') return false; const n = (it.name || '').toLowerCase(); const m = (it.mime_type || '').toLowerCase(); return m.includes('zip') || m.includes('rar') || m.includes('7z') || n.endsWith('.zip') || n.endsWith('.rar') || n.endsWith('.7z'); }; const hasArchive = selectedIds.some(id => isArchive(S.itemMap.get(id))); UI.btnUnzip.disabled = S.trashMode || !hasArchive; } } const PK_WEB_VOD_TS_DOWNLOADER = 'https://web-vod-xdrive.mypikpak.com/ts_downloader'; const PK_WEB_VOD_CLIENT_ID = 'UElLUEFLX1dFQg'; const buildPikPakTsHlsUrl = (rawUrl) => { const input = String(rawUrl || '').trim(); if (!input) return input; if (input.includes('ts_downloader') && input.includes('url=')) { return input; } const cleanUrl = input .replace('&ext=.m3u8', '') .replace('?ext=.m3u8&', '?') .replace('?ext=.m3u8', ''); return `${PK_WEB_VOD_TS_DOWNLOADER}?client_id=${PK_WEB_VOD_CLIENT_ID}&url=${encodeURIComponent(cleanUrl)}`; }; const isPikPakTsDownloaderUrl = (url) => { const s = String(url || ''); return s.includes('web-vod-xdrive.mypikpak.com/ts_downloader') || s.includes('/ts_downloader?'); }; let pkOfficialHlsCtorCache = null; let pkOfficialHlsProbeFailed = false; const getPikPakPageWindow = () => { try { if (typeof unsafeWindow !== 'undefined' && unsafeWindow) return unsafeWindow; } catch (e) {} return window; }; const getPikPakOfficialHlsCtor = () => { if (pkOfficialHlsCtorCache) return pkOfficialHlsCtorCache; if (pkOfficialHlsProbeFailed) return null; try { const pageWin = getPikPakPageWindow(); const chunk = pageWin.webpackChunkpikpak_web; if (!chunk || typeof chunk.push !== 'function') { pkOfficialHlsProbeFailed = true; return null; } let webpackRequire = null; const probeName = `pk_hls_probe_${Date.now()}_${Math.random().toString(36).slice(2)}`; chunk.push([ [probeName], {}, function (__webpack_require__) { webpackRequire = __webpack_require__; } ]); if (!webpackRequire) { pkOfficialHlsProbeFailed = true; return null; } const mod = webpackRequire(64945); const HlsCtor = mod && (mod.Ay || mod.default || mod.Hls); if ( typeof HlsCtor === 'function' && HlsCtor.Events && typeof HlsCtor.isSupported === 'function' ) { pkOfficialHlsCtorCache = HlsCtor; return pkOfficialHlsCtorCache; } pkOfficialHlsProbeFailed = true; console.warn('[Hls] Official PikPak Hls core module found, but constructor is invalid:', mod); return null; } catch (e) { pkOfficialHlsProbeFailed = true; console.warn('[Hls] Failed to capture official PikPak Hls core:', e); return null; } }; const isLikelyHevcCodec = (codecText) => { const s = String(codecText || '').toLowerCase(); return s.includes('hevc') || s.includes('h265') || s.includes('h.265') || s.includes('hvc1') || s.includes('hev1'); }; const getPikPakMediaCodecText = (m) => { const v = (m && m.video) || {}; const p = (m && m.params) || {}; return [ v.video_codec, v.codec, v.codec_name, v.mime_type, m && m.video_codec, m && m.codec, m && m.codec_name, m && m.mime_type, p.video_codec, p.codec, m && m.video_stream_id ].filter(Boolean).join(' '); }; let pkHevcWebPlaybackSupportCache = null; const hasLikelyHevcWebPlaybackSupport = () => { if (pkHevcWebPlaybackSupportCache !== null) return pkHevcWebPlaybackSupportCache; let supported = false; try { const video = document.createElement('video'); const samples = [ 'video/mp4; codecs="hvc1.1.6.L93.B0"', 'video/mp4; codecs="hev1.1.6.L93.B0"', 'video/mp4; codecs="hvc1.1.6.L120.B0"', 'video/mp4; codecs="hev1.1.6.L120.B0"' ]; supported = samples.some(mime => { const nativeCanPlay = !!(video.canPlayType && video.canPlayType(mime)); const mseCanPlay = !!(window.MediaSource && MediaSource.isTypeSupported && MediaSource.isTypeSupported(mime)); return nativeCanPlay || mseCanPlay; }); } catch (e) { supported = false; } pkHevcWebPlaybackSupportCache = supported; return supported; }; const getPikPakQualityCodecTextForSniff = (q) => { return [ q && q.codecText, q && q.videoType, q && q.rawName, q && q.name ].filter(Boolean).join(' '); }; const isPikPakQualityLikelyHevc = (q) => { if (!q) return false; return !!q.isLikelyHevcTranscode || isLikelyHevcCodec(getPikPakQualityCodecTextForSniff(q)); }; const shouldBlockWebPlaybackByOfficialSniff = (q) => { return isPikPakQualityLikelyHevc(q) && !hasLikelyHevcWebPlaybackSupport(); }; const normalizePikPakTsM3u8 = (rawText) => { let text = String(rawText || '').replace(/\r\n/g, '\n'); if (!text.includes('#EXTM3U')) return text; if (!text.includes('#EXT-X-ALLOW-CACHE')) { if (text.includes('#EXT-X-MEDIA-SEQUENCE')) { text = text.replace( /(#EXT-X-MEDIA-SEQUENCE:[^\n]*\n)/, '$1#EXT-X-ALLOW-CACHE:YES\n' ); } else { text = text.replace( /(#EXTM3U\n)/, '$1#EXT-X-ALLOW-CACHE:YES\n' ); } } text = text.replace( /(#EXT-X-BYTERANGE:[^\n]+)\n(#EXTINF:[^\n]+)\n/g, '$2\n$1\n' ); return text; }; const getPikPakQualityRank = (name) => { const n = String(name || '').toUpperCase(); if (n.includes('1080')) return 1080; if (n.includes('720')) return 720; if (n.includes('480')) return 480; return 0; }; const getPikPakQualityResolution = (q) => { const width = parseInt(q.width || q.videoWidth || 0); const height = parseInt(q.height || q.videoHeight || 0); if (width > 0 && height > 0) return `${width}x${height}`; const rank = getPikPakQualityRank(q.name || q.rawName); if (rank >= 1080) return '1920x1080'; if (rank >= 720) return '1280x720'; if (rank >= 480) return '852x480'; return '1280x720'; }; const getPikPakQualityBandwidth = (q) => { const explicit = parseInt(q.bandwidth || q.bitrate || q.bitRate || 0); if (explicit > 0) return explicit; const rank = getPikPakQualityRank(q.name || q.rawName); if (rank >= 1080) return 2664403; if (rank >= 720) return 1528012; if (rank >= 480) return 1028110; return 1000000; }; const getPikPakQualityCodec = (q) => { const codecText = String(q.codecText || '').toLowerCase(); if ( codecText.includes('avc1') || codecText.includes('h264') || codecText.includes('h.264') ) { return 'avc1.640028,mp4a.40.2'; } if ( q.isLikelyHevcTranscode || isLikelyHevcCodec(codecText) || codecText.includes('mpegts') || !codecText ) { return 'hev1.1.6.L93.B0,mp4a.40.2'; } return 'hev1.1.6.L93.B0,mp4a.40.2'; }; const buildPikPakMasterM3u8FromQualityList = (list) => { const transcodes = (Array.isArray(list) ? list : []) .filter(q => q && !q.isOriginal) .filter(q => isPikPakTsDownloaderUrl(q.link || q.url)) .sort((a, b) => getPikPakQualityRank(b.name || b.rawName) - getPikPakQualityRank(a.name || a.rawName)); if (!transcodes.length) return ''; const lines = ['#EXTM3U']; transcodes.forEach(q => { const url = q.link || q.url; const name = String(q.name || q.rawName || '').trim() || 'Auto'; const bandwidth = getPikPakQualityBandwidth(q); const codec = getPikPakQualityCodec(q); const resolution = getPikPakQualityResolution(q); lines.push(`#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth},CODECS="${codec}",RESOLUTION=${resolution},NAME="${name}"`); lines.push(url); }); return lines.join('\n') + '\n'; }; const findPikPakHlsLevelIndex = (hls, targetName, targetUrl) => { const levels = (hls && Array.isArray(hls.levels)) ? hls.levels : []; const wantedName = String(targetName || '').toUpperCase(); const wantedRank = getPikPakQualityRank(wantedName); let idx = levels.findIndex(l => { const attrs = l && l.attrs ? l.attrs : {}; const levelName = String(l.name || attrs.NAME || '').toUpperCase(); return wantedName && levelName === wantedName; }); if (idx >= 0) return idx; idx = levels.findIndex(l => { const h = parseInt(l && l.height || 0); if (!wantedRank) return false; if (wantedRank >= 1080) return h >= 900; if (wantedRank >= 720) return h >= 600 && h < 900; if (wantedRank >= 480) return h > 0 && h < 600; return false; }); if (idx >= 0) return idx; const wantedUrl = String(targetUrl || ''); if (wantedUrl) { idx = levels.findIndex(l => { const u = String((l && l.url && (Array.isArray(l.url) ? l.url[0] : l.url)) || ''); return u && wantedUrl && (u === wantedUrl || u.includes(wantedUrl) || wantedUrl.includes(u)); }); } return idx; }; const applyPikPakPreferredHlsLevel = (hls, targetName, targetUrl) => { const idx = findPikPakHlsLevelIndex(hls, targetName, targetUrl); if (idx < 0) return false; try { hls.currentLevel = idx; hls.nextLevel = idx; hls.loadLevel = idx; return true; } catch (e) { console.warn('[Hls] Failed to select PikPak master level:', e); return false; } }; const normalizeDefaultVideoQuality = (val) => { const v = String(val || '').toLowerCase(); if (v === '1080p' || v === '720p' || v === '480p') return v; return 'original'; }; const getDefaultVideoQualityPref = () => { return normalizeDefaultVideoQuality(gmGet('pk_default_video_quality', 'original')); }; const normalizeDefaultOpenPlayer = (val) => { const v = String(val || '').toLowerCase(); return v === 'potplayer' ? 'potplayer' : 'script'; }; const getDefaultOpenPlayerPref = () => { return normalizeDefaultOpenPlayer(gmGet('pk_default_open_player', 'script')); }; const shouldLoadVideoProgressCache = () => { return gmGet('pk_video_load_progress_cache', true) !== false; }; const parseOfficialPlaySeconds = (v) => { const n = parseFloat(v || 0); return Number.isFinite(n) ? n : 0; }; const fetchOfficialPlayHistoryProgress = async (fileId) => { const targetId = String(fileId || ''); if (!targetId) return null; const filters = encodeURIComponent(JSON.stringify({ type: { in: 'TYPE_PLAY' } })); const limit = 50; const maxPages = 3; let pageToken = ''; for (let page = 0; page < maxPages; page++) { let timer = 0; const controller = typeof AbortController !== 'undefined' ? new AbortController() : null; try { if (controller) timer = setTimeout(() => controller.abort(), 3500); const url = `https://api-drive.mypikpak.com/drive/v1/events?thumbnail_size=SIZE_MEDIUM&limit=${limit}&filters=${filters}${pageToken ? `&page_token=${encodeURIComponent(pageToken)}` : ''}&_t=${Date.now()}`; const res = await fetch(url, { headers: getHeaders(), signal: controller ? controller.signal : undefined }); if (!res.ok) return null; const data = await res.json(); const events = Array.isArray(data.events) ? data.events : (Array.isArray(data.data) ? data.data : []); for (const ev of events) { const ref = ev.reference_resource || {}; const officialEventId = String(ev.id || ev.event_id || ''); const evFileId = String(ev.file_id || ref.id || ''); if (evFileId !== targetId) continue; const params = ev.params || {}; const refParams = ref.params || {}; const playSeconds = parseOfficialPlaySeconds(params.play_seconds); const playDuration = parseOfficialPlaySeconds(params.play_duration || refParams.duration); const updatedTime = Date.parse(ev.updated_time || ev.create_time || '') || Date.now(); if (playSeconds > 5 && !(playDuration > 0 && playDuration - playSeconds <= 5)) { return { t: playSeconds, d: playDuration || 0, ts: updatedTime, source: 'official', officialEventId }; } return null; } pageToken = data.next_page_token || data.nextPageToken || ''; if (!pageToken) break; } catch (e) { if (!e || e.name !== 'AbortError') console.warn('[OfficialProgress] Fetch failed:', e); return null; } finally { if (timer) clearTimeout(timer); } } return null; }; const deleteOfficialPlayHistoryEvents = async (eventIds) => { const ids = Array.from(new Set((eventIds || []).map(id => String(id || '').trim()).filter(Boolean))); if (!ids.length) return true; const headers = Object.assign({}, getHeaders(), { 'Content-Type': 'application/json' }); for (let i = 0; i < ids.length; i += 100) { const chunk = ids.slice(i, i + 100); const res = await fetch('https://api-drive.mypikpak.com/drive/v1/events:delete', { method: 'POST', headers, body: JSON.stringify({ ids: chunk }) }); if (!res.ok) { console.warn('[OfficialHistory] Delete failed:', res.status, chunk); return false; } } return true; }; const clearOfficialPlayHistory = async () => { const headers = Object.assign({}, getHeaders(), { 'Content-Type': 'application/json' }); const res = await fetch('https://api-drive.mypikpak.com/drive/v1/events:clear', { method: 'POST', headers, body: JSON.stringify({ types: ['TYPE_PLAY'] }) }); if (!res.ok) { console.warn('[OfficialHistory] Clear failed:', res.status); return false; } return true; }; const postOfficialPlayProgress = async (fileId, playSeconds, playDuration) => { const targetId = String(fileId || ''); const seconds = Math.round(Number(playSeconds || 0)); const duration = Math.round(Number(playDuration || 0)); if (!targetId || !(seconds > 1)) return false; if (duration > 0 && duration - seconds <= 5) return false; try { const headers = Object.assign({}, getHeaders(), { 'Content-Type': 'application/json' }); const res = await fetch('https://api-drive.mypikpak.com/drive/v1/events', { method: 'POST', headers, body: JSON.stringify({ event: { type: 'TYPE_PLAY', file_id: targetId, params: { play_duration: String(duration || 0), play_seconds: String(seconds) } } }) }); if (!res.ok) { console.warn('[OfficialProgress] POST failed:', res.status); return false; } return true; } catch (e) { console.warn('[OfficialProgress] POST error:', e); return false; } }; const findQualityByDefaultPref = (list, pref) => { const p = normalizeDefaultVideoQuality(pref); const arr = Array.isArray(list) ? list : []; if (p === 'original') { return arr.find(q => q && q.isOriginal) || null; } const target = p.toUpperCase(); return arr.find(q => { const n = String((q && q.name) || '').toUpperCase(); return n === target || n.includes(target.replace('P', '')); }) || null; }; const generateQualityList = (data) => { const list = []; const seenNames = new Set(); const officialOriginMedia = (data.medias && Array.isArray(data.medias)) ? data.medias.find(m => { const rawName = String((m && (m.media_name || m.resolution_name || m.video_stream_id)) || '').trim().toLowerCase(); return !!(m && m.link && m.link.url && (m.is_origin || rawName.includes('原画') || rawName.includes('original'))); }) : null; const officialOriginVideo = (officialOriginMedia && officialOriginMedia.video) || {}; const officialOriginUrl = officialOriginMedia && officialOriginMedia.link ? officialOriginMedia.link.url : ''; const originUrl = officialOriginUrl || data.web_content_link || ''; if (originUrl) { list.push({ name: L.str_original, url: originUrl, isOriginal: true, rawName: (officialOriginMedia && (officialOriginMedia.media_name || officialOriginMedia.resolution_name || officialOriginMedia.video_stream_id)) || L.str_original, codecText: officialOriginMedia ? getPikPakMediaCodecText(officialOriginMedia) : '', videoType: String((officialOriginVideo && officialOriginVideo.video_type) || (officialOriginMedia && officialOriginMedia.video_type) || '').toLowerCase(), width: parseInt((officialOriginVideo && (officialOriginVideo.width || officialOriginVideo.video_width)) || (officialOriginMedia && (officialOriginMedia.width || officialOriginMedia.video_width)) || 0), height: parseInt((officialOriginVideo && (officialOriginVideo.height || officialOriginVideo.video_height)) || (officialOriginMedia && (officialOriginMedia.height || officialOriginMedia.video_height)) || 0), bandwidth: parseInt((officialOriginVideo && (officialOriginVideo.bitrate || officialOriginVideo.bit_rate)) || (officialOriginMedia && (officialOriginMedia.bitrate || officialOriginMedia.bit_rate || officialOriginMedia.bandwidth)) || 0) }); seenNames.add(L.str_original); } const transcodeList = []; if (data.medias && Array.isArray(data.medias)) { data.medias.forEach(m => { if (!m.link || !m.link.url) return; const rawOriginName = String(m.media_name || m.resolution_name || m.video_stream_id || '').trim().toLowerCase(); if (m.is_origin || rawOriginName.includes('原画') || rawOriginName.includes('original')) return; let name = m.resolution_name || m.video_stream_id || 'Unknown'; let streamUrl = m.link.url; const videoInfo = (m && m.video) || {}; const videoType = String(videoInfo.video_type || m.video_type || '').toLowerCase(); const codecText = getPikPakMediaCodecText(m); const isLikelyHevcTranscode = isLikelyHevcCodec(codecText); const width = parseInt(videoInfo.width || videoInfo.video_width || m.width || m.video_width || 0); const height = parseInt(videoInfo.height || videoInfo.video_height || m.height || m.video_height || 0); const bandwidth = parseInt(videoInfo.bitrate || videoInfo.bit_rate || m.bitrate || m.bit_rate || m.bandwidth || 0); if (videoType === 'mpegts') { streamUrl = buildPikPakTsHlsUrl(streamUrl); } const rawQualityName = String(name || '').trim(); const qualityKey = rawQualityName.toUpperCase(); if (qualityKey.includes('1080') || qualityKey === 'FHD') name = '1080P'; else if (qualityKey.includes('720') || qualityKey === 'HD') name = '720P'; else if (qualityKey.includes('480') || qualityKey === 'SD') name = '480P'; else return; if (seenNames.has(name)) return; seenNames.add(name); transcodeList.push({ name: name, url: streamUrl, isOriginal: false, rawName: name, codecText, isLikelyHevcTranscode, videoType, width, height, bandwidth }); }); } transcodeList.forEach(t => { list.push({ name: t.name, url: t.url, isOriginal: false, rawName: t.rawName || t.name, codecText: t.codecText || '', isLikelyHevcTranscode: !!t.isLikelyHevcTranscode, videoType: t.videoType || '', width: t.width || 0, height: t.height || 0, bandwidth: t.bandwidth || 0 }); }); list.sort((a, b) => { if (a.isOriginal) return -1; if (b.isOriginal) return 1; const getRank = (s) => { const n = String(s || '').toUpperCase(); if (n.includes('1080')) return 80; if (n.includes('720')) return 70; if (n.includes('480')) return 60; return 0; }; const rA = getRank(a.name); const rB = getRank(b.name); if (rA !== rB) return rB - rA; return b.name.localeCompare(a.name, undefined, { numeric: true }); }); return list; }; const getBestSource = (data) => { const generatedList = generateQualityList(data); const list = generatedList.map(item => ({ name: item.name, link: item.url, active: false, isOriginal: item.isOriginal, rawName: item.rawName || item.name, codecText: item.codecText || '', isLikelyHevcTranscode: !!item.isLikelyHevcTranscode, videoType: item.videoType || '', width: item.width || 0, height: item.height || 0, bandwidth: item.bandwidth || 0 })); let bestMatch = null; const defaultQualityPref = getDefaultVideoQualityPref(); const preferredMatch = findQualityByDefaultPref(list, defaultQualityPref); const originalMatch = list.find(item => item && item.isOriginal) || null; if (preferredMatch) { bestMatch = preferredMatch; } else if (defaultQualityPref !== 'original' && originalMatch) { bestMatch = originalMatch; } else if (list.length > 0) { bestMatch = list[0]; } else { bestMatch = { name: L.str_original, link: data.web_content_link, active: true, isOriginal: true }; list.push(bestMatch); } bestMatch.active = true; return { src: bestMatch.link, name: bestMatch.name, list: list }; }; const launchPotPlayerWithFallback = (cleanUrl, opts = {}) => { const L = getStrings(); const button = opts.button || null; const timeout = opts.timeout || 3500; const source = normalizePotPlayerLaunchSource(opts.source); const launchAt = Date.now(); let copiedAt = launchAt; let likelyOpened = false; let finished = false; let timer = null; let confirmTimer = null; let focusProbe = null; const markOpened = () => { likelyOpened = true; }; const onVisibility = () => { if (document.hidden) markOpened(); }; const cleanup = () => { window.removeEventListener('blur', markOpened); document.removeEventListener('visibilitychange', onVisibility, true); if (timer) clearTimeout(timer); if (confirmTimer) clearTimeout(confirmTimer); if (focusProbe) clearInterval(focusProbe); }; recordPotPlayerLaunchAttempt(source, launchAt); try { GM_setClipboard(cleanUrl); copiedAt = Date.now(); } catch (e) {} if (button) { button.disabled = true; button.textContent = L.msg_potplayer_launching; } window.addEventListener('blur', markOpened, { once: true }); document.addEventListener('visibilitychange', onVisibility, true); if (typeof document.hasFocus === 'function') { focusProbe = setInterval(() => { if (!document.hasFocus()) markOpened(); }, 120); } try { const ua = navigator.userAgent.replace(/"/g, ''); const cmd = `${cleanUrl} /user_agent="${ua}" /referer="https://mypikpak.com/"`; window.location.href = `potplayer://${cmd}`; } catch (e) {} const finishLikelyOpen = () => { if (finished) return; finished = true; cleanup(); recordPotPlayerLaunchResult('likely_open', { source, launchAt }); if (typeof opts.onLikelyOpen === 'function') opts.onLikelyOpen(); }; const finishLikelyFail = () => { if (finished) return; finished = true; cleanup(); recordPotPlayerLaunchResult('likely_fail', { source, launchAt, copiedAt }); const failToastDuration = opts.failToastDuration || 6500; const failCloseDelay = opts.failCloseDelay || 3200; if (!opts.silentLikelyFailToast) showToast(L.msg_potplayer_maybe_failed, 'warning', failToastDuration); const notifyLikelyFail = () => { if (typeof opts.onLikelyFail === 'function') { opts.onLikelyFail({ source, launchAt, copiedAt, cleanUrl, autoRepairPrompt: opts.autoRepairPrompt, autoRepairPromptDelay: opts.autoRepairPromptDelay }); } }; if (button) { button.disabled = true; button.textContent = L.msg_potplayer_link_copied; button.classList.add('pk-copy-success-freeze'); button.style.setProperty('background', '#52c41a', 'important'); button.style.setProperty('color', '#fff', 'important'); } setTimeout(notifyLikelyFail, failCloseDelay); }; timer = setTimeout(() => { if (finished) return; if (likelyOpened) { finishLikelyOpen(); return; } const confirmDelay = Number.isFinite(Number(opts.confirmFailDelay)) ? Math.max(0, Number(opts.confirmFailDelay)) : 0; if (confirmDelay > 0) { confirmTimer = setTimeout(() => { if (finished) return; if (likelyOpened) { finishLikelyOpen(); return; } finishLikelyFail(); }, confirmDelay); return; } finishLikelyFail(); }, timeout); return cleanup; }; const openPotPlayerProtocolRepairHelper = (playUrl, opts = {}) => { const L = getStrings(); const cleanUrl = String(playUrl || ''); const isAutoPrompt = opts.autoPrompt === true; const title = isAutoPrompt ? L.potplayer_fix_auto_title : L.potplayer_fix_title; const autoReason = isAutoPrompt ? `
${L.potplayer_fix_reason_auto}
` : ''; const protocolState = readPotPlayerProtocolState(); const savedCustomPath = normalizePotPlayerCustomPath(protocolState.customPlayerPath); const versionOutdated = !!protocolState.confirmedRepairVersion && protocolState.confirmedRepairVersion !== CONF.potplayerProtocolRepairVersion; const getPathStatusText = () => { const state = readPotPlayerProtocolState(); const checked = validatePotPlayerCustomPath(state.customPlayerPath); if (!checked.ok) return L.potplayer_fix_path_status_empty; return (state.confirmedRepairVersion === CONF.potplayerProtocolRepairVersion && state.pathBoundRepairVersion === CONF.potplayerProtocolRepairVersion) ? L.potplayer_fix_path_status_confirmed : L.potplayer_fix_path_status_unconfirmed; }; const m = showModal(`

${title}

${autoReason}
${L.potplayer_fix_desc}
${L.potplayer_fix_manual_tip}
${versionOutdated ? `
${L.potplayer_fix_version_outdated}
` : ''}
${L.potplayer_fix_advanced_desc}
${getPathStatusText()}
${L.potplayer_fix_browser_policy_desc}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { modalBox.classList.add('pk-potfix-modal'); Object.assign(modalBox.style, { width: '480px', padding: '24px', height: 'auto', minHeight: 'auto' }); modalBox.style.setProperty('max-height', '92vh', 'important'); modalBox.style.setProperty('overflow-x', 'hidden', 'important'); modalBox.style.setProperty('overflow-y', 'auto', 'important'); modalBox.style.setProperty('clip-path', 'inset(0 round 12px)', 'important'); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '18px', right: '18px' }); } const pathInput = m.querySelector('#pk_potfix_path'); const pathStatus = m.querySelector('#pk_potfix_path_status'); const refreshPathStatus = () => { if (pathStatus) pathStatus.textContent = getPathStatusText(); }; m.querySelector('#pk_potfix_copy').onclick = () => { try { GM_setClipboard(cleanUrl); } catch (e) {} showToast(L.potplayer_fix_copy_success || L.msg_copy_success); }; m.querySelector('#pk_potfix_delete').onclick = () => { downloadPotPlayerRegFile('delete'); showToast(L.potplayer_fix_delete_downloaded); }; m.querySelector('#pk_potfix_save_path').onclick = () => { const result = savePotPlayerCustomPath(pathInput ? pathInput.value : ''); if (!result.ok) { showToast(L.potplayer_fix_path_invalid, 'warning'); return; } if (pathInput) pathInput.value = result.path; refreshPathStatus(); showToast(result.changed && result.wasConfigured ? L.potplayer_fix_path_changed : L.potplayer_fix_path_saved, result.changed && result.wasConfigured ? 'warning' : 'success'); }; m.querySelector('#pk_potfix_custom_install').onclick = () => { const result = savePotPlayerCustomPath(pathInput ? pathInput.value : ''); if (!result.ok) { showToast(L.potplayer_fix_path_invalid, 'warning'); return; } if (pathInput) pathInput.value = result.path; refreshPathStatus(); if (!downloadPotPlayerRegFile('install')) { showToast(L.potplayer_fix_path_invalid, 'warning'); return; } showToast(L.potplayer_fix_install_downloaded); }; m.querySelector('#pk_potfix_browser_policy').onclick = () => { downloadPotPlayerRegFile('browser_policy'); showToast(L.potplayer_fix_browser_policy_downloaded); }; m.querySelector('#pk_potfix_retry').onclick = () => { const savedPath = getValidSavedPotPlayerPath(); if (!savedPath) { const result = savePotPlayerCustomPath(pathInput ? pathInput.value : ''); if (!result.ok) { showToast(L.potplayer_fix_retry_need_path || L.potplayer_fix_path_invalid, 'warning'); return; } if (pathInput) pathInput.value = result.path; refreshPathStatus(); showToast(L.potplayer_fix_retry_need_path || L.potplayer_fix_path_saved, 'warning'); return; } recordPotPlayerProtocolUserFixed(); clearPotPlayerLaunchFailCount(); showToast(L.potplayer_fix_confirmed); m.remove(); launchPotPlayerWithFallback(cleanUrl, { button: opts.button || null, source: typeof opts.source === 'string' ? opts.source : 'normal', failToastDuration: 6500, failCloseDelay: 3200, confirmFailDelay: CONF.potplayerPostRepairConfirmDelay, autoRepairPrompt: false, onLikelyOpen: () => { if (typeof opts.onRetryLikelyOpen === 'function') opts.onRetryLikelyOpen(); }, onLikelyFail: () => { if (typeof opts.onRetryLikelyFail === 'function') opts.onRetryLikelyFail(); } }); }; m.querySelector('#pk_potfix_skip_today').onclick = () => { suppressPotPlayerAutoPromptToday(); m.remove(); showToast(L.potplayer_fix_skip_today_done); }; m.querySelector('#pk_potfix_never_auto').onclick = () => { disablePotPlayerAutoPrompt(); m.remove(); showToast(L.potplayer_fix_never_auto_done); }; m.querySelector('#pk_potfix_close').onclick = () => m.remove(); m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); m.remove(); } }); return m; }; async function resolveAudioPlayableSource(item, force = false) { const L = getStrings(); if (!item) throw new Error(L.audio_link_missing); const existing = force ? '' : getAudioDirectUrl(item); if (existing) return { item, url: existing }; try { if (item._isShareItem) { const detail = force ? await fetchShareFileInfo(item) : await resolveSharePlayableFile(item); if (force) mergeSharePlayableDetail(item, detail); const url = getAudioDirectUrl(item) || getAudioDirectUrl(detail) || getShareDirectUrl(detail); if (!url) throw new Error(L.audio_share_unsupported || L.audio_link_missing); item.web_content_link = url; return { item, url }; } const fileId = getAudioPhysicalId(item); if (!fileId) throw new Error(L.audio_link_missing); const detail = await apiGet(fileId); if (detail) { if (detail.web_content_link) item.web_content_link = detail.web_content_link; if (detail.links) item.links = detail.links; if (detail.mime_type) item.mime_type = detail.mime_type; if (detail.size) item.size = detail.size; if (detail.name) item.name = detail.name; if (detail.icon_link) item.icon_link = detail.icon_link; if (detail.thumbnail_link) item.thumbnail_link = detail.thumbnail_link; } const url = getAudioDirectUrl(detail) || getAudioDirectUrl(item); if (!url) throw new Error(L.audio_link_missing); return { item, url }; } catch (e) { const msg = String((e && e.message) || ''); if ([L.audio_link_missing, L.audio_share_unsupported, L.audio_link_expired, L.audio_format_unsupported, L.audio_play_failed].includes(msg)) throw e; throw new Error(item._isShareItem ? (L.audio_share_unsupported || L.audio_link_missing) : L.audio_link_missing); } } const pkAudioCoverCache = new Map(); const pkAudioCoverFailSet = new Set(); const getAudioCoverKey = (item) => String(getAudioPhysicalId(item) || (item && (item.id || item.file_id || item.name)) || ''); const normalizeAudioCoverUrl = (url) => String(url || '').replace('SIZE_MEDIUM', 'SIZE_LARGE'); const cacheAudioCoverUrl = (key, url, revoke = false) => { if (!key || !url) return url; const old = pkAudioCoverCache.get(key); if (old && old !== url && old.startsWith('blob:')) { try { URL.revokeObjectURL(old); } catch (e) {} } pkAudioCoverCache.set(key, url); while (pkAudioCoverCache.size > (CONF.audioCoverCacheMax || 80)) { const firstKey = pkAudioCoverCache.keys().next().value; const firstUrl = pkAudioCoverCache.get(firstKey); pkAudioCoverCache.delete(firstKey); if (revoke && firstUrl && firstUrl.startsWith('blob:')) { try { URL.revokeObjectURL(firstUrl); } catch (e) {} } } return url; }; const readSynchsafeInt = (bytes, offset) => ((bytes[offset] & 0x7f) << 21) | ((bytes[offset + 1] & 0x7f) << 14) | ((bytes[offset + 2] & 0x7f) << 7) | (bytes[offset + 3] & 0x7f); const readUtf16TerminatedEnd = (bytes, start, end) => { for (let i = start; i + 1 < end; i += 2) { if (bytes[i] === 0 && bytes[i + 1] === 0) return i + 2; } return start; }; const parseId3ApicCover = (buffer) => { const bytes = new Uint8Array(buffer || 0); if (bytes.length < 16 || bytes[0] !== 0x49 || bytes[1] !== 0x44 || bytes[2] !== 0x33) return null; const ver = bytes[3]; const tagSize = readSynchsafeInt(bytes, 6); let offset = 10; const end = Math.min(bytes.length, 10 + tagSize); while (offset + (ver === 2 ? 6 : 10) <= end) { if (ver === 2) { const id = String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2]); const size = (bytes[offset + 3] << 16) | (bytes[offset + 4] << 8) | bytes[offset + 5]; offset += 6; if (!size || offset + size > end) break; if (id === 'PIC') { const frameEnd = offset + size; const mime = `image/${String.fromCharCode(bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]).toLowerCase().replace('jpg', 'jpeg')}`; let imgStart = offset + 5; const enc = bytes[offset] || 0; if (enc === 1 || enc === 2) imgStart = readUtf16TerminatedEnd(bytes, imgStart, frameEnd); else { while (imgStart < frameEnd && bytes[imgStart] !== 0) imgStart++; imgStart++; } if (imgStart < frameEnd) return { mime, bytes: bytes.slice(imgStart, frameEnd) }; } offset += size; continue; } const id = String.fromCharCode(bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]); const size = ver === 4 ? readSynchsafeInt(bytes, offset + 4) : ((bytes[offset + 4] << 24) | (bytes[offset + 5] << 16) | (bytes[offset + 6] << 8) | bytes[offset + 7]) >>> 0; offset += 10; if (!size || offset + size > end) break; if (id === 'APIC') { const frameEnd = offset + size; const enc = bytes[offset] || 0; let p = offset + 1; const mimeStart = p; while (p < frameEnd && bytes[p] !== 0) p++; const mime = Array.from(bytes.slice(mimeStart, p)).map(c => String.fromCharCode(c)).join('') || 'image/jpeg'; p++; p++; if (enc === 1 || enc === 2) p = readUtf16TerminatedEnd(bytes, p, frameEnd); else { while (p < frameEnd && bytes[p] !== 0) p++; p++; } if (p < frameEnd) return { mime, bytes: bytes.slice(p, frameEnd) }; } offset += size; } return null; }; const getAudioDirectCoverUrl = (item) => { if (!item) return ''; const thumb = normalizeAudioCoverUrl(item.thumbnail_link); if (thumb && thumb !== item.icon_link) return thumb; return ''; }; const findAudioSiblingCoverUrl = (item) => { if (!item || !Array.isArray(S.display)) return ''; const parentId = String(item.parent_id || item._shareParentId || ''); const coverNameScore = (name) => { const n = String(name || '').toLowerCase(); if (/^(cover|folder|albumart)\.(jpe?g|png|webp)$/.test(n)) return 1; if (/^(cover_thumb|__ia_thumb)\.(jpe?g|png|webp)$/.test(n)) return 2; if (/(cover|front|folder|album).*\.(jpe?g|png|webp)$/.test(n)) return 3; return 0; }; let best = null; let bestScore = 0; for (const it of S.display) { if (!isImageLikeItem(it)) continue; const score = coverNameScore(it.name); if (!score) continue; const itParent = String(it.parent_id || it._shareParentId || ''); if (parentId && itParent && itParent !== parentId) continue; const url = normalizeAudioCoverUrl(it.thumbnail_link); if (!url || url === it.icon_link) continue; if (!best || score < bestScore) { best = url; bestScore = score; } } return best || ''; }; async function fetchAudioEmbeddedCoverUrl(audioUrl, item) { const key = getAudioCoverKey(item); if (!key || !audioUrl || pkAudioCoverFailSet.has(key)) return ''; const ext = getItemExt(item).toLowerCase(); const mime = String((item && item.mime_type) || '').toLowerCase(); const urlLower = String(audioUrl || '').toLowerCase(); if (!(ext === 'mp3' || mime.includes('mpeg') || urlLower.includes('.mp3'))) return ''; try { const end = Math.max(65535, (CONF.audioCoverRangeBytes || 196608) - 1); const opts = { headers: { Range: `bytes=0-${end}` }, referrerPolicy: 'no-referrer' }; if (window.AbortSignal && AbortSignal.timeout) opts.signal = AbortSignal.timeout(9000); const res = await fetch(audioUrl, opts); if (!(res && (res.ok || res.status === 206))) throw new Error('audio cover fetch failed'); const parsed = parseId3ApicCover(await res.arrayBuffer()); if (!parsed || !parsed.bytes || !parsed.bytes.length) throw new Error('audio cover missing'); const blob = new Blob([parsed.bytes], { type: parsed.mime || 'image/jpeg' }); return cacheAudioCoverUrl(key, URL.createObjectURL(blob), true); } catch (e) { pkAudioCoverFailSet.add(key); return ''; } } let pkAudioMiniSession = null; function isAudioMiniActive() { return !!((pkAudioMiniSession && typeof pkAudioMiniSession.isMiniActive === 'function' && pkAudioMiniSession.isMiniActive()) || document.getElementById('pk-audio-mini')); } function getAudioMiniSavedPosition() { try { const pos = JSON.parse(gmGet(CONF.audioMiniPosKey, '') || 'null'); if (!pos) return null; const x = pos.x === 'right' ? 'right' : 'left'; const y = pos.y === 'bottom' ? 'bottom' : 'top'; const hx = Number(pos[x]); const vy = Number(pos[y]); if (Number.isFinite(hx) && Number.isFinite(vy)) return { x, y, [x]: hx, [y]: vy }; if (Number.isFinite(Number(pos.left)) && Number.isFinite(Number(pos.top))) return { x:'left', y:'top', left:Number(pos.left), top:Number(pos.top) }; return null; } catch (e) { return null; } } function applyAudioMiniPositionState(mini, pos) { if (!mini || !pos) return false; const x = pos.x === 'right' ? 'right' : 'left'; const y = pos.y === 'bottom' ? 'bottom' : 'top'; const hx = Number(pos[x]); const vy = Number(pos[y]); if (!Number.isFinite(hx) || !Number.isFinite(vy)) return false; if (x === 'right') { mini.style.right = `${Math.max(0, Math.round(hx))}px`; mini.style.left = 'auto'; } else { mini.style.left = `${Math.max(0, Math.round(hx))}px`; mini.style.right = 'auto'; } if (y === 'bottom') { mini.style.bottom = `${Math.max(0, Math.round(vy))}px`; mini.style.top = 'auto'; } else { mini.style.top = `${Math.max(0, Math.round(vy))}px`; mini.style.bottom = 'auto'; } return true; } function saveAudioMiniPosition(mini = document.getElementById('pk-audio-mini')) { if (!mini) return; const rect = mini.getBoundingClientRect(); if (!rect || !Number.isFinite(rect.left) || !Number.isFinite(rect.top) || !Number.isFinite(rect.right) || !Number.isFinite(rect.bottom)) return; const left = Math.max(0, rect.left); const right = Math.max(0, window.innerWidth - rect.right); const top = Math.max(0, rect.top); const bottom = Math.max(0, window.innerHeight - rect.bottom); const x = right <= left ? 'right' : 'left'; const y = bottom <= top ? 'bottom' : 'top'; const pos = { x, y, left: Math.round(left), right: Math.round(right), top: Math.round(top), bottom: Math.round(bottom) }; gmSet(CONF.audioMiniPosKey, JSON.stringify(pos)); applyAudioMiniPositionState(mini, pos); } function applyAudioMiniSavedPosition(mini = document.getElementById('pk-audio-mini')) { if (!mini) return false; return applyAudioMiniPositionState(mini, getAudioMiniSavedPosition()); } function getAudioMiniThemeSource() { return document.querySelector('.pk-ov') || ((typeof UI !== 'undefined' && UI.win && UI.win.isConnected) ? (UI.win.closest && UI.win.closest('.pk-ov')) || UI.win : null) || document.querySelector('#pk-audio-ov') || document.querySelector('.pk-win') || document.documentElement; } function getAudioShellThemeSnapshot() { const savedTheme = gmGet('pk_theme', 'auto'); const sysDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; const src = getAudioMiniThemeSource(); const darkNode = document.querySelector('.pk-ov.pk-dark,.pk-win.pk-dark'); const srcDark = !!(darkNode || (src && src.classList && src.classList.contains('pk-dark')) || (src && src.closest && src.closest('.pk-dark')) || document.documentElement.classList.contains('pk-dark') || (document.body && document.body.classList.contains('pk-dark'))); const isDark = srcDark || savedTheme === 'dark' || (savedTheme === 'auto' && sysDark); const cs = getComputedStyle(src || document.documentElement); return { src, cs, isDark }; } function applyAudioShellThemeVars(el, snap = getAudioShellThemeSnapshot()) { if (!el) return; const { cs, isDark } = snap; el.classList.toggle('pk-dark', !!isDark); const setVar = (name, fallback) => { const v = (cs.getPropertyValue(name) || '').trim(); el.style.setProperty(name, v || fallback); }; setVar('--pk-bg-rgb', isDark ? '32,32,32' : '255,255,255'); setVar('--pk-bg', isDark ? '#202020' : '#fff'); setVar('--pk-fg', isDark ? '#f5f5f5' : '#222'); setVar('--pk-bd', isDark ? '#333' : 'rgba(0,0,0,.12)'); setVar('--pk-hl', isDark ? 'rgba(255,255,255,.08)' : 'rgba(0,0,0,.06)'); setVar('--pk-pri', isDark ? '#4cc2ff' : '#0067c0'); } function syncAudioFullPlayerTheme(shell = document.getElementById('pk-audio-ov')) { applyAudioShellThemeVars(shell); } function syncAudioMiniTheme(mini = document.getElementById('pk-audio-mini')) { applyAudioShellThemeVars(mini); } function bindAudioMiniTheme(mini) { if (!mini) return; const savedTheme = gmGet('pk_theme', 'auto'); const sysDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; const src = getAudioMiniThemeSource(); const darkNode = document.querySelector('.pk-ov.pk-dark,.pk-win.pk-dark,#pk-audio-ov.pk-dark'); const srcDark = !!(darkNode || (src && src.classList && src.classList.contains('pk-dark')) || (src && src.closest && src.closest('.pk-dark')) || document.documentElement.classList.contains('pk-dark') || (document.body && document.body.classList.contains('pk-dark'))); const isDark = srcDark || savedTheme === 'dark' || (savedTheme === 'auto' && sysDark); mini.classList.toggle('pk-dark', !!isDark); const cs = getComputedStyle(src || document.documentElement); const setVar = (name, fallback) => { const v = (cs.getPropertyValue(name) || '').trim(); mini.style.setProperty(name, v || fallback); }; setVar('--pk-bg-rgb', isDark ? '32,32,32' : '255,255,255'); setVar('--pk-bg', isDark ? '#202020' : '#fff'); setVar('--pk-fg', isDark ? '#f5f5f5' : '#222'); setVar('--pk-bd', isDark ? '#333' : 'rgba(0,0,0,.12)'); setVar('--pk-hl', isDark ? 'rgba(255,255,255,.08)' : 'rgba(0,0,0,.06)'); setVar('--pk-pri', isDark ? '#4cc2ff' : '#0067c0'); } function bindAudioMiniTheme(mini) { if (!mini) return; if (typeof mini._pkAudioMiniCleanupTheme === 'function') mini._pkAudioMiniCleanupTheme(); let themeSyncRaf = 0; const sync = () => { if (themeSyncRaf) cancelAnimationFrame(themeSyncRaf); themeSyncRaf = requestAnimationFrame(() => { themeSyncRaf = 0; syncAudioMiniTheme(mini); }); }; let mq = null; const mqHandler = () => sync(); if (window.matchMedia) { mq = window.matchMedia('(prefers-color-scheme: dark)'); if (mq && mq.addEventListener) mq.addEventListener('change', mqHandler); else if (mq && mq.addListener) mq.addListener(mqHandler); } const mo = typeof MutationObserver !== 'undefined' ? new MutationObserver(sync) : null; if (mo) { const observe = (n) => { if (n) { try { mo.observe(n, { attributes:true, attributeFilter:['class','style'] }); } catch (e) {} } }; observe(document.documentElement); observe(document.body); document.querySelectorAll('.pk-ov,.pk-win,#pk-audio-ov').forEach(observe); } const timer = setInterval(sync, 300); mini._pkAudioMiniCleanupTheme = () => { if (themeSyncRaf) cancelAnimationFrame(themeSyncRaf); clearInterval(timer); if (mo) mo.disconnect(); if (mq && mq.removeEventListener) mq.removeEventListener('change', mqHandler); else if (mq && mq.removeListener) mq.removeListener(mqHandler); }; sync(); } function clampAudioMiniPosition(mini = document.getElementById('pk-audio-mini')) { if (!mini) return; const rect = mini.getBoundingClientRect(); const cs = getComputedStyle(mini); const w = rect.width || mini.offsetWidth || 300; const h = rect.height || mini.offsetHeight || 120; const inlineLeft = Number.parseFloat(mini.style.left); const inlineRight = Number.parseFloat(mini.style.right); const inlineTop = Number.parseFloat(mini.style.top); const inlineBottom = Number.parseFloat(mini.style.bottom); const cssLeft = Number.parseFloat(cs.left); const cssRight = Number.parseFloat(cs.right); const cssTop = Number.parseFloat(cs.top); const cssBottom = Number.parseFloat(cs.bottom); const useRight = Number.isFinite(inlineRight) || (!Number.isFinite(inlineLeft) && Number.isFinite(cssRight) && (!Number.isFinite(cssLeft) || cssRight <= cssLeft)); const useBottom = Number.isFinite(inlineBottom) || (!Number.isFinite(inlineTop) && Number.isFinite(cssBottom) && (!Number.isFinite(cssTop) || cssBottom <= cssTop)); const maxX = Math.max(0, window.innerWidth - w); const maxY = Math.max(0, window.innerHeight - h); let hx = useRight ? (Number.isFinite(inlineRight) ? inlineRight : (Number.isFinite(cssRight) ? cssRight : Math.max(0, window.innerWidth - rect.right))) : (Number.isFinite(inlineLeft) ? inlineLeft : (Number.isFinite(rect.left) ? rect.left : maxX)); let vy = useBottom ? (Number.isFinite(inlineBottom) ? inlineBottom : (Number.isFinite(cssBottom) ? cssBottom : Math.max(0, window.innerHeight - rect.bottom))) : (Number.isFinite(inlineTop) ? inlineTop : (Number.isFinite(rect.top) ? rect.top : maxY)); hx = Math.max(0, Math.min(hx, maxX)); vy = Math.max(0, Math.min(vy, maxY)); applyAudioMiniPositionState(mini, useRight ? { x:'right', y: useBottom ? 'bottom' : 'top', right:hx, [useBottom ? 'bottom' : 'top']:vy } : { x:'left', y: useBottom ? 'bottom' : 'top', left:hx, [useBottom ? 'bottom' : 'top']:vy }); } function makeAudioMiniDraggable(mini, handle) { if (!mini || !handle) return; let dragging = false; let startX = 0; let startY = 0; let startLeft = 0; let startTop = 0; const onMove = (e) => { if (!dragging) return; const rect = mini.getBoundingClientRect(); const w = rect.width || mini.offsetWidth || 300; const h = rect.height || mini.offsetHeight || 120; const maxLeft = Math.max(0, window.innerWidth - w); const maxTop = Math.max(0, window.innerHeight - h); const left = Math.max(0, Math.min(startLeft + e.clientX - startX, maxLeft)); const top = Math.max(0, Math.min(startTop + e.clientY - startY, maxTop)); mini.style.left = `${left}px`; mini.style.top = `${top}px`; mini.style.right = 'auto'; mini.style.bottom = 'auto'; }; const onUp = () => { if (!dragging) return; dragging = false; document.removeEventListener('pointermove', onMove, true); document.removeEventListener('pointerup', onUp, true); clampAudioMiniPosition(mini); saveAudioMiniPosition(mini); }; const onDown = (e) => { if (e.button !== 0 || (e.target && e.target.closest && e.target.closest('.pk-audio-mini-btn,.pk-audio-mini-fold'))) return; e.preventDefault(); const rect = mini.getBoundingClientRect(); dragging = true; startX = e.clientX; startY = e.clientY; startLeft = rect.left; startTop = rect.top; mini.style.left = `${startLeft}px`; mini.style.top = `${startTop}px`; mini.style.right = 'auto'; mini.style.bottom = 'auto'; document.addEventListener('pointermove', onMove, true); document.addEventListener('pointerup', onUp, true); }; const onResize = () => { clampAudioMiniPosition(mini); saveAudioMiniPosition(mini); }; handle.addEventListener('pointerdown', onDown); window.addEventListener('resize', onResize); mini._pkAudioMiniCleanupDrag = () => { dragging = false; handle.removeEventListener('pointerdown', onDown); window.removeEventListener('resize', onResize); document.removeEventListener('pointermove', onMove, true); document.removeEventListener('pointerup', onUp, true); }; } function destroyAudioMini(stopAudio = true) { if (pkAudioMiniSession && typeof pkAudioMiniSession.destroyMini === 'function') return pkAudioMiniSession.destroyMini(stopAudio); const mini = document.getElementById('pk-audio-mini'); if (mini) { if (typeof mini._pkAudioMiniCleanupDrag === 'function') mini._pkAudioMiniCleanupDrag(); mini.remove(); return true; } return false; } function closeAudioMiniForMediaOpen() { if (!isAudioMiniActive()) return false; return destroyAudioMini(true); } function playAudio(item) { if (S.trashMode || !item) return; if (isAudioMiniActive()) destroyAudioMini(true); const old = document.getElementById('pk-audio-ov'); if (old && typeof old._pkDestroyAudioPlayer === 'function') old._pkDestroyAudioPlayer(); else if (old) old.remove(); const L = getStrings(); const audioMiniModeLabel = `${L.audio_mini_mode} [${(CONF.audioHotkeys && CONF.audioHotkeys.mini) || 'P'}]`; const audioKey = (it) => String((it && (it.id || it.file_id || it.name)) || ''); const list = []; const seen = new Set(); (Array.isArray(S.display) ? S.display : []).forEach(it => { if (!isAudioLikeItem(it)) return; const key = audioKey(it); if (!key || seen.has(key)) return; seen.add(key); list.push(it); }); const curKey = audioKey(item); let curIdx = list.findIndex(it => audioKey(it) === curKey); if (curIdx < 0) { list.unshift(item); curIdx = 0; } const d = document.createElement('div'); d.id = 'pk-audio-ov'; syncAudioFullPlayerTheme(d); d.tabIndex = 0; d.innerHTML = ` `; const audio = d.querySelector('#pk_audio'); const audioBox = d.querySelector('#pk_audio_box'); const titleEl = d.querySelector('#pk_audio_title'); const nameEl = d.querySelector('#pk_audio_name'); const coverEl = d.querySelector('#pk_audio_cover'); const formatEl = d.querySelector('#pk_audio_format'); const sizeEl = d.querySelector('#pk_audio_size'); const statusEl = d.querySelector('#pk_audio_status'); const curEl = d.querySelector('#pk_audio_t_cur'); const durEl = d.querySelector('#pk_audio_t_dur'); const progressWrap = d.querySelector('#pk_audio_progress_wrap'); const progressEl = d.querySelector('#pk_audio_progress'); const progressTipEl = d.querySelector('#pk_audio_progress_tip'); const volumeEl = d.querySelector('#pk_audio_volume'); const playBtn = d.querySelector('#pk_audio_play'); const muteBtn = d.querySelector('#pk_audio_mute'); const modeBtn = d.querySelector('#pk_audio_mode'); const prevBtn = d.querySelector('#pk_audio_prev'); const nextBtn = d.querySelector('#pk_audio_next'); const playlistBtn = d.querySelector('#pk_audio_playlist'); const playlistPanel = d.querySelector('#pk_audio_playlist_panel'); const playlistBody = d.querySelector('#pk_audio_playlist_body'); const playlistCountEl = d.querySelector('#pk_audio_playlist_count'); const miniBtn = d.querySelector('#pk_audio_mini'); const closeBtn = d.querySelector('#pk_audio_close'); let destroyed = false; let loadToken = 0; let retryUsed = false; let seeking = false; let loadingAudio = false; let pendingPlayRequested = false; let audioFailedLocked = false; let audioFailNextTimer = null; let audioFailAutoSkipCount = 0; let audioPlaylistOpen = false; let audioDurationRenderRaf = 0; const audioPlayModes = ['order','list','single','shuffle']; let audioPlayMode = audioPlayModes.includes(gmGet('pk_audio_play_mode', 'order')) ? gmGet('pk_audio_play_mode', 'order') : 'order'; let lastVolume = Math.max(0, Math.min(1, Number(gmGet('pk_audio_vol_level', 1)) || 1)); const savedAudioMuted = gmGet('pk_audio_vol_muted', false); let audioMuted = savedAudioMuted === true || savedAudioMuted === 'true'; let miniEl = null; let miniHost = null; audio.volume = lastVolume; audio.muted = audioMuted; volumeEl.value = String(audioMuted ? 0 : lastVolume); volumeEl.style.setProperty('--pk-audio-pct', `${Math.round((audioMuted ? 0 : lastVolume) * 100)}%`); const current = () => list[curIdx] || item; const fmtTime = (sec) => fmtDur(Number.isFinite(sec) && sec > 0 ? sec : 0); let audioShuffleOrder = []; let audioShufflePos = -1; const isValidAudioShuffleIndex = (idx) => Number.isInteger(idx) && idx >= 0 && idx < list.length; const shuffleAudioIndexes = (arr) => { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; }; const resetAudioShuffleHistory = (keepCurrent = true) => { audioShuffleOrder = []; audioShufflePos = -1; if (!list.length) return; const startIdx = keepCurrent && isValidAudioShuffleIndex(curIdx) ? curIdx : 0; const rest = shuffleAudioIndexes(list.map((_, idx) => idx).filter(idx => idx !== startIdx)); audioShuffleOrder = [startIdx, ...rest]; audioShufflePos = 0; }; const syncAudioShuffleCurrent = () => { if (!list.length) { audioShuffleOrder = []; audioShufflePos = -1; return; } const setSize = new Set(audioShuffleOrder).size; const valid = audioShuffleOrder.length === list.length && setSize === list.length && audioShuffleOrder.every(isValidAudioShuffleIndex); if (!valid || !audioShuffleOrder.includes(curIdx)) { resetAudioShuffleHistory(true); return; } audioShufflePos = audioShuffleOrder.indexOf(curIdx); }; resetAudioShuffleHistory(true); const getAudioDisplayName = (it) => { const name = it && it.name ? String(it.name) : L.audio_player_title; const ext = getItemExt(it); return ext && name.toLowerCase().endsWith('.' + ext.toLowerCase()) ? name.slice(0, -(ext.length + 1)) : name; }; const setRangePct = (el, pct) => el.style.setProperty('--pk-audio-pct', `${Math.max(0, Math.min(100, pct))}%`); const setMiniVolumePct = (el, value) => el && el.style.setProperty('--pk-mini-vol-pct', `${Math.max(0, Math.min(100, (Number(value) || 0) * 100))}%`); let audioCoverToken = 0; const renderAudioCoverUrl = (url) => { if (!coverEl) return; if (url) { coverEl.innerHTML = ``; return; } coverEl.innerHTML = CONF.typeIcons.audio; }; const refreshAudioCover = async (audioUrl = '') => { const it = current(); const key = getAudioCoverKey(it); const token = ++audioCoverToken; const cached = key && pkAudioCoverCache.get(key); const direct = getAudioDirectCoverUrl(it); const sibling = findAudioSiblingCoverUrl(it); if (cached || direct || sibling) { renderAudioCoverUrl(cached || cacheAudioCoverUrl(key, direct || sibling)); return; } renderAudioCoverUrl(''); if (!audioUrl) return; const embedded = await fetchAudioEmbeddedCoverUrl(audioUrl, it); if (!destroyed && token === audioCoverToken && audioKey(current()) === audioKey(it)) renderAudioCoverUrl(embedded || ''); }; const updateMiniInfo = () => { if (!miniEl) return; const it = current(); const name = it && it.name ? it.name : L.audio_player_title; const total = Math.max(1, list.length || 1); const index = Math.min(total, Math.max(1, curIdx + 1)); const miniName = miniEl.querySelector('.pk-audio-mini-name'); const miniMeta = miniEl.querySelector('.pk-audio-mini-meta'); const miniPlayBtn = miniEl.querySelector('#pk_audio_mini_play'); if (miniName) miniName.textContent = name; if (miniMeta) { const dur = Number(audio.duration) || 0; const cur = Number(audio.currentTime) || 0; const status = statusEl.textContent || ''; miniMeta.textContent = `${index}/${total} - ${fmtTime(cur)} / ${fmtTime(dur)}${status ? ` - ${status}` : ''}`; } if (miniPlayBtn) { const isPaused = audio.paused || audio.ended; const label = isPaused ? L.audio_play : L.audio_pause; miniPlayBtn.innerHTML = isPaused ? CONF.audioIcons.play : CONF.audioIcons.pause; miniPlayBtn.setAttribute('aria-label', label); miniPlayBtn.dataset.tip = label; } }; const setStatus = (text, isErr = false) => { statusEl.textContent = text || ''; statusEl.classList.toggle('err', !!isErr); updateMiniInfo(); }; const clearAudioFailNextTimer = () => { if (audioFailNextTimer) { clearTimeout(audioFailNextTimer); audioFailNextTimer = null; } }; const scheduleAudioFailNext = () => { clearAudioFailNextTimer(); if (list.length <= 1 || audioFailAutoSkipCount >= list.length - 1) return; const token = loadToken; audioFailNextTimer = setTimeout(() => { if (destroyed || token !== loadToken) return; audioFailNextTimer = null; audioFailAutoSkipCount++; switchAudio(audioPlayMode === 'shuffle' ? getAudioShuffleNextIndex() : curIdx + 1); }, 3000); }; const markAudioFinalFailure = (text) => { audioFailedLocked = true; pendingPlayRequested = false; loadingAudio = false; setStatus(text || L.audio_play_failed, true); updatePlayButton(); scheduleAudioFailNext(); }; const updatePlayButton = () => { const isPaused = audio.paused || audio.ended; const label = isPaused ? L.audio_play : L.audio_pause; playBtn.innerHTML = isPaused ? CONF.audioIcons.play : CONF.audioIcons.pause; playBtn.removeAttribute('title'); playBtn.dataset.tip = label; playBtn.setAttribute('aria-label', label); updateMiniInfo(); }; const updateMuteButton = () => { const isMuted = audio.muted || audio.volume <= 0; const label = isMuted ? L.audio_unmute : L.audio_mute; const icon = isMuted ? CONF.audioIcons.mute : CONF.audioIcons.vol; const val = isMuted ? 0 : audio.volume; muteBtn.innerHTML = icon; muteBtn.removeAttribute('title'); muteBtn.dataset.tip = label; muteBtn.setAttribute('aria-label', label); const miniMuteBtn = miniEl && miniEl.querySelector('#pk_audio_mini_mute'); const miniVolumeEl = miniEl && miniEl.querySelector('#pk_audio_mini_volume'); if (miniMuteBtn) { miniMuteBtn.innerHTML = icon; miniMuteBtn.removeAttribute('data-tip'); miniMuteBtn.setAttribute('aria-label', label); } if (miniVolumeEl) { miniVolumeEl.value = String(val); setMiniVolumePct(miniVolumeEl, val); } }; const updateModeButton = () => { const labelMap = {order:L.audio_mode_order,list:L.audio_mode_list,single:L.audio_mode_single,shuffle:L.audio_mode_shuffle}; const iconMap = {order:CONF.audioIcons.modeOrder,list:CONF.audioIcons.modeList,single:CONF.audioIcons.modeSingle,shuffle:CONF.audioIcons.modeShuffle}; const label = labelMap[audioPlayMode] || L.audio_mode_order; const icon = iconMap[audioPlayMode] || CONF.audioIcons.modeOrder; modeBtn.innerHTML = icon; modeBtn.removeAttribute('title'); modeBtn.dataset.tip = label; modeBtn.setAttribute('aria-label', label); const miniModeBtn = miniEl && miniEl.querySelector('#pk_audio_mini_mode'); if (miniModeBtn) { miniModeBtn.innerHTML = icon; miniModeBtn.dataset.tip = label; miniModeBtn.setAttribute('aria-label', label); } }; const updateTime = () => { if (!seeking) { const dur = Number(audio.duration) || 0; const cur = Number(audio.currentTime) || 0; const val = dur > 0 ? Math.round(cur / dur * 1000) : 0; progressEl.value = String(val); setRangePct(progressEl, val / 10); curEl.textContent = fmtTime(cur); durEl.textContent = fmtTime(dur); updateMiniInfo(); } }; const hideAudioProgressTip = () => { if (progressTipEl) progressTipEl.classList.remove('show'); }; const updateAudioProgressTip = (clientX) => { if (!progressWrap || !progressTipEl) return; const dur = Number(audio.duration) || getAudioKnownDuration(current()) || 0; if (!(dur > 0)) { hideAudioProgressTip(); return; } const rect = progressWrap.getBoundingClientRect(); const ratio = Math.max(0, Math.min(1, (clientX - rect.left) / Math.max(1, rect.width))); progressTipEl.textContent = fmtTime(dur * ratio); progressTipEl.style.left = `${ratio * 100}%`; progressTipEl.classList.add('show'); }; const getAudioKnownDuration = (it) => { const key = audioKey(it); const raw = (it && it.params && it.params.duration) || (key && S.durationMap && S.durationMap.get(key)) || (key && gmGet('pk_duration_' + key, 0)) || 0; const dur = parseFloat(raw || 0); return Number.isFinite(dur) && dur > 0 ? dur : 0; }; const scheduleAudioDurationViewRefresh = () => { if (audioDurationRenderRaf) cancelAnimationFrame(audioDurationRenderRaf); audioDurationRenderRaf = requestAnimationFrame(() => { audioDurationRenderRaf = 0; if (typeof renderVisible === 'function') renderVisible(); }); }; const saveAudioKnownDuration = (it, dur) => { const seconds = Math.round(Number(dur) || 0); if (!(seconds > 0)) return; const key = audioKey(it); if (it) { if (!it.params) it.params = {}; it.params.duration = seconds; } if (key && S.durationMap) S.durationMap.set(key, seconds); if (key) gmSet('pk_duration_' + key, seconds); const syncOne = (target) => { if (!target || audioKey(target) !== key) return; if (!target.params) target.params = {}; target.params.duration = seconds; }; [list, S.items, S.display].forEach(arr => Array.isArray(arr) && arr.forEach(syncOne)); if (audioPlaylistOpen) renderAudioPlaylist(); else updateAudioPlaylistActive(); scheduleAudioDurationViewRefresh(); }; const getAudioPlaylistMeta = (it) => { const size = it && it.size ? fmtSize(Number(it.size)) : ''; const dur = getAudioKnownDuration(it); const durationText = dur > 0 ? fmtTime(dur) : ''; return size && durationText ? `${size}/${durationText}` : (size || durationText); }; const updateAudioPlaylistActive = () => { if (playlistCountEl) playlistCountEl.textContent = list.length ? `${curIdx + 1}/${list.length}` : '0'; if (!playlistBody) return; let activeEl = null; playlistBody.querySelectorAll('.pk-audio-playlist-item').forEach(row => { const active = Number(row.dataset.idx) === curIdx; row.classList.toggle('pk-audio-playlist-item-active', active); row.setAttribute('aria-current', active ? 'true' : 'false'); if (active) activeEl = row; }); if (audioPlaylistOpen && activeEl) { try { activeEl.scrollIntoView({ block: 'nearest' }); } catch (e) {} } }; const renderAudioPlaylist = () => { if (!playlistBody) return; if (!list.length) { playlistBody.innerHTML = `
${esc(L.audio_playlist_empty)}
`; updateAudioPlaylistActive(); return; } playlistBody.innerHTML = list.map((it, idx) => { const name = it && it.name ? it.name : L.audio_player_title; const meta = getAudioPlaylistMeta(it); const activeCls = idx === curIdx ? ' pk-audio-playlist-item-active' : ''; return ``; }).join(''); updateAudioPlaylistActive(); }; const setAudioPlaylistOpen = (open) => { audioPlaylistOpen = !!open; if (audioBox) audioBox.classList.toggle('pk-audio-playlist-open', audioPlaylistOpen); if (playlistPanel) playlistPanel.setAttribute('aria-hidden', audioPlaylistOpen ? 'false' : 'true'); if (playlistBtn) { const label = audioPlaylistOpen ? L.audio_playlist_close : L.audio_playlist; playlistBtn.classList.toggle('pk-audio-btn-active', audioPlaylistOpen); playlistBtn.dataset.tip = label; playlistBtn.setAttribute('aria-label', label); playlistBtn.setAttribute('aria-pressed', audioPlaylistOpen ? 'true' : 'false'); } if (audioPlaylistOpen) renderAudioPlaylist(); }; const onUnifiedDurationSaved = (e) => { const detail = (e && e.detail) || {}; const key = String(detail.id || ''); const seconds = Math.round(Number(detail.duration) || 0); if (!key || !(seconds > 0)) return; const syncOne = (target) => { if (!target || audioKey(target) !== key) return; if (!target.params) target.params = {}; target.params.duration = seconds; }; list.forEach(syncOne); syncOne(item); if (audioKey(current()) === key && !(Number(audio.duration) > 0)) durEl.textContent = fmtTime(seconds); if (audioPlaylistOpen) renderAudioPlaylist(); else updateAudioPlaylistActive(); updateMiniInfo(); }; document.addEventListener('pk-duration-saved', onUnifiedDurationSaved); const stopAudioDurationPrefetch = () => { }; const updateInfo = () => { const it = current(); const name = it && it.name ? it.name : L.audio_player_title; const displayName = getAudioDisplayName(it); const ext = getItemExt(it).toUpperCase(); const mime = String((it && it.mime_type) || '').replace(/^audio\//i, '').toUpperCase(); const total = Math.max(1, list.length || 1); const index = Math.min(total, Math.max(1, curIdx + 1)); titleEl.textContent = `[${index}/${total}] ${name}`; nameEl.textContent = displayName; formatEl.textContent = ext || mime || '-'; sizeEl.textContent = it && it.size ? fmtSize(Number(it.size)) : '-'; refreshAudioCover(''); updateAudioPlaylistActive(); updateMiniInfo(); }; const resetAudioElement = () => { clearAudioFailNextTimer(); audioFailedLocked = false; audio.pause(); audio.removeAttribute('src'); audio.load(); curEl.textContent = '00:00'; durEl.textContent = '00:00'; progressEl.value = '0'; setRangePct(progressEl, 0); updatePlayButton(); }; const loadCurrent = async (autoPlay = true, force = false) => { const token = ++loadToken; if (!force) retryUsed = false; loadingAudio = true; pendingPlayRequested = !!autoPlay; resetAudioElement(); updateInfo(); setStatus(L.audio_loading); try { const result = await resolveAudioPlayableSource(current(), force); if (destroyed || token !== loadToken) return; refreshAudioCover(result.url); audio.src = result.url; audio.load(); setStatus(L.audio_ready); if (autoPlay || pendingPlayRequested) { try { await audio.play(); if (!destroyed && token === loadToken) { pendingPlayRequested = false; setStatus(L.audio_playing); } } catch (e) { if (!destroyed && token === loadToken && !(audio.error && audio.error.code)) setStatus(L.audio_ready); } } } catch (e) { if (destroyed || token !== loadToken) return; markAudioFinalFailure((e && e.message) || L.audio_link_missing); } finally { if (token === loadToken) loadingAudio = false; updatePlayButton(); updateMuteButton(); } }; const switchAudio = (idx) => { if (!list.length) return; clearAudioFailNextTimer(); curIdx = (idx + list.length) % list.length; if (audioPlayMode === 'shuffle') syncAudioShuffleCurrent(); retryUsed = false; updateAudioPlaylistActive(); loadCurrent(true, false); }; const getAudioShuffleStepIndex = (delta) => { if (list.length <= 1) return curIdx; syncAudioShuffleCurrent(); if (audioShuffleOrder.length <= 1 || audioShufflePos < 0) return curIdx; audioShufflePos = (audioShufflePos + delta + audioShuffleOrder.length) % audioShuffleOrder.length; return audioShuffleOrder[audioShufflePos]; }; const getAudioShuffleNextIndex = () => getAudioShuffleStepIndex(1); const getAudioShufflePrevIndex = () => getAudioShuffleStepIndex(-1); const getManualNextIndex = (delta) => audioPlayMode === 'shuffle' ? (delta < 0 ? getAudioShufflePrevIndex() : getAudioShuffleNextIndex()) : curIdx + delta; const handleAudioEnded = () => { audioFailedLocked = false; if (audioPlayMode === 'single') { audio.currentTime = 0; audio.play().catch(() => { if (!destroyed && audio.error && audio.error.code) markAudioFinalFailure(L.audio_play_failed); }); return; } if (audioPlayMode === 'shuffle') { if (list.length > 1) switchAudio(getAudioShuffleNextIndex()); else { audio.currentTime = 0; setStatus(L.audio_paused); updatePlayButton(); } return; } if (audioPlayMode === 'list') { if (list.length > 1) switchAudio(curIdx + 1); else { audio.currentTime = 0; setStatus(L.audio_paused); updatePlayButton(); } return; } if (list.length > 1 && curIdx < list.length - 1) switchAudio(curIdx + 1); else { audio.currentTime = 0; setStatus(L.audio_paused); updatePlayButton(); } }; const seekBy = (delta) => { const dur = Number(audio.duration) || 0; if (!dur) return; audio.currentTime = Math.max(0, Math.min(dur, (Number(audio.currentTime) || 0) + delta)); updateTime(); }; const setVolume = (value) => { const v = Math.max(0, Math.min(1, Number(value) || 0)); audio.volume = v; audioMuted = v <= 0; audio.muted = audioMuted; if (v > 0) lastVolume = v; volumeEl.value = String(audioMuted ? 0 : v); setRangePct(volumeEl, (audioMuted ? 0 : v) * 100); gmSet('pk_audio_vol_level', v); gmSet('pk_audio_vol_muted', audioMuted); updateMuteButton(); }; const isTextInputTarget = (target) => { if (!target) return false; const tag = String(target.tagName || '').toUpperCase(); return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || !!target.closest('[contenteditable="true"],[contenteditable=""]'); }; const removeAudioMiniShell = () => { const mini = miniEl || document.getElementById('pk-audio-mini'); if (mini && typeof mini._pkAudioMiniCleanupDrag === 'function') mini._pkAudioMiniCleanupDrag(); if (mini && typeof mini._pkAudioMiniCleanupTheme === 'function') mini._pkAudioMiniCleanupTheme(); if (mini) mini.remove(); miniEl = null; miniHost = null; }; const destroyCurrentAudioMini = (stopAudio = true) => { const hadMini = !!(miniEl || document.getElementById('pk-audio-mini')); if (!stopAudio) { restoreAudioFromMini(); return hadMini; } removeAudioMiniShell(); if (pkAudioMiniSession && pkAudioMiniSession.owner === d) pkAudioMiniSession = null; destroy(); return hadMini; }; const restoreAudioFromMini = () => { if (destroyed || !miniEl) return; if (audioBox && audio.parentNode !== audioBox) audioBox.insertBefore(audio, audioBox.firstChild); removeAudioMiniShell(); syncAudioFullPlayerTheme(d); if (!d.isConnected) document.body.appendChild(d); requestAnimationFrame(() => syncAudioFullPlayerTheme(d)); if (pkAudioMiniSession && pkAudioMiniSession.owner === d) pkAudioMiniSession = null; updateInfo(); updateModeButton(); updateMuteButton(); updatePlayButton(); updateTime(); setTimeout(() => d.focus(), 0); }; const enterAudioMiniMode = () => { if (destroyed) return; if (miniEl) { clampAudioMiniPosition(miniEl); return; } const staleMini = document.getElementById('pk-audio-mini'); if (staleMini) { if (typeof staleMini._pkAudioMiniCleanupDrag === 'function') staleMini._pkAudioMiniCleanupDrag(); staleMini.remove(); } miniEl = document.createElement('div'); miniEl.id = 'pk-audio-mini'; miniEl.setAttribute('role', 'dialog'); miniEl.setAttribute('aria-label', L.audio_mini_mode); miniEl.innerHTML = `
`; miniHost = miniEl.querySelector('.pk-audio-mini-audio-host'); if (miniHost && audio.parentNode !== miniHost) miniHost.appendChild(audio); document.body.appendChild(miniEl); bindAudioMiniTheme(miniEl); applyAudioMiniSavedPosition(miniEl); if (d.isConnected) d.remove(); const miniClose = miniEl.querySelector('#pk_audio_mini_close'); const miniFold = miniEl.querySelector('#pk_audio_mini_fold'); const miniMode = miniEl.querySelector('#pk_audio_mini_mode'); const miniPrev = miniEl.querySelector('#pk_audio_mini_prev'); const miniPlay = miniEl.querySelector('#pk_audio_mini_play'); const miniNext = miniEl.querySelector('#pk_audio_mini_next'); const miniMute = miniEl.querySelector('#pk_audio_mini_mute'); const miniVolumeWrap = miniEl.querySelector('.pk-audio-mini-volume-wrap'); const miniVolume = miniEl.querySelector('#pk_audio_mini_volume'); const miniRestore = miniEl.querySelector('#pk_audio_mini_restore'); if (miniClose) miniClose.onclick = () => destroyCurrentAudioMini(true); if (miniFold) miniFold.onclick = () => { const collapsed = !miniEl.classList.contains('pk-audio-mini-collapsed'); miniEl.classList.toggle('pk-audio-mini-collapsed', collapsed); miniFold.setAttribute('aria-pressed', collapsed ? 'true' : 'false'); requestAnimationFrame(() => { clampAudioMiniPosition(miniEl); saveAudioMiniPosition(miniEl); }); }; if (miniMode) miniMode.onclick = () => modeBtn.click(); if (miniPrev) miniPrev.onclick = () => prevBtn.click(); if (miniPlay) miniPlay.onclick = () => playBtn.click(); if (miniNext) miniNext.onclick = () => nextBtn.click(); if (miniMute) miniMute.onclick = () => muteBtn.click(); if (miniVolume) { const syncMiniVolume = () => { const v = audio.muted ? 0 : audio.volume; miniVolume.value = String(v); setMiniVolumePct(miniVolume, v); }; syncMiniVolume(); miniVolume.oninput = () => { setMiniVolumePct(miniVolume, miniVolume.value); setVolume(miniVolume.value); }; miniVolume.addEventListener('pointerdown', e => { e.stopPropagation(); if (miniVolumeWrap) miniVolumeWrap.classList.add('pk-audio-mini-volume-active'); const done = () => { if (miniVolumeWrap) miniVolumeWrap.classList.remove('pk-audio-mini-volume-active'); miniVolume.blur(); window.removeEventListener('pointerup', done, true); window.removeEventListener('pointercancel', done, true); }; window.addEventListener('pointerup', done, true); window.addEventListener('pointercancel', done, true); }); miniVolume.addEventListener('click', e => e.stopPropagation()); } if (miniRestore) miniRestore.onclick = restoreAudioFromMini; makeAudioMiniDraggable(miniEl, miniEl.querySelector('.pk-audio-mini-drag')); pkAudioMiniSession = { owner: d, isMiniActive: () => !destroyed && !!(miniEl && miniEl.isConnected), destroyMini: destroyCurrentAudioMini, restoreFull: restoreAudioFromMini }; updateMiniInfo(); updateModeButton(); updateMuteButton(); requestAnimationFrame(() => { syncAudioMiniTheme(miniEl); clampAudioMiniPosition(miniEl); }); }; const keyHandler = (e) => { if (destroyed || !document.getElementById('pk-audio-ov') || isTextInputTarget(e.target)) return; if (e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey && e.key === 'ArrowLeft') { e.preventDefault(); e.stopPropagation(); prevBtn.click(); } else if (e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey && e.key === 'ArrowRight') { e.preventDefault(); e.stopPropagation(); nextBtn.click(); } else if (e.key === ' ') { e.preventDefault(); e.stopPropagation(); playBtn.click(); } else if (!e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey && String(e.key || '').toLowerCase() === 'p') { e.preventDefault(); e.stopPropagation(); if (miniBtn && document.getElementById('pk-audio-ov')) miniBtn.click(); } else if (e.key === 'ArrowLeft') { e.preventDefault(); e.stopPropagation(); seekBy(-10); } else if (e.key === 'ArrowRight') { e.preventDefault(); e.stopPropagation(); seekBy(10); } else if (e.key === 'ArrowUp') { e.preventDefault(); e.stopPropagation(); setVolume(audio.volume + 0.05); } else if (e.key === 'ArrowDown') { e.preventDefault(); e.stopPropagation(); setVolume(audio.volume - 0.05); } else if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); destroy(); } }; function destroy() { if (destroyed) return; destroyed = true; stopAudioDurationPrefetch(); clearAudioFailNextTimer(); removeAudioMiniShell(); if (pkAudioMiniSession && pkAudioMiniSession.owner === d) pkAudioMiniSession = null; document.removeEventListener('pk-duration-saved', onUnifiedDurationSaved); document.removeEventListener('keydown', keyHandler, true); audio.pause(); audio.removeAttribute('src'); audio.load(); d.remove(); } d._pkDestroyAudioPlayer = destroy; closeBtn.onclick = destroy; if (miniBtn) miniBtn.onclick = enterAudioMiniMode; modeBtn.onclick = () => { const idx = audioPlayModes.indexOf(audioPlayMode); audioPlayMode = audioPlayModes[(idx + 1) % audioPlayModes.length] || 'order'; gmSet('pk_audio_play_mode', audioPlayMode); if (audioPlayMode === 'shuffle') resetAudioShuffleHistory(true); else resetAudioShuffleHistory(false); updateModeButton(); }; if (playlistBtn) playlistBtn.onclick = () => setAudioPlaylistOpen(!audioPlaylistOpen); if (playlistBody) { playlistBody.addEventListener('click', (e) => { const row = e.target && e.target.closest ? e.target.closest('.pk-audio-playlist-item') : null; if (!row) return; const idx = Number(row.dataset.idx); if (!Number.isInteger(idx) || idx < 0 || idx >= list.length) return; e.preventDefault(); audioFailAutoSkipCount = 0; clearAudioFailNextTimer(); switchAudio(idx); if (audioPlayMode === 'shuffle') resetAudioShuffleHistory(true); }); } prevBtn.onclick = () => { audioFailAutoSkipCount = 0; switchAudio(getManualNextIndex(-1)); }; nextBtn.onclick = () => { audioFailAutoSkipCount = 0; switchAudio(getManualNextIndex(1)); }; playBtn.onclick = () => { if (loadingAudio || (audio.src && audio.readyState < 2 && !audio.error)) { pendingPlayRequested = true; setStatus(L.audio_loading); return; } if (!audio.src) { pendingPlayRequested = true; loadCurrent(true, false); return; } if (audio.paused || audio.ended) { pendingPlayRequested = true; audio.play().then(() => { if (!destroyed) { audioFailedLocked = false; pendingPlayRequested = false; setStatus(L.audio_playing); updatePlayButton(); } }).catch(() => { if (!destroyed && audio.error && audio.error.code) markAudioFinalFailure(L.audio_play_failed); }); } else { pendingPlayRequested = false; audio.pause(); } }; muteBtn.onclick = () => { if (audio.muted || audio.volume <= 0) setVolume(lastVolume || 1); else { lastVolume = audio.volume || lastVolume || 1; audioMuted = true; audio.muted = true; volumeEl.value = '0'; setRangePct(volumeEl, 0); gmSet('pk_audio_vol_level', lastVolume); gmSet('pk_audio_vol_muted', true); updateMuteButton(); } }; volumeEl.oninput = () => setVolume(volumeEl.value); progressEl.oninput = () => { seeking = true; const dur = Number(audio.duration) || 0; const val = Number(progressEl.value) || 0; setRangePct(progressEl, val / 10); curEl.textContent = fmtTime(dur * val / 1000); }; progressEl.onchange = () => { const dur = Number(audio.duration) || 0; if (dur > 0) audio.currentTime = dur * (Number(progressEl.value) || 0) / 1000; seeking = false; updateTime(); }; if (progressWrap) { progressWrap.addEventListener('pointermove', (e) => updateAudioProgressTip(e.clientX)); progressWrap.addEventListener('pointerenter', (e) => updateAudioProgressTip(e.clientX)); progressWrap.addEventListener('pointerleave', hideAudioProgressTip); } progressEl.addEventListener('pointerup', () => { progressEl.onchange(); }); audio.addEventListener('loadedmetadata', () => { updateTime(); const dur = Number(audio.duration) || 0; if (dur > 0 && Number.isFinite(dur)) { saveAudioKnownDuration(current(), dur); if (audioPlaylistOpen) renderAudioPlaylist(); else updateAudioPlaylistActive(); } }); audio.addEventListener('canplay', () => { if (!pendingPlayRequested || destroyed || !audio.src || !audio.paused) return; audio.play().then(() => { if (!destroyed) { audioFailedLocked = false; pendingPlayRequested = false; setStatus(L.audio_playing); updatePlayButton(); } }).catch(() => { if (!destroyed && audio.error && audio.error.code) markAudioFinalFailure(L.audio_play_failed); }); }); audio.addEventListener('timeupdate', updateTime); audio.addEventListener('playing', () => { clearAudioFailNextTimer(); audioFailAutoSkipCount = 0; audioFailedLocked = false; setStatus(L.audio_playing); updatePlayButton(); }); audio.addEventListener('pause', () => { if (!destroyed && audio.src && !audio.ended && !audioFailedLocked) setStatus(L.audio_paused); updatePlayButton(); }); audio.addEventListener('volumechange', () => { volumeEl.value = String(audio.muted ? 0 : audio.volume); setRangePct(volumeEl, (audio.muted ? 0 : audio.volume) * 100); updateMuteButton(); }); audio.addEventListener('ended', handleAudioEnded); audio.addEventListener('error', () => { if (destroyed || !audio.src) return; if (!retryUsed) { retryUsed = true; setStatus(L.audio_link_expired); loadCurrent(true, true); return; } const code = audio.error && audio.error.code; markAudioFinalFailure(code === 4 ? L.audio_format_unsupported : L.audio_play_failed); }); document.body.appendChild(d); document.addEventListener('keydown', keyHandler, true); renderAudioPlaylist(); setAudioPlaylistOpen(false); updateInfo(); updateModeButton(); updateMuteButton(); updatePlayButton(); setTimeout(() => d.focus(), 0); loadCurrent(true, false); } async function playVideo(item, startFullscreen = false) { if (S.trashMode) return; closeAudioMiniForMediaOpen(); const getPhysicalId = (it) => { if ((S.offlineMode && it.kind === 'drive#task') || (S.uploadMode && it.file_id)) { return it.file_id || it.id; } return it.id; }; let pkHls = null; let isPlayerDestroyed = false; const L = getStrings(); if (getDefaultOpenPlayerPref() === 'potplayer' && typeof openDefaultPotPlayerForItem === 'function') { const handledByExternalPlayer = await openDefaultPotPlayerForItem(item); if (handledByExternalPlayer) return; } const shouldSyncOfficialPlayHistory = (it = item) => !(it && it._isShareItem); let sharePreviewLimitTipShown = false; let sharePreviewSoftLimitTipShown = false; const getSharePreviewLimitTip = (it = item) => { if (!it || !it._isShareItem) return ''; const p = it.params || {}; if (!p.anonymous_play_seconds && !p.anonymous_play_range) return ''; return L.msg_share_preview_limited_save || ''; }; const getSharePreviewExtLimitTip = (it = item) => { if (!it || !it._isShareItem) return ''; const p = it.params || {}; if (!p.anonymous_play_seconds && !p.anonymous_play_range) return ''; return L.msg_share_ext_preview_limited_save || L.msg_share_preview_limited_save || ''; }; const getSharePreviewSoftLimitTip = () => L.msg_share_preview_soft_limit; const getSharePreviewLimitParams = (it = item, duration = 0) => { if (!it || !it._isShareItem) return null; const p = it.params || {}; let seconds = Number(p.anonymous_play_seconds || 0); let range = Number(p.anonymous_play_range || 0); if (range > 1) range = range / 100; if (!(seconds > 0) && !(range > 0)) return null; const dur = Number(duration || (p && p.duration) || 0); if (!(dur > 0) || !isFinite(dur)) return null; let ratio = 0; if (seconds > 0) ratio = Math.max(ratio, seconds / dur); if (range > 0) ratio = Math.max(ratio, range); ratio = Math.max(0, Math.min(1, ratio)); if (!(ratio > 0) || ratio >= 0.995) return null; return { ratio, time: dur * ratio, seconds, range }; }; const isVideoItem = (it) => { if (!it || it.isHeader || it.kind === 'drive#folder') return false; if (S.offlineMode && it.phase !== 'PHASE_TYPE_COMPLETE') return false; if (S.uploadMode && it.status !== 'DONE') return false; const m = (it.mime_type || '').toLowerCase(); const n = (it.name || '').toLowerCase(); const dur = (it.params && it.params.duration) || 0; const vExts = ['mp4','mkv','avi','mov','wmv','flv','webm','ts','m4v','3gp']; return m.startsWith('video/') || dur > 0 || vExts.some(e => n.endsWith('.' + e)); }; const videoPlaylist = S.display.filter(i => isVideoItem(i)); let curListIdx = videoPlaylist.findIndex(v => v.id === item.id); const totalInList = videoPlaylist.length; let isSwitching = false; let switchReqId = 0; let mediaSessionToken = 0; let activeHealthTimer = null; let activeHlsObjectUrl = null; const isStaleMediaSession = (token) => token !== mediaSessionToken || isPlayerDestroyed; const showSadBox = (codecName, reason = 'codec', opts = {}) => { if (box.querySelector('.pk-err-dialog')) return; if (posterEl) { posterEl.style.transition = 'none'; posterEl.style.display = 'flex'; posterEl.style.opacity = '1'; posterEl.style.pointerEvents = 'auto'; } const savedPlayer = gmGet('pk_ext_player', 'potplayer'); const sadBoxSVG = ``; const isWebUnsupportedHint = reason === 'web_unsupported'; const safeQualityList = Array.isArray(qualityList) ? qualityList : []; const preferredErrUrl = String(opts.url || currentLink || ''); const preferredErrName = String(opts.name || currentResName || codecName || ''); const fallbackQuality = safeQualityList[0] || { name: preferredErrName, link: preferredErrUrl, url: preferredErrUrl }; const recommended = isWebUnsupportedHint ? (safeQualityList.find(q => String(q.link || q.url || '') === preferredErrUrl) || safeQualityList.find(q => String(q.name || '') === preferredErrName) || safeQualityList.find(q => String(q.name || '') === String(currentResName || '')) || fallbackQuality) : (safeQualityList.find(q => !q.isOriginal) || fallbackQuality); const recommendedUrl = preferredErrUrl || recommended.link || recommended.url; let selectedErrUrl = recommendedUrl; let selectedErrName = preferredErrName || recommended.name; let selectedErrPlayer = (savedPlayer === 'other') ? 'other' : 'potplayer'; const resOptions = safeQualityList.map(q => { const qUrl = q.link || q.url; const isSelected = qUrl === selectedErrUrl; return `
${q.name}
`; }).join(''); const lblRes = L.lbl_resolution; const errTitle = isWebUnsupportedHint ? L.err_web_unsupported_t1 : L.err_codec_t1.replace('{c}', codecName); const errDesc = isWebUnsupportedHint ? L.err_web_unsupported_t2 : L.err_codec_t2; const shareExtPreviewTip = getSharePreviewExtLimitTip(item); const dialog = document.createElement('div'); dialog.className = 'pk-err-dialog'; dialog.style.cssText = "position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(30, 30, 30, 0.85);backdrop-filter:blur(12px);border-radius:12px;padding:40px 50px;display:flex;flex-direction:column;align-items:center;text-align:center;box-shadow:0 20px 60px rgba(0,0,0,0.8);z-index:999;min-width:400px;border:1px solid rgba(255,255,255,0.1);"; dialog.innerHTML = `
${sadBoxSVG}
${errTitle}
${errDesc}
${shareExtPreviewTip ? `
${shareExtPreviewTip}
` : ''}
${lblRes}
${selectedErrName}
${CONF.crumbIcons.down}
${resOptions}
${L.lbl_player}
${selectedErrPlayer === 'potplayer' ? 'PotPlayer' : L.opt_player_other}
${CONF.crumbIcons.down}
PotPlayer
${L.opt_player_other}
`; box.appendChild(dialog); const launchBtn = dialog.querySelector('#pk_err_launch_btn'); const fixBtn = dialog.querySelector('#pk_err_potplayer_fix'); const closeErrMenus = () => dialog.querySelectorAll('.pk-select-menu').forEach(menu => menu.style.display = 'none'); const removeErrDialog = () => { document.removeEventListener('click', closeErrMenus); dialog.remove(); }; const getCleanErrSelectedUrl = () => { let cleanLink = String(selectedErrUrl || '').replace('&ext=.m3u8', ''); if (cleanLink.includes('ts_downloader') && cleanLink.includes('url=')) { try { const urlParam = new URL(cleanLink).searchParams.get('url'); if (urlParam) cleanLink = decodeURIComponent(urlParam); } catch (e) {} } return cleanLink; }; const updateErrPotPlayerFixEntry = () => { if (fixBtn) fixBtn.style.display = selectedErrPlayer === 'potplayer' ? 'inline-flex' : 'none'; }; const bindErrSelect = (id, onSelect) => { const container = dialog.querySelector(`#${id}`); if (!container) return; const trigger = container.querySelector('.pk-select-trigger'); const menu = container.querySelector('.pk-select-menu'); const txt = container.querySelector('.pk-select-trigger > span'); if (!trigger || !menu || !txt) return; trigger.onclick = (e) => { e.stopPropagation(); const isOpen = menu.style.display === 'block'; closeErrMenus(); menu.style.display = isOpen ? 'none' : 'block'; }; container.querySelectorAll('.pk-select-item').forEach(item => { item.onclick = (e) => { e.stopPropagation(); container.querySelectorAll('.pk-select-item').forEach(i => i.classList.remove('act')); item.classList.add('act'); txt.textContent = item.textContent; menu.style.display = 'none'; onSelect(item.dataset.val, item.textContent); }; }); }; dialog.querySelector('.pk-err-close').onclick = (e) => { e.stopPropagation(); removeErrDialog(); }; dialog.onclick = (e) => e.stopPropagation(); bindErrSelect('pk_err_res_cs', (val, text) => { selectedErrUrl = val; selectedErrName = text; }); bindErrSelect('pk_err_player_cs', (val) => { selectedErrPlayer = val; launchBtn.textContent = (val === 'other') ? L.btn_copy_link : L.btn_start_play; updateErrPotPlayerFixEntry(); }); launchBtn.textContent = (selectedErrPlayer === 'other') ? L.btn_copy_link : L.btn_start_play; setTimeout(() => document.addEventListener('click', closeErrMenus), 0); if (fixBtn) { fixBtn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); openPotPlayerProtocolRepairHelper(getCleanErrSelectedUrl(), { button: launchBtn, source: 'error_fallback', onRetryLikelyOpen: () => { removeErrDialog(); destroyPlayer(); }, onRetryLikelyFail: () => { removeErrDialog(); destroyPlayer(); } }); }; } launchBtn.onclick = (e) => { e.stopPropagation(); const selPlayer = selectedErrPlayer; const selResName = selectedErrName; gmSet('pk_ext_player', selPlayer); const cleanLink = getCleanErrSelectedUrl(); if (selPlayer === 'other') { GM_setClipboard(cleanLink); launchBtn.textContent = L.msg_copy_success; launchBtn.style.background = "#52c41a"; launchBtn.style.color = "#fff"; setTimeout(() => { removeErrDialog(); if (typeof destroyPlayer === 'function') destroyPlayer(); }, 800); } else if (selPlayer === 'potplayer') { launchPotPlayerWithFallback(cleanLink, { button: launchBtn, source: 'error_fallback', restoreText: L.btn_start_play, restoreBg: launchBtn.style.background, restoreColor: launchBtn.style.color, failToastDuration: 6500, failCloseDelay: 3200, onLikelyOpen: () => { removeErrDialog(); destroyPlayer(); }, onLikelyFail: (ctx) => { removeErrDialog(); destroyPlayer(); schedulePotPlayerAutoRepairPrompt(cleanLink, { source: 'error_fallback', autoRepairPrompt: ctx && ctx.autoRepairPrompt, autoRepairPromptDelay: ctx && ctx.autoRepairPromptDelay }); } }); } else { const curT = v.currentTime; currentLink = selResLink; currentResName = selResName; box.classList.add('buffering'); if (posterEl) { if (v.readyState >= 2 && v.currentTime > 0) { try { const cvs = document.createElement('canvas'); cvs.width = v.videoWidth; cvs.height = v.videoHeight; cvs.getContext('2d').drawImage(v, 0, 0); posterEl.querySelector('img').src = cvs.toDataURL('image/jpeg', 0.7); } catch (err) {} } posterEl.style.display = 'flex'; posterEl.style.opacity = '1'; } if(resTxt) resTxt.textContent = currentResName; if(resList) { resList.innerHTML = renderQualityMenu(qualityList, currentResName); bindResEvents(); } removeErrDialog(); shutterTargetTime = curT > 0.1 ? curT : 0; loadSource(currentLink, curT); v.play().catch(()=>{}); } }; }; const renderPlaylistItems = () => { const RANGE = 150; const start = Math.max(0, curListIdx - RANGE); const end = Math.min(videoPlaylist.length, curListIdx + RANGE + 1); return videoPlaylist.slice(start, end).map((v, i) => { const absIdx = start + i; const coverSrc = (v.thumbnail_link && v.thumbnail_link !== v.icon_link) ? v.thumbnail_link : ''; const phSrc = v.icon_link || v.thumbnail_link || ''; const phSvg = getIcon(v); return `
${phSrc ? `` : phSvg}
${coverSrc ? `` : ``}
`; }).join(''); }; const triggerResume = (targetVideo, targetItem) => { if (!shouldLoadVideoProgressCache()) return; if (targetVideo._hasShownResumeToast || hasUserSeeked || targetVideo._isRestarting) return; const targetId = getPhysicalId(targetItem); if (targetVideo._pkActiveFileId && targetVideo._pkActiveFileId !== targetId) return; if (targetVideo._pkOfficialResumeFileId && targetVideo._pkOfficialResumeFileId !== targetId) return; const savedT = parseFloat(targetVideo._pkOfficialResumeTime || 0); const skipOp = targetVideo._pkIntroSkipApplied ? (parseInt(gmGet('pk_skip_intro', 0)) || 0) : 0; if (savedT > 5 || skipOp > 0) { targetVideo._hasShownResumeToast = true; } else { return; } const showToast = (text) => { const oldToast = d.querySelector('.pk-p-resume-toast'); if (oldToast) oldToast.remove(); const toast = document.createElement('div'); toast.className = 'pk-p-resume-toast'; toast.style.zIndex = "150"; toast.innerHTML = `${text}${L.btn_restart}
${mkSvg(icons.close)}
`; d.querySelector('#pk_p_box').appendChild(toast); toast.querySelector('#pk_re_start').onclick = (e) => { e.stopPropagation(); if (tCur) tCur.textContent = "00:00:00"; if (progFilled) progFilled.style.setProperty('width', '0%', 'important'); shutterTargetTime = 0.1; targetVideo._isRestarting = true; targetVideo.currentTime = 0; toast.remove(); }; toast.querySelector('#pk_re_close').onclick = (e) => { e.stopPropagation(); toast.remove(); }; setTimeout(() => { if(toast.parentNode) toast.remove(); }, 8000); }; if (savedT > 5) { showToast(L.msg_resume_hint.replace('{t}', fmtT(savedT))); } else if (skipOp > 0) { const dp = d.querySelector('.pk-player-box'); if(dp && !dp.querySelector('.pk-skip-toast')) { const tip = document.createElement('div'); tip.className = 'pk-skip-toast'; tip.style.cssText = "position:absolute;top:80px;left:20px;background:rgba(0,0,0,0.6);color:#ddd;padding:4px 8px;border-radius:4px;font-size:12px;pointer-events:none;animation:pkFadeIn 0.5s;"; tip.textContent = `${L.lbl_skip_op} ${skipOp}s`; dp.appendChild(tip); setTimeout(()=>tip.remove(), 3000); } } }; const softSwitch = async (newIdx, scrollMode = 'smooth') => { if (!videoPlaylist[newIdx]) return; switchReqId++; mediaSessionToken++; const myReqId = switchReqId; isSwitching = true; lastWorkingLink = null; lastWorkingResName = null; failedUrls.clear(); const v = d.querySelector('#pk_video'); const loader = d.querySelector('.pk-p-loading'); const poster = d.querySelector('#pk_p_poster'); if (shouldSyncOfficialPlayHistory(item) && v && v.currentTime > 5 && v.duration > 0 && v.duration - v.currentTime > 5) { postOfficialPlayProgress(getPhysicalId(item), v.currentTime, v.duration).catch(() => {}); } if (activeHealthTimer) { clearInterval(activeHealthTimer); activeHealthTimer = null; } if (pkHls) { try { pkHls.stopLoad(); } catch (e) {} try { pkHls.detachMedia(); } catch (e) {} try { pkHls.destroy(); } catch (e) {} pkHls = null; } if (activeHlsObjectUrl) { try { URL.revokeObjectURL(activeHlsObjectUrl); } catch (e) {} activeHlsObjectUrl = null; } if (v) { v.pause(); v._bufferingSince = null; v._blackScreenCount = 0; v._isRestarting = false; v._hasShownResumeToast = false; v._pkPendingIntroSkip = 0; v._pkIntroSkipApplied = false; Array.from(v.querySelectorAll('track')).forEach(t => t.remove()); if (subState.blobUrl) { URL.revokeObjectURL(subState.blobUrl); subState.blobUrl = null; } subState.hasSub = false; subState.track = null; const subNameLabel = d.querySelector('#pk_sub_name'); if (subNameLabel) subNameLabel.textContent = L.str_no_sub; v.src = ""; v.load(); } if (progFilled) progFilled.style.setProperty('width', '0%', 'important'); sharePreviewSoftLimitTipShown = false; if (sharePreviewLimitMarker) sharePreviewLimitMarker.style.display = 'none'; const newItem = videoPlaylist[newIdx]; if (tCur) tCur.textContent = '00:00:00'; if (tDur) { const newPId = getPhysicalId(newItem); const knownDur = (newItem.params && newItem.params.duration) || S.durationMap.get(newPId) || gmGet('pk_duration_' + newPId, 0); tDur.textContent = knownDur > 0 ? fmtT(knownDur) : '00:00:00'; } transformState = { rotate: 0, flipH: 1, flipV: 1, ratio: 'default' }; if (typeof applyTransform === 'function') applyTransform(); const ratioBtns = d.querySelectorAll('#pk_ratio_opts .pk-size-btn'); ratioBtns.forEach(btn => { if (btn.dataset.ratio === 'default') btn.classList.add('active'); else btn.classList.remove('active'); }); if (loader) loader.style.display = 'block'; box.classList.add('buffering'); hasCheckedProgress = false; const oldResumeToast = d.querySelector('.pk-p-resume-toast'); if (oldResumeToast) oldResumeToast.remove(); const errDialogs = d.querySelectorAll('.pk-err-dialog'); errDialogs.forEach(el => el.remove()); const linkOvs = d.querySelectorAll('.pk-link-export-ov'); linkOvs.forEach(el => el.remove()); const searchModal = d.querySelector('.pk-sub-search-modal'); if (searchModal) searchModal.remove(); const btnSearchS = d.querySelector('#pk_p_search'); if (btnSearchS) btnSearchS.style.setProperty('display', 'none', 'important'); const btnPipS = d.querySelector('#pk_p_pip'); if (btnPipS) btnPipS.style.setProperty('display', 'none', 'important'); curListIdx = newIdx; item = newItem; if (posterEl) { posterEl.style.transition = 'none'; posterEl.style.display = 'flex'; posterEl.style.opacity = '1'; const img = posterEl.querySelector('img'); if (img && newItem.thumbnail_link) { img.style.display = 'block'; img.src = newItem.thumbnail_link.replace('SIZE_MEDIUM', 'SIZE_LARGE'); img.style.opacity = '0'; img.onload = () => { img.style.opacity = '1'; if (btnSearchS) btnSearchS.style.setProperty('display', 'flex', 'important'); }; img.onerror = () => img.style.display = 'none'; } else if (img) { img.style.display = 'none'; } setTimeout(() => { posterEl.style.transition = 'opacity 0.4s ease'; }, 50); } if (loaderEl) loaderEl.style.display = 'block'; const pTitle = d.querySelector('.pk-player-title'); if(pTitle) pTitle.textContent = `[${newIdx + 1}/${totalInList}] ${newItem.name}`; const pTab = d.querySelector('#pk_p_plist_tab'); if(pTab) pTab.innerHTML = `${curListIdx + 1} / ${totalInList} `; if (pScroll) { syncVideoPlistItems( scrollMode === 'instant' ? 'instant' : (box.classList.contains('plist-active') ? 'smooth' : false) ); } const btnL = d.querySelector('#pk_p_side_L'); const btnR = d.querySelector('#pk_p_side_R'); if (btnL) btnL.style.display = newIdx === 0 ? 'none' : 'flex'; if (btnR) btnR.style.display = newIdx === totalInList - 1 ? 'none' : 'flex'; if (typeof updatePlistNav === 'function') updatePlistNav(); hasUserSeeked = false; try { const targetApiId = getPhysicalId(newItem); const newData = newItem._isShareItem ? await resolveSharePlayableFile(newItem) : await apiGet(targetApiId); if (newData) { if (newData.thumbnail_link) newItem.thumbnail_link = newData.thumbnail_link; if (newData.icon_link) newItem.icon_link = newData.icon_link; } if (myReqId !== switchReqId) return; const freshData = getBestSource(newData); currentResName = freshData.name; currentLink = freshData.src; qualityList = freshData.list; const resTxt = d.querySelector('#pk_p_res_txt'); if (resTxt) resTxt.textContent = currentResName; const resList = d.querySelector('#pk_p_res_list'); if (resList) { resList.innerHTML = renderQualityMenu(qualityList, currentResName); bindResEvents(); } loadSource(freshData.src, null); if (typeof ThumbnailEngine !== 'undefined') { ThumbnailEngine.resetSource(freshData.src); } if(poster) poster.style.display = 'block'; if (d.querySelector('#pk_sub_name')) d.querySelector('#pk_sub_name').textContent = L.str_no_sub; autoMatchSubtitle(newItem); if (!isPlayerDestroyed) { await v.play().catch(()=>{}); } if (!v.paused && !isPlayerDestroyed) { box.classList.add('pk-v-started'); stopSpinner(); } updateState(); } catch (err) { if (myReqId === switchReqId) console.error("[SoftSwitch] Critical Error:", err); } finally { if (myReqId === switchReqId) { isSwitching = false; if (loader && (v.readyState >= 2 || box.querySelector('.pk-err-dialog'))) loader.style.display = 'none'; } } }; const getLoadedHistoryProgress = (it) => { if (!S.historyMode || !it) return null; const targetId = String(getPhysicalId(it) || ''); if (!targetId) return null; const candidates = [it]; const mapItem = S.itemMap && S.itemMap.get(targetId); if (mapItem) candidates.push(mapItem); if (Array.isArray(S.items)) { const itemInList = S.items.find(f => f && String(f.id || '') === targetId); if (itemInList) candidates.push(itemInList); } if (Array.isArray(S.display)) { const itemInDisplay = S.display.find(f => f && !f.isHeader && String(f.id || '') === targetId); if (itemInDisplay) candidates.push(itemInDisplay); } for (const h of candidates) { const t = parseOfficialPlaySeconds(h && h._history_progress); const d = parseOfficialPlaySeconds((h && h._history_duration) || (h && h.params && h.params.duration) || S.durationMap.get(targetId) || gmGet('pk_duration_' + targetId, 0)); if (t > 5 && !(d > 0 && d - t <= 5)) { return { t, d, ts: Number((h && h._history_ts) || 0), source: 'loaded_history', officialEventId: String((h && h._history_event_id) || '') }; } } return null; }; const loadSource = async (url, customStartTime = null) => { sharePreviewSoftLimitTipShown = false; if (sharePreviewLimitMarker) sharePreviewLimitMarker.style.display = 'none'; let startTime = 0; const skipOp = parseInt(gmGet('pk_skip_intro', 0)) || 0; const pId = getPhysicalId(item); let shouldPendingIntroSkip = false; v._pkActiveFileId = pId; v._pkOfficialResumeTime = 0; v._pkOfficialResumeFileId = ''; v._hasShownResumeToast = false; const oldResumeToast = d.querySelector('.pk-p-resume-toast'); if (oldResumeToast) oldResumeToast.remove(); let loadedHistoryProgress = null; if (customStartTime !== null) { startTime = customStartTime; } else { loadedHistoryProgress = getLoadedHistoryProgress(item); if (loadedHistoryProgress) { startTime = loadedHistoryProgress.t; v._pkOfficialResumeTime = startTime; v._pkOfficialResumeFileId = pId; } else if (skipOp > 0) { const knownDur = (item.params && item.params.duration) || S.durationMap.get(pId) || gmGet('pk_duration_' + pId, 0); if (knownDur > 0 && skipOp >= knownDur) { console.warn(`[AutoSkip] Intro skip (${skipOp}s) exceeds total duration (${knownDur}s), ignored.`); } else { shouldPendingIntroSkip = true; } } } v._pkPendingIntroSkip = shouldPendingIntroSkip ? skipOp : 0; v._pkIntroSkipApplied = false; shutterTargetTime = startTime > 1 ? startTime : 0; if (tCur) tCur.textContent = fmtT(startTime); const totalDur = (item.params && item.params.duration) || S.durationMap.get(pId) || gmGet('pk_duration_' + pId, 0); if (progFilled && totalDur > 0) { const pct = Math.min(100, (startTime / totalDur) * 100); progFilled.style.setProperty('width', `${pct}%`, 'important'); } const currentMediaToken = ++mediaSessionToken; if (customStartTime === null && !loadedHistoryProgress && shouldSyncOfficialPlayHistory(item) && shouldLoadVideoProgressCache()) { fetchOfficialPlayHistoryProgress(pId).then((officialProgress) => { if (!officialProgress || isStaleMediaSession(currentMediaToken) || hasUserSeeked || v._isRestarting) return; if (v._pkActiveFileId !== pId || getPhysicalId(item) !== pId) return; const officialT = parseFloat(officialProgress.t || 0); const officialD = parseFloat(officialProgress.d || 0); if (!(officialT > 5)) return; if (officialD > 0 && officialD - officialT <= 5) return; const currentT = parseFloat(v.currentTime || 0); if (currentT > 5 && Math.abs(currentT - startTime) > 5) return; const applyOfficialProgress = () => { if (isStaleMediaSession(currentMediaToken) || hasUserSeeked || v._isRestarting) return; if (v._pkActiveFileId !== pId || getPhysicalId(item) !== pId) return; shutterTargetTime = officialT > 1 ? officialT : 0; try { v.currentTime = officialT; } catch (e) { console.warn('[OfficialProgress] Seek failed:', e); } if (tCur) tCur.textContent = fmtT(officialT); const knownDur = officialD || (item.params && item.params.duration) || S.durationMap.get(pId) || gmGet('pk_duration_' + pId, 0); if (progFilled && knownDur > 0) { const pct = Math.min(100, (officialT / knownDur) * 100); progFilled.style.setProperty('width', `${pct}%`, 'important'); } v._pkOfficialResumeTime = officialT; v._pkOfficialResumeFileId = pId; v._hasShownResumeToast = false; triggerResume(v, item); }; if (v.readyState >= 1) { applyOfficialProgress(); } else { v.addEventListener('loadedmetadata', applyOfficialProgress, { once: true }); } }).catch((e) => { console.warn('[OfficialProgress] Apply failed:', e); }); } if (activeHealthTimer) { clearInterval(activeHealthTimer); activeHealthTimer = null; } if (pkHls) { try { pkHls.stopLoad(); } catch (e) {} try { pkHls.detachMedia(); } catch (e) {} try { pkHls.destroy(); } catch (e) {} pkHls = null; } if (activeHlsObjectUrl) { try { URL.revokeObjectURL(activeHlsObjectUrl); } catch (e) {} activeHlsObjectUrl = null; } v._pkMediaToken = currentMediaToken; v._bufferingSince = null; v._blackScreenCount = 0; box.classList.add('buffering'); const isPikPakTsHls = isPikPakTsDownloaderUrl(url); const isM3u8 = url.includes('.m3u8') || isPikPakTsHls; const currentQualityItem = (Array.isArray(qualityList) ? qualityList : []).find(q => { const qUrl = String(q && (q.link || q.url) || ''); return qUrl && (qUrl === url || qUrl === currentLink || String(q.name || '') === String(currentResName || '')); }) || null; if (currentQualityItem && shouldBlockWebPlaybackByOfficialSniff(currentQualityItem)) { console.warn('[VideoCheck] Web playback blocked by official-like codec sniff.', { name: currentQualityItem.name || currentResName, codecText: getPikPakQualityCodecTextForSniff(currentQualityItem), hevcWebSupport: hasLikelyHevcWebPlaybackSupport() }); box.classList.remove('buffering'); const loader = d.querySelector('.pk-p-loading'); if (loader) loader.style.display = 'none'; showSadBox(currentQualityItem.codecText || 'HEVC', 'web_unsupported', { url: String((currentQualityItem.link || currentQualityItem.url) || url || currentLink || ''), name: currentQualityItem.name || currentResName }); return; } const HlsCtor = isPikPakTsHls ? (getPikPakOfficialHlsCtor() || window.Hls) : window.Hls; const usingOfficialPikPakHls = isPikPakTsHls && HlsCtor && HlsCtor !== window.Hls; const HlsEvents = (HlsCtor && HlsCtor.Events) || (window.Hls && window.Hls.Events) || {}; const HlsErrorTypes = (HlsCtor && HlsCtor.ErrorTypes) || (window.Hls && window.Hls.ErrorTypes) || {}; if (isM3u8 && HlsCtor && HlsCtor.isSupported && HlsCtor.isSupported()) { let finalPlayUrl = url; if (url.includes('&ext=.m3u8') || url.includes('?ext=.m3u8')) { finalPlayUrl = buildPikPakTsHlsUrl(url); } let isOfficialPikPakTsHls = isPikPakTsDownloaderUrl(finalPlayUrl); let pikPakMasterTargetName = ''; let pikPakMasterTargetUrl = ''; const hlsLoadStartedAt = Date.now(); if (isOfficialPikPakTsHls) { const targetItem = (Array.isArray(qualityList) ? qualityList : []).find(q => { const qUrl = String(q.link || q.url || ''); return qUrl && (qUrl === finalPlayUrl || q.name === currentResName); }); pikPakMasterTargetName = (targetItem && targetItem.name) || currentResName || ''; pikPakMasterTargetUrl = (targetItem && (targetItem.link || targetItem.url)) || finalPlayUrl; const masterM3u8 = buildPikPakMasterM3u8FromQualityList(qualityList); if (masterM3u8) { const blob = new Blob([masterM3u8], { type: 'audio/mpegurl' }); finalPlayUrl = URL.createObjectURL(blob); activeHlsObjectUrl = finalPlayUrl; } else { try { const manifestRes = await fetch(finalPlayUrl, { method: 'GET', cache: 'no-store', credentials: 'omit' }); if (isStaleMediaSession(currentMediaToken)) return; if (manifestRes && manifestRes.ok) { const rawManifest = await manifestRes.text(); const fixedManifest = normalizePikPakTsM3u8(rawManifest); if (fixedManifest.includes('#EXTM3U')) { const blob = new Blob([fixedManifest], { type: 'application/vnd.apple.mpegurl' }); finalPlayUrl = URL.createObjectURL(blob); activeHlsObjectUrl = finalPlayUrl; } } else { console.warn("[Hls] Failed to prefetch PikPak ts_downloader manifest, fallback to direct HLS URL:", manifestRes && manifestRes.status); } } catch (e) { console.warn("[Hls] Prefetch PikPak ts_downloader manifest failed, fallback to direct HLS URL:", e); } } } const hlsOptions = usingOfficialPikPakHls ? { debug: false, maxBufferHole: 0.5, maxFragLookUpTolerance: 0.2, enableWorker: true, progressive: true, abrBandWidthFactor: 0, abrBandWidthUpFactor: 0, maxMaxBufferLength: 30, maxBufferLength: 10, maxBufferSize: 60 * 1024 * 1024, fragLoadPolicy: { default: { maxTimeToFirstByteMs: 10000, maxLoadTimeMs: 15000, timeoutRetry: { maxNumRetry: 6, retryDelayMs: 1000, maxRetryDelayMs: 0 }, errorRetry: { maxNumRetry: 2, retryDelayMs: 1000, maxRetryDelayMs: 8000 } } }, lowLatencyMode: false, backBufferLength: 30, forceKeyFrameOnDiscontinuity: true, startPosition: startTime, startLevel: -1, capLevelToPlayerSize: false } : { debug: false, enableWorker: true, lowLatencyMode: false, startPosition: startTime, fragLoadingMaxRetry: 3, manifestLoadingMaxRetry: 3, levelLoadingMaxRetry: 3, fragLoadingRetryDelay: 800, manifestLoadingRetryDelay: 800, levelLoadingRetryDelay: 800, fragLoadingTimeOut: 20000, manifestLoadingTimeOut: 15000, maxBufferHole: 0.8, maxFragLookUpTolerance: 0.25, nudgeOffset: 0.1, nudgeMaxRetry: 8, maxBufferLength: 120, maxMaxBufferLength: 600, backBufferLength: 90, startLevel: -1, capLevelToPlayerSize: false }; pkHls = new HlsCtor(hlsOptions); if (HlsEvents.MANIFEST_PARSED) { pkHls.on(HlsEvents.MANIFEST_PARSED, function () { if (isStaleMediaSession(currentMediaToken)) return; if (isOfficialPikPakTsHls && pikPakMasterTargetName) { applyPikPakPreferredHlsLevel(pkHls, pikPakMasterTargetName, pikPakMasterTargetUrl); } }); } pkHls.loadSource(finalPlayUrl); pkHls.attachMedia(v); const healthTimer = setInterval(() => { if (isStaleMediaSession(currentMediaToken) || !pkHls) { clearInterval(healthTimer); if (activeHealthTimer === healthTimer) activeHealthTimer = null; return; } if (box.classList.contains('buffering')) { if (!v._bufferingSince) v._bufferingSince = Date.now(); const isColdStart = v.currentTime < 1.0; const timeoutThreshold = isColdStart ? 30000 : 20000; if (Date.now() - v._bufferingSince > timeoutThreshold) { console.warn(`[Watchdog] Buffering timeout (${isColdStart ? 'Cold Start' : 'Mid-Stream'}). Forcing error...`); clearInterval(healthTimer); if (activeHealthTimer === healthTimer) activeHealthTimer = null; handleVideoError({ force: true, target: v, mediaToken: currentMediaToken }); } } else { v._bufferingSince = null; } if (!v.paused && v.currentTime > 0.5) { if (v.videoWidth === 0 || v.videoHeight === 0) { v._blackScreenCount = (v._blackScreenCount || 0) + 1; } else { const quality = v.getVideoPlaybackQuality ? v.getVideoPlaybackQuality() : null; const decodedFrames = quality ? quality.totalVideoFrames : (v.webkitDecodedFrameCount || 0); if (decodedFrames === 0) { v._blackScreenCount = (v._blackScreenCount || 0) + 1; } else { v._blackScreenCount = 0; } } const blackScreenLimit = isOfficialPikPakTsHls ? 8 : 3; if (v._blackScreenCount > blackScreenLimit) { const quality = v.getVideoPlaybackQuality ? v.getVideoPlaybackQuality() : null; const decodedFrames = quality ? quality.totalVideoFrames : (v.webkitDecodedFrameCount || 0); console.warn(`[Watchdog] Black screen detected.`, { time: v.currentTime, readyState: v.readyState, videoWidth: v.videoWidth, videoHeight: v.videoHeight, decodedFrames, isPikPakTsHls: isOfficialPikPakTsHls, hevcCodec: v._pkHevcTsCodec || '' }); clearInterval(healthTimer); if (activeHealthTimer === healthTimer) activeHealthTimer = null; box.classList.remove('buffering'); const loader = d.querySelector('.pk-p-loading'); if (loader) loader.style.display = 'none'; showSadBox(currentResName || 'HEVC', 'web_unsupported', { url: String(currentLink || ''), name: currentResName || '' }); } } }, 1000); activeHealthTimer = healthTimer; pkHls.on(HlsEvents.LEVEL_LOADED, function (event, data) { if (isStaleMediaSession(currentMediaToken)) return; const details = data && data.details ? data.details : null; const vCodec = String((details && details.videoCodec) || '').toLowerCase(); if (!vCodec) return; const isHevcVideo = isLikelyHevcCodec(vCodec); const isMp4vVideo = vCodec.includes('mp4v'); if (isOfficialPikPakTsHls && isHevcVideo) { v._pkHevcTsSource = true; v._pkHevcTsCodec = vCodec; v._blackScreenCount = 0; return; } if (isMp4vVideo) { const testMime = `video/mp4; codecs="${vCodec}"`; if (window.MediaSource && !window.MediaSource.isTypeSupported(testMime)) { console.warn(`[VideoCheck] Unsupported legacy video codec detected: ${vCodec}`); v.pause(); showSadBox(vCodec); if (pkHls) { pkHls.destroy(); pkHls = null; } } } }); pkHls.on(window.Hls.Events.AUDIO_TRACKS_UPDATED, function (event, data) { if (isStaleMediaSession(currentMediaToken)) return; const tracks = data.audioTracks || []; let detectedBadCodec = null; for (const track of tracks) { const codec = (track.codec || '').toLowerCase(); const name = (track.name || '').toLowerCase(); const isSuspicious = codec.includes('ac-3') || codec.includes('ec-3') || codec.includes('dts') || name.includes('dts') || name.includes('truehd') || name.includes('atmos'); if (isSuspicious) { const testMime = `audio/mp4; codecs="${track.codec || 'ac-3'}"`; if (window.MediaSource && !window.MediaSource.isTypeSupported(testMime)) { detectedBadCodec = codec || name; break; } } } if (detectedBadCodec) { console.warn(`[AudioCheck] Unsupported codec detected: ${detectedBadCodec}`); v.pause(); showSadBox(detectedBadCodec.toUpperCase()); } }); const hasHlsPlaybackEvidence = () => { try { const hasBuffer = v.buffered && v.buffered.length > 0 && v.buffered.end(v.buffered.length - 1) > 0; return v.readyState >= 2 || v.currentTime > 0.2 || hasBuffer || (v.videoWidth > 0 && v.videoHeight > 0); } catch (e) { return v.readyState >= 2 || v.currentTime > 0.2 || (v.videoWidth > 0 && v.videoHeight > 0); } }; pkHls.on(HlsEvents.ERROR, function (event, data) { if (isStaleMediaSession(currentMediaToken)) return; const elapsed = Date.now() - hlsLoadStartedAt; const isColdStart = v.currentTime < 1 && !hasHlsPlaybackEvidence(); if (data.fatal) { switch (data.type) { case HlsErrorTypes.NETWORK_ERROR: if (data.response && (data.response.code === 403 || data.response.code === 404 || data.response.code === 401)) { console.warn(`[Hls] Fatal Network Error (${data.response.code}). Triggering rollback.`); handleVideoError({ force: true, target: v, mediaToken: currentMediaToken }); } else { try { pkHls.startLoad(); } catch (e) {} } break; case HlsErrorTypes.MEDIA_ERROR: console.warn("[Hls] Media error, trying recoverMediaError first.", data.details || data); try { pkHls.recoverMediaError(); } catch (e) {} setTimeout(() => { if (isStaleMediaSession(currentMediaToken)) return; if (!pkHls || box.querySelector('.pk-err-dialog')) return; if (hasHlsPlaybackEvidence()) return; if (isOfficialPikPakTsHls && Date.now() - hlsLoadStartedAt < 12000) { try { pkHls.startLoad(); } catch (e) {} return; } handleVideoError({ force: true, target: v, mediaToken: currentMediaToken }); }, isOfficialPikPakTsHls ? 8000 : 2000); break; default: if (isOfficialPikPakTsHls && isColdStart && elapsed < 15000) { console.warn("[Hls] Fatal error during PikPak TS cold start, delaying rollback once.", data.details || data); try { pkHls.startLoad(); } catch (e) {} return; } handleVideoError({ force: true, target: v, mediaToken: currentMediaToken }); break; } } else if (data.details === 'bufferStalledError') { if (isOfficialPikPakTsHls && isColdStart && elapsed < 20000) { console.warn("[Hls] Ignored cold-start bufferStalledError for PikPak TS source."); try { pkHls.startLoad(); } catch (e) {} return; } if (!isOfficialPikPakTsHls && v.currentTime < 1 && elapsed > 15000) { handleVideoError({ force: true, target: v, mediaToken: currentMediaToken }); } } }); } else { if (isStaleMediaSession(currentMediaToken)) return; const directUrl = String(url || ''); const directItem = (Array.isArray(qualityList) ? qualityList : []).find(q => { const qUrl = String(q && (q.link || q.url) || ''); return qUrl && qUrl === directUrl; }); const isOriginalDirectPlayback = !!(directItem && directItem.isOriginal) || String(currentResName || '') === String(L.str_original); if (isOriginalDirectPlayback) { try { v.pause(); } catch (e) {} try { v.removeAttribute('src'); v.load(); } catch (e) {} v.preload = 'auto'; v.src = directUrl; try { v.load(); } catch (e) {} } else { v.src = directUrl; } if (startTime > 0) { const applyDirectStartTime = () => { if (isStaleMediaSession(currentMediaToken)) return; try { v.currentTime = startTime; } catch (e) {} }; if (v.readyState >= 1) { applyDirectStartTime(); } else { v.addEventListener('loadedmetadata', applyDirectStartTime, { once: true }); } } } }; let initialData = getBestSource(item); let currentLink = initialData.src; let qualityList = initialData.list; let currentResName = initialData.name; let lastWorkingLink = null; let lastWorkingResName = null; const failedUrls = new Set(); let posterUrl = item.thumbnail_link || ''; if (posterUrl && posterUrl.includes('SIZE_MEDIUM')) { posterUrl = posterUrl.replace('SIZE_MEDIUM', 'SIZE_LARGE'); } try { const linkUrl = new URL(currentLink); const linkDomain = linkUrl.origin; if (!document.head.querySelector(`link[href="${linkDomain}"]`)) { const pc = document.createElement('link'); pc.rel = 'preconnect'; pc.href = linkDomain; pc.crossOrigin = 'anonymous'; document.head.appendChild(pc); } } catch(e) {} const d = document.createElement('div'); d.id = 'pk-player-ov'; d.className = 'pk-ov pk-dark'; d.tabIndex = 0; d.style.cssText = "position:fixed;inset:0;z-index:2147483640;background:rgba(0,0,0,0.9);display:flex;justify-content:center;align-items:center;outline:none;will-change:transform;"; const icons = { play: '', playCenter: '', pause: '', vol: '', mute: '', full: '', webFull: '', webExitFull: '', settings: '', exitFull: '', close: '', search: '', pip: '', rotL: '', rotR: '', flipH: '', flipV: '' }; const mkSvg = (path) => `${path}`; const renderQualityMenu = (list, activeName) => list.map(q => { const displayName = q.name.replace(/\s(\(|\uff08)/, '
$1'); return `
${displayName}
`; }).join(''); const styleEl = document.createElement('style'); styleEl.textContent = ` .pk-player-box.ui-hidden .pk-player-top, .pk-player-box.ui-hidden .pk-player-controls, .pk-player-box.ui-hidden .pk-p-prog-wrap, .pk-player-box.ui-hidden .pk-p-side-nav { opacity: 0 !important; pointer-events: none !important; } .pk-player-box.ui-hidden:not(.plist-active) .pk-p-plist-tab { opacity: 0 !important; pointer-events: none !important; } .pk-player-box.ui-hidden .pk-p-center-play { opacity: 1 !important; pointer-events: none; } .pk-player-box.ui-hidden { cursor: none; } .pk-p-pop { flex-direction: column !important; } .pk-p-eye { transition: transform 1.0s cubic-bezier(0.2, 0, 0.2, 1); transform: translate3d(0,0,0); backface-visibility: hidden; image-rendering: -webkit-optimize-contrast; } .pk-look-r .pk-p-eye { transform: translate3d(24px,0,0); } .pk-look-l .pk-p-eye { transform: translate3d(-24px,0,0); } .pk-eye-closed { display: none; } .pk-player-progress-thumb.pk-blink-anim .pk-eye-open { display: none; } .pk-player-progress-thumb.pk-blink-anim .pk-eye-closed { display: block; } .pk-player-progress-thumb { position: absolute; top: 50% !important; transform: perspective(1px) translateY(-50%) scale(0) translateZ(0) !important; opacity: 0 !important; will-change: transform, opacity; transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s !important; image-rendering: -webkit-optimize-contrast; } .pk-player-progress-bg { transition: height 0.2s ease !important; transform: translateZ(0); backface-visibility: hidden; -webkit-perspective: 1000; } #pk_p_prog_area:hover .pk-player-progress-thumb, .pk-player-box.pk-is-seeking .pk-player-progress-thumb, .pk-player-progress-thumb.pk-blink-hold { transform: perspective(1px) translateY(-50%) scale(1) translateZ(0) !important; opacity: 1 !important; } #pk_p_prog_area:hover .pk-player-progress-bg, .pk-player-box.pk-is-seeking .pk-player-progress-bg, .pk-player-progress-bg:has(.pk-blink-hold) { height: 6px !important; } #pk_p_box:not(.pk-is-seeking) #pk_p_plist:hover ~ .pk-player-controls, #pk_p_box:not(.pk-is-seeking) #pk_p_plist:hover ~ .pk-p-prog-wrap { opacity: 0 !important; pointer-events: none !important; } .pk-p-side-nav { position: absolute; top: 50%; transform: translateY(-50%) translateZ(0); width: 60px; height: 60px; background: rgba(0, 0, 0, 0.3); display: flex; align-items: center; justify-content: center; color: #fff; cursor: pointer; z-index: 40; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); opacity: 0; border-radius: 50%; border: 1px solid rgba(255, 255, 255, 0.18); pointer-events: auto !important; box-shadow: 0 4px 15px rgba(0,0,0,0.15); will-change: transform, opacity; } .pk-player-box:hover .pk-p-side-nav { opacity: 1; } .pk-p-side-nav:hover { background: rgba(0, 0, 0, 0.45); transform: translateY(-50%) scale(1.08) translateZ(0); border-color: rgba(255, 255, 255, 0.3); } .pk-p-side-nav.L { left: 30px; } .pk-p-side-nav.R { right: 30px; } .pk-p-side-nav svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 2.8; stroke-linecap: round; stroke-linejoin: round; filter: drop-shadow(0 0 5px rgba(0,0,0,0.2)); } .pk-p-side-nav.L svg { margin-left: -2px; } .pk-p-side-nav.R svg { margin-left: 2px; } .pk-p-sub-pop { position: absolute; bottom: 48px; right: -60px; background: #222; border-radius: 8px; padding: 16px; width: 340px; height: 380px; color: #eee; font-size: 12px; box-shadow: 0 12px 40px rgba(0,0,0,0.5); border: 1px solid #333; display: none; flex-direction: column; cursor: default; z-index: 30; font-family: sans-serif; box-sizing: border-box; } .pk-p-sub-pop::after { content: ""; position: absolute; top: 100%; left: 0; right: 0; height: 12px; background: transparent; } .pk-p-menu-con:hover .pk-p-sub-pop, .pk-p-sub-pop:hover { display: flex; } .pk-sub-tabs { display: flex; border-bottom: 1px solid #333; margin-bottom: 15px; flex-shrink: 0; } .pk-sub-tab { padding: 8px 12px; color: #aaa; cursor: pointer; font-size: 13px; position: relative; } .pk-sub-tab.active { color: #4aa1ff; font-weight: bold; border-bottom: 2px solid #4aa1ff; } .pk-sub-tab.active::after { content: ''; position: absolute; bottom: -1px; left: 0; right: 0; height: 2px; background: #4aa1ff; } .pk-sub-pane { display: none; flex-direction: column; gap: 12px; flex: 1; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #444 transparent; } .pk-sub-pane.active { display: flex; } .pk-sub-radio-grp { display: flex; flex-direction: column; gap: 14px; margin-top: 5px; } .pk-sub-radio-item { display: flex; align-items: center; gap: 10px; cursor: pointer; color: #eee; font-size: 13px; user-select: none; } .pk-sub-radio-item input { width: 16px; height: 16px; accent-color: #4aa1ff; cursor: pointer; margin: 0; } .pk-sub-val-input { width: 50px; background: #333; border: 1px solid #444; color: #fff; border-radius: 4px; padding: 2px 5px; text-align: center; font-size: 12px; outline: none; } .pk-sub-val-input:focus { border-color: #4aa1ff; } .pk-more-sep { height: 1px; background: #333; margin: 15px 0; } .pk-size-row { display: flex; align-items: center; margin-bottom: 15px; } .pk-size-label { min-width: 40px; margin-right: 12px; white-space: nowrap; color: #888; font-size: 12px; flex-shrink: 0; } .pk-size-opts { display: flex; gap: 8px; flex: 1; } .pk-size-btn { flex: 1; background: #333; border: 1px solid #444; color: #ddd; padding: 6px 4px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: all 0.2s; display: flex; align-items: center; justify-content: center; gap: 0; text-align: center; } .pk-size-btn:hover { background: #444; border-color: #555; color: #fff; } .pk-size-btn.active { color: #4aa1ff; border-color: #4aa1ff; background: rgba(74, 161, 255, 0.1); } .pk-size-btn svg { width: 16px; height: 16px; margin-right: 4px; display: block; flex-shrink: 0; margin-bottom: 1px; } .pk-sub-pane::-webkit-scrollbar { width: 6px; } .pk-sub-pane::-webkit-scrollbar-track { background: transparent; } .pk-sub-pane::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; } .pk-sub-pane::-webkit-scrollbar-thumb:hover { background: #555; } .pk-sub-radio-group { display: flex; flex-direction: column; gap: 12px; margin-top: 8px; } .pk-sub-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; } .pk-sub-label { color: #aaa; white-space: nowrap; flex-shrink: 0; margin-right: 10px; } .pk-sub-btn-group { display: flex; gap: 8px; margin-top: 4px; } .pk-sub-btn { flex: 1; background: #333; border: 1px solid #444; color: #ddd; padding: 6px 4px; border-radius: 4px; cursor: pointer; text-align: center; transition: background 0.2s; font-size: 11.5px; line-height: 1.2; display: flex; align-items: center; justify-content: center; } .pk-sub-btn:hover { background: #444; } .pk-sub-btn:active { background: #555; } .pk-sub-ctrl { display: flex; align-items: center; background: #333; border-radius: 4px; border: 1px solid #444; } .pk-sub-ctrl-btn { width: 28px; height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #ddd; } .pk-sub-ctrl-btn:hover { background: #444; } .pk-sub-val { width: 60px; text-align: center; border-left: 1px solid #444; border-right: 1px solid #444; height: 24px; line-height: 24px; font-variant-numeric: tabular-nums; } .pk-sub-slider { -webkit-appearance: none; width: 100px; height: 4px; background: #444; border-radius: 2px; outline: none; } .pk-sub-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 12px; height: 12px; border-radius: 50%; background: #fff; cursor: pointer; } .pk-sub-check { width: 14px; height: 14px; accent-color: #4aa1ff; cursor: pointer; } video::cue { background: rgba(0,0,0,0.5); color: white; font-family: sans-serif; } #pk_p_box { position: absolute !important; top: 10% !important; left: 50% !important; transform: translateX(-50%) !important; width: 90% !important; height: 80% !important; background: #000; border-radius: 0 !important; overflow: visible !important; transition: height 0.2s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 25px 50px rgba(0,0,0,0.5); } #pk_p_box.plist-active { height: calc(80% - 84px) !important; } html.pk-player-web-fullscreen-lock, body.pk-player-web-fullscreen-lock { overflow: hidden !important; } .pk-web-fullscreen-ov { position: fixed !important; inset: 0 !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; max-width: none !important; max-height: none !important; margin: 0 !important; padding: 0 !important; transform-origin: 0 0 !important; display: block !important; justify-content: initial !important; align-items: initial !important; background: #000 !important; overflow: hidden !important; z-index: 2147483646 !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen { position: absolute !important; inset: 0 !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; min-width: 0 !important; max-width: none !important; max-height: none !important; transform: none !important; margin: 0 !important; border-radius: 0 !important; overflow: hidden !important; z-index: 1 !important; box-shadow: none !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen.plist-active { height: 100vh !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen .pk-player-top { position: fixed !important; top: 0 !important; left: 0 !important; right: 0 !important; transform: none !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen #pk_video, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen #pk_p_poster { height: 100% !important; bottom: auto !important; top: 0 !important; transform: translate3d(0, 0, 0) !important; transform-origin: center center !important; will-change: transform; backface-visibility: hidden; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen.plist-active #pk_video, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen.plist-active #pk_p_poster { height: calc(100% - 84px) !important; transform: translate3d(0, 0, 0) !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen.plist-active .pk-p-side-nav { top: 50% !important; transform: translateY(calc(-50% - 42px)) translateZ(0) !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen.plist-active .pk-p-center-play, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen.plist-active .pk-p-seek-indicator, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen.plist-active .pk-p-loading { top: 50% !important; transform: translate(-50%, calc(-50% - 42px)) translateZ(0) !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen #pk_p_plist { top: auto !important; bottom: 0 !important; transform: translateY(100%) translateZ(0); backface-visibility: hidden; perspective: 1000px; transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1); } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen:not(.plist-active) #pk_p_plist { transform: translateY(100%) translateZ(0) !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen:not(.plist-active) .pk-p-plist-strip { opacity: 0 !important; pointer-events: none !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen:not(.plist-active) .pk-player-controls, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen:not(.plist-active) .pk-p-prog-wrap, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen:not(.plist-active) .pk-p-resume-toast { transform: translateY(0) translateZ(0) !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen:not(.plist-active) .pk-p-side-nav { top: 50% !important; transform: translateY(-50%) translateZ(0) !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen:not(.plist-active) .pk-p-center-play, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen:not(.plist-active) .pk-p-seek-indicator, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen:not(.plist-active) .pk-p-loading { top: 50% !important; transform: translate(-50%, -50%) translateZ(0) !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen #pk_video, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen #pk_p_poster { transition: height 0.2s cubic-bezier(0.4, 0, 0.2, 1), transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen .pk-player-controls, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen .pk-p-prog-wrap, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen .pk-p-resume-toast { transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen .pk-p-side-nav, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen .pk-p-center-play, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen .pk-p-seek-indicator, .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen .pk-p-loading { transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.3s ease !important; will-change: transform, opacity; backface-visibility: hidden; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen.paused.pk-v-started:not(.buffering):not(.pk-is-seeking) .pk-p-center-play { animation: none !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen .pk-p-plist-tab { bottom: auto !important; top: 0 !important; transform: translateY(-100%) translateZ(0) !important; margin-top: 1px !important; margin-bottom: 0 !important; backface-visibility: hidden; z-index: 100 !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen.plist-active #pk_p_plist { transform: translateY(0); } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen.plist-active .pk-player-controls { bottom: 0 !important; transform: translateY(-84px) translateZ(0); } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen.plist-active .pk-p-prog-wrap { bottom: 64px !important; transform: translateY(-84px) translateZ(0); } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen.plist-active .pk-p-resume-toast { bottom: 85px !important; transform: translateY(-84px) translateZ(0); } #pk_p_box:fullscreen #pk_p_web_full, #pk_p_box:-webkit-full-screen #pk_p_web_full, #pk_p_box:-moz-full-screen #pk_p_web_full { display: none !important; } #pk_p_box:fullscreen, #pk_p_box:-webkit-full-screen, #pk_p_box:-moz-full-screen { width: 100% !important; height: 100% !important; top: 0 !important; left: 0 !important; transform: none !important; margin: 0 !important; border-radius: 0 !important; overflow: hidden !important; } #pk_video { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: contain; z-index: 10; transition: height 0.52s cubic-bezier(0.22, 1, 0.36, 1), top 0.52s cubic-bezier(0.22, 1, 0.36, 1); } .pk-player-controls { position: absolute; bottom: 0; left: 0; right: 0; z-index: 80 !important; transition: bottom 0.52s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.3s ease; } .pk-p-prog-wrap { position: absolute; bottom: 64px; left: 20px; right: 20px; z-index: 60 !important; display: flex; align-items: center; gap: 16px; transition: bottom 0.52s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.3s ease; pointer-events: none; } .pk-p-prog-wrap > * { pointer-events: auto; } #pk_p_box.pk-tab-hover:not(.pk-is-seeking) .pk-player-controls, #pk_p_box.pk-tab-hover:not(.pk-is-seeking) .pk-p-prog-wrap { opacity: 0 !important; pointer-events: none !important; transition: opacity 0.2s ease; } .pk-player-progress-container { position: relative !important; bottom: auto !important; left: auto !important; right: auto !important; width: auto !important; flex: 1; z-index: auto !important; } .pk-p-side-nav, .pk-p-center-play, .pk-p-seek-indicator { top: 50%; transition: top 0.52s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.3s ease, transform 0.52s cubic-bezier(0.22, 1, 0.36, 1) !important; } #pk_p_plist { position: absolute; top: 100%; left: 0; right: 0; z-index: 95 !important; height: 84px; opacity: 1; pointer-events: none; } .pk-p-plist-tab { pointer-events: auto !important; margin-bottom: -1px !important; z-index: 90 !important; } .pk-p-plist-strip { opacity: 0; pointer-events: none !important; transition: opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1); } #pk_p_box.plist-active .pk-p-plist-strip { opacity: 1; pointer-events: auto !important; } #pk_p_plist .pk-p-plist-strip { transition: opacity 0.12s ease-out !important; } .pk-player-box .pk-p-plist-strip { border-top: none !important; } #pk_p_box:not(.pk-web-fullscreen):not(:fullscreen):not(:-webkit-full-screen) #pk_p_plist .pk-p-plist-tab::before { bottom: -2px !important; } .pk-web-fullscreen-ov #pk_p_box.pk-web-fullscreen .pk-p-plist-tab { margin-top: 2px !important; } .pk-p-loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 35; pointer-events: none; } #pk_p_poster { z-index: 25 !important; transition: opacity 0.4s ease; width: 100% !important; height: 100% !important; background: #000; } .pk-player-box.pk-v-started #pk_p_poster { pointer-events: none; } .pk-transcode-mask { position: absolute; inset: 0; z-index: 30; background: #0c0c0c; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #ddd; font-size: 14px; } .pk-tc-icon { width: 50px; height: 50px; margin-bottom: 20px; border: 3px solid rgba(255,255,255,0.1); border-top-color: var(--pk-pri); border-radius: 50%; animation: spin 1s linear infinite; } .pk-tc-btn { margin-top: 20px; padding: 6px 16px; border: 1px solid #444; border-radius: 20px; cursor: pointer; font-size: 12px; transition: all 0.2s; } .pk-tc-btn:hover { background: #333; border-color: #666; color: #fff; } #pk_p_preview { position: absolute; bottom: 85px; left: 0; display: flex; flex-direction: column; align-items: center; z-index: 100; pointer-events: none; opacity: 0; transform: translateX(-50%) scale(0.95); transform-origin: bottom center; transition: opacity 0.15s ease, transform 0.15s ease; will-change: transform, opacity, left; } #pk_p_preview.show { opacity: 1; transform: translateX(-50%) scale(1); } .pk-prev-img-box { position: relative; background: #000; border: 2px solid #fff; border-radius: 4px; box-shadow: 0 4px 15px rgba(0,0,0,0.5); overflow: hidden; display: none; min-width: 120px; min-height: 68px; transition: width 0.1s, height 0.1s; } .pk-prev-img-box img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity 0.1s ease-out; } .pk-prev-img-box img.active { opacity: 1; } .pk-prev-time { margin-top: 6px; background: rgba(0,0,0,0.85); color: #fff; font-size: 12px; font-weight: 600; text-align: center; padding: 4px 8px; border-radius: 4px; font-family: "Segoe UI", Roboto, monospace; box-shadow: 0 2px 8px rgba(0,0,0,0.3); text-shadow: 0 1px 2px rgba(0,0,0,0.5); } `; document.head.appendChild(styleEl); d.innerHTML = `
00:00
${posterUrl ? `` : ''}
${mkSvg(icons.playCenter)}
${mkSvg(icons.vol)}
100%
00:00 / 00:00
${mkSvg('')}
${mkSvg('')}
[${curListIdx + 1}/${totalInList}] ${esc(item.name)}
${mkSvg(icons.pip)}
${mkSvg(icons.close)}
${curListIdx + 1} / ${totalInList}
${mkSvg('')}
${renderPlaylistItems()}
${mkSvg('')}
00:00:00
00:00:00
${mkSvg(icons.pause)}
${mkSvg(icons.vol)}
${currentResName}
${renderQualityMenu(qualityList, currentResName)}
1.0x
3.0x
2.0x
1.5x
1.25x
1.0x
0.75x
0.5x
${mkSvg(icons.settings)}
${L.tab_sub}
${L.tab_size}
${L.tab_more}
${L.lbl_sub_sel}
${L.str_no_sub}
${L.btn_sub_search}
${L.btn_sub_cloud}
${L.btn_sub_local}
${L.lbl_sub_pos}
${L.lbl_sub_bottom} ${L.lbl_sub_top}
${L.lbl_sub_bg_op}
0% 100%
${L.lbl_sub_size}
20
${L.lbl_sub_offset}
0.0 ${L.unit_sec}
${L.lbl_ratio}
${L.opt_ratio_def}
16:9
4:3
${L.lbl_direction}
${mkSvg(icons.rotL)} ${L.btn_rot_l}
${mkSvg(icons.rotR)} ${L.btn_rot_r}
${mkSvg(icons.flipH)} ${L.btn_flip_h}
${mkSvg(icons.flipV)} ${L.btn_flip_v}
${L.lbl_play_end}
${L.lbl_skip_op}
${L.btn_mark}
0 ${L.unit_sec}
${L.lbl_skip_ed}
${L.btn_mark}
0 ${L.unit_sec}
${icons.webFull}
${mkSvg(icons.full)}
`; const v = d.querySelector('#pk_video'); const box = d.querySelector('#pk_p_box'); const btnPlay = d.querySelector('#pk_p_play'); const btnVol = d.querySelector('#pk_p_vol'); const slideVol = d.querySelector('#pk_p_vol_slide'); const btnWebFull = d.querySelector('#pk_p_web_full'); const btnFull = d.querySelector('#pk_p_full'); const btnClose = d.querySelector('#pk_p_close'); const btnSearch = d.querySelector('#pk_p_search'); const tCur = d.querySelector('#pk_t_cur'); const tDur = d.querySelector('#pk_t_dur'); const progArea = d.querySelector('#pk_p_prog_area'); const progFilled = d.querySelector('#pk_p_filled'); const sharePreviewLimitMarker = d.querySelector('#pk_share_preview_limit_marker'); const resList = d.querySelector('#pk_p_res_list'); const resTxt = d.querySelector('#pk_p_res_txt'); const spdList = d.querySelector('#pk_p_spd_list'); const plist = d.querySelector('#pk_p_plist'); const pTab = d.querySelector('#pk_p_plist_tab'); const pScroll = d.querySelector('#pk_p_plist_scroll'); pScroll.onwheel = (e) => { e.preventDefault(); pScroll.scrollBy({ left: e.deltaY > 0 ? 300 : -300, behavior: 'smooth' }); }; let pTip = document.getElementById('pk_p_plist_tip_global'); if (!pTip) { pTip = document.createElement('div'); pTip.id = 'pk_p_plist_tip_global'; pTip.className = 'pk-p-plist-tip'; document.body.appendChild(pTip); } const updateSharePreviewSoftLimitMarker = () => { if (!sharePreviewLimitMarker) return null; const dur = Number((v && v.duration) || (item && item.params && item.params.duration) || 0); const limit = getSharePreviewLimitParams(item, dur); if (!limit) { sharePreviewLimitMarker.style.display = 'none'; return null; } sharePreviewLimitMarker.style.display = 'block'; sharePreviewLimitMarker.style.left = `${Math.max(0, Math.min(100, limit.ratio * 100))}%`; return limit; }; const maybeToastSharePreviewSoftLimit = () => { if (sharePreviewSoftLimitTipShown || !v) return; const limit = updateSharePreviewSoftLimitMarker(); if (!limit) return; if (Number(v.currentTime || 0) >= Math.max(0, limit.time - 0.25)) { sharePreviewSoftLimitTipShown = true; showToast(getSharePreviewSoftLimitTip(), 'warning', 7000); } }; d.querySelector('#pk_p_side_L').onclick = (e) => { e.stopPropagation(); const prevIdx = (curListIdx - 1 + totalInList) % totalInList; softSwitch(prevIdx); }; d.querySelector('#pk_p_side_R').onclick = (e) => { e.stopPropagation(); const nextIdx = (curListIdx + 1) % totalInList; softSwitch(nextIdx); }; pTab.onmouseenter = () => box.classList.add('pk-tab-hover'); pTab.onmouseleave = () => box.classList.remove('pk-tab-hover'); const strip = d.querySelector('.pk-p-plist-strip'); if (strip) { strip.onmouseenter = () => box.classList.add('pk-tab-hover'); strip.onmouseleave = () => box.classList.remove('pk-tab-hover'); } let webFullScaleRaf = 0; let webFullScaleDelayTimer = 0; let webFullScaleWatchBound = false; const applyWebFullscreenScale = () => { if (!d || !d.classList.contains('pk-web-fullscreen-ov')) return; d.style.removeProperty('transform-origin'); d.style.removeProperty('transform'); }; const scheduleWebFullscreenScale = (withDelay = false) => { if (!d || !d.classList.contains('pk-web-fullscreen-ov')) return; if (!webFullScaleRaf) { webFullScaleRaf = requestAnimationFrame(() => { webFullScaleRaf = 0; applyWebFullscreenScale(); }); } if (withDelay) { if (webFullScaleDelayTimer) clearTimeout(webFullScaleDelayTimer); webFullScaleDelayTimer = setTimeout(() => { webFullScaleDelayTimer = 0; scheduleWebFullscreenScale(false); }, 120); } }; const onWebFullscreenViewportChange = () => { scheduleWebFullscreenScale(true); }; const bindWebFullscreenScaleWatchers = () => { if (webFullScaleWatchBound) return; webFullScaleWatchBound = true; window.addEventListener('resize', onWebFullscreenViewportChange); window.addEventListener('orientationchange', onWebFullscreenViewportChange); if (window.visualViewport) { window.visualViewport.addEventListener('resize', onWebFullscreenViewportChange); } }; const unbindWebFullscreenScaleWatchers = () => { if (!webFullScaleWatchBound) return; webFullScaleWatchBound = false; window.removeEventListener('resize', onWebFullscreenViewportChange); window.removeEventListener('orientationchange', onWebFullscreenViewportChange); if (window.visualViewport) { window.visualViewport.removeEventListener('resize', onWebFullscreenViewportChange); } if (webFullScaleRaf) { cancelAnimationFrame(webFullScaleRaf); webFullScaleRaf = 0; } if (webFullScaleDelayTimer) { clearTimeout(webFullScaleDelayTimer); webFullScaleDelayTimer = 0; } }; const setWebFullscreen = (on) => { if (!box || !btnWebFull) return; const enabled = !!on; if (enabled) { if (d._pkWebFullOldTransform === undefined) { d._pkWebFullOldTransform = d.style.getPropertyValue('transform') || ''; d._pkWebFullOldTransformPriority = d.style.getPropertyPriority('transform') || ''; d._pkWebFullOldTransformOrigin = d.style.getPropertyValue('transform-origin') || ''; d._pkWebFullOldTransformOriginPriority = d.style.getPropertyPriority('transform-origin') || ''; } d.classList.add('pk-web-fullscreen-ov'); bindWebFullscreenScaleWatchers(); applyWebFullscreenScale(); scheduleWebFullscreenScale(true); document.documentElement.classList.add('pk-player-web-fullscreen-lock'); document.body.classList.add('pk-player-web-fullscreen-lock'); } else { unbindWebFullscreenScaleWatchers(); d.classList.remove('pk-web-fullscreen-ov'); if (d._pkWebFullOldTransform !== undefined) { if (d._pkWebFullOldTransform) d.style.setProperty('transform', d._pkWebFullOldTransform, d._pkWebFullOldTransformPriority || ''); else d.style.removeProperty('transform'); if (d._pkWebFullOldTransformOrigin) d.style.setProperty('transform-origin', d._pkWebFullOldTransformOrigin, d._pkWebFullOldTransformOriginPriority || ''); else d.style.removeProperty('transform-origin'); delete d._pkWebFullOldTransform; delete d._pkWebFullOldTransformPriority; delete d._pkWebFullOldTransformOrigin; delete d._pkWebFullOldTransformOriginPriority; } document.documentElement.classList.remove('pk-player-web-fullscreen-lock'); document.body.classList.remove('pk-player-web-fullscreen-lock'); } box.classList.toggle('pk-web-fullscreen', enabled); btnWebFull.classList.toggle('active', enabled); btnWebFull.innerHTML = enabled ? icons.webExitFull : icons.webFull; btnWebFull.setAttribute( 'data-pk-tip', enabled ? (L.tip_exit_web_fullscreen) : (L.tip_web_fullscreen) ); requestAnimationFrame(() => { try { window.dispatchEvent(new Event('resize')); } catch (e) {} }); }; if (btnWebFull) { btnWebFull.onclick = (e) => { e.preventDefault(); e.stopPropagation(); setWebFullscreen(!box.classList.contains('pk-web-fullscreen')); }; } pTab.onclick = (e) => { e.stopPropagation(); const nextOpen = !(plist.classList.contains('open') || box.classList.contains('plist-active')); plist.classList.toggle('open', nextOpen); box.classList.toggle('plist-active', nextOpen); if (!nextOpen) box.classList.remove('pk-tab-hover'); pTab.setAttribute('data-pk-tip', nextOpen ? L.tip_plist_close : L.tip_plist_open); if (nextOpen) { const activeItem = pScroll.querySelector('.active'); centerVideoPlistItem(activeItem, 'instant'); } }; const centerVideoPlistItem = (activeItem, scrollMode = 'smooth') => { if (!activeItem || !pScroll) return; const maxLeft = Math.max(0, pScroll.scrollWidth - pScroll.clientWidth); const targetLeft = Math.max(0, Math.min(maxLeft, activeItem.offsetLeft - (pScroll.clientWidth / 2) + (activeItem.clientWidth / 2))); if (scrollMode === 'instant') { pScroll.style.scrollBehavior = 'auto'; pScroll.scrollLeft = targetLeft; setTimeout(() => { pScroll.style.scrollBehavior = 'smooth'; }, 50); return; } try { pScroll.scrollTo({ left: targetLeft, behavior: scrollMode === 'smooth' ? 'smooth' : 'auto' }); } catch (e) { pScroll.scrollLeft = targetLeft; } }; const bindVideoPlistItems = () => { pScroll.querySelectorAll('.pk-p-plist-item').forEach(el => { el.onmouseenter = (e) => { if (!plist.classList.contains('open')) return; pTip.innerHTML = `${e.currentTarget.dataset.name}
${e.currentTarget.dataset.size}`; pTip.style.display = 'block'; }; el.onmousemove = (e) => { if (pTip.style.display === 'block') { const tW = pTip.offsetWidth || 150; pTip.style.left = (e.clientX - (tW / 2)) + 'px'; pTip.style.top = (e.clientY - 60) + 'px'; } }; el.onmouseleave = () => pTip.style.display = 'none'; el.onclick = (e) => { e.stopPropagation(); if (pTip) pTip.style.display = 'none'; const idx = parseInt(e.currentTarget.dataset.idx, 10); if (idx === curListIdx) return; softSwitch(idx); }; }); }; const syncVideoPlistItems = (scrollMode = 'smooth') => { const RANGE = 150; const desiredStart = Math.max(0, curListIdx - RANGE); const desiredEnd = Math.min(videoPlaylist.length, curListIdx + RANGE + 1); const prevStart = parseInt(pScroll.dataset.pkStart || '-1', 10); const prevEnd = parseInt(pScroll.dataset.pkEnd || '-1', 10); const needRebuild = !Number.isFinite(prevStart) || !Number.isFinite(prevEnd) || curListIdx < prevStart || curListIdx >= prevEnd || pScroll.childElementCount === 0 || pScroll.dataset.pkTotal !== String(videoPlaylist.length); if (needRebuild) { pScroll.innerHTML = renderPlaylistItems(); pScroll.dataset.pkStart = String(desiredStart); pScroll.dataset.pkEnd = String(desiredEnd); pScroll.dataset.pkTotal = String(videoPlaylist.length); bindVideoPlistItems(); } pScroll.querySelectorAll('.pk-p-plist-item').forEach(el => { const idx = parseInt(el.dataset.idx, 10); el.classList.toggle('active', idx === curListIdx); }); const activeItem = pScroll.querySelector('.pk-p-plist-item.active'); if (activeItem && plist.classList.contains('open') && scrollMode !== false) { centerVideoPlistItem(activeItem, scrollMode === 'instant' ? 'instant' : 'smooth'); } if (typeof updatePlistNav === 'function') updatePlistNav(); }; bindVideoPlistItems(); const updatePlistNav = () => { const sl = pScroll.scrollLeft; const sw = pScroll.scrollWidth; const cw = pScroll.clientWidth; d.querySelector('#pk_p_plist_L').style.display = sl <= 5 ? 'none' : 'flex'; d.querySelector('#pk_p_plist_R').style.display = (sl + cw >= sw - 5) ? 'none' : 'flex'; }; d.querySelector('#pk_p_plist_L').onclick = (e) => { e.stopPropagation(); pScroll.scrollBy({ left: -400, behavior: 'smooth' }); setTimeout(updatePlistNav, 300); }; d.querySelector('#pk_p_plist_R').onclick = (e) => { e.stopPropagation(); pScroll.scrollBy({ left: 400, behavior: 'smooth' }); setTimeout(updatePlistNav, 300); }; pScroll.addEventListener('scroll', updatePlistNav, { passive: true }); setTimeout(() => syncVideoPlistItems(false), 50); document.body.appendChild(d); const initL = d.querySelector('#pk_p_side_L'); const initR = d.querySelector('#pk_p_side_R'); if (initL) initL.style.display = curListIdx === 0 ? 'none' : 'flex'; if (initR) initR.style.display = curListIdx === totalInList - 1 ? 'none' : 'flex'; let hideTimer = null; let isMouseOverUI = false; const resetHideTimer = () => { box.classList.remove('ui-hidden'); if (hideTimer) clearTimeout(hideTimer); if (isMouseOverUI) return; hideTimer = setTimeout(() => { if (!v.paused) box.classList.add('ui-hidden'); }, 3000); }; const protectedUIs = [ d.querySelector('.pk-player-top'), d.querySelector('.pk-player-controls'), d.querySelector('.pk-p-prog-wrap') ]; protectedUIs.forEach(ui => { if (!ui) return; ui.addEventListener('mouseenter', () => { isMouseOverUI = true; if (hideTimer) clearTimeout(hideTimer); }); ui.addEventListener('mouseleave', () => { isMouseOverUI = false; resetHideTimer(); }); }); box.addEventListener('mouseleave', () => { if (!isMouseOverUI) box.classList.add('ui-hidden'); }); box.addEventListener('mouseenter', resetHideTimer); box.addEventListener('mousemove', resetHideTimer); box.addEventListener('click', resetHideTimer); box.addEventListener('keydown', resetHideTimer); resetHideTimer(); const handleVideoError = (e = {}) => { const eventToken = e.mediaToken || (e.target && e.target._pkMediaToken) || v._pkMediaToken || 0; if (eventToken && eventToken !== mediaSessionToken) return; if (isSwitching && !eventToken) return; if (!v.getAttribute('src') && !pkHls) return; if (v.networkState === 2 && !v.error && !e.force && !pkHls) return; const errCode = v.error ? v.error.code : (e.force ? 4 : 0); const errMsg = v.error ? v.error.message : ""; console.warn(`[VideoError] Code: ${errCode}, Msg: ${errMsg}, Src: ${v.src || 'HLS'}`); const retryUrl = String(currentLink || v.src || ''); const retryItem = (Array.isArray(qualityList) ? qualityList : []).find(q => { const qUrl = String(q && (q.link || q.url) || ''); return qUrl && qUrl === retryUrl; }); const isOriginalDirectRetry = errCode === 4 && !e.force && !pkHls && retryUrl && !retryUrl.includes('.m3u8') && v._pkOriginalDirectRetryUrl !== retryUrl && (!!(retryItem && retryItem.isOriginal) || String(currentResName || '') === String(L.str_original)); if (isOriginalDirectRetry) { v._pkOriginalDirectRetryUrl = retryUrl; const retryToken = eventToken || v._pkMediaToken || mediaSessionToken; const savedTime = parseFloat(v.currentTime || 0) || 0; setTimeout(() => { if (isStaleMediaSession(retryToken) || currentLink !== retryUrl) return; loadSource(retryUrl, savedTime); v.play().catch(()=>{}); }, 1200); return; } if (currentLink) failedUrls.add(currentLink); if (lastWorkingLink && lastWorkingLink !== currentLink && !failedUrls.has(lastWorkingLink)) { if (resTxt) resTxt.textContent = `${L.str_compat_mode} (${lastWorkingResName})`; const toast = document.createElement('div'); toast.style.cssText = "position:absolute;top:20px;left:50%;transform:translateX(-50%);background:rgba(217, 48, 37, 0.9);color:#fff;padding:6px 12px;border-radius:20px;font-size:12px;z-index:100;animation:pkFadeIn 0.5s;"; toast.textContent = L.str_switch_compat.replace('{n}', lastWorkingResName); box.appendChild(toast); setTimeout(() => toast.remove(), 4000); currentResName = lastWorkingResName; currentLink = lastWorkingLink; const savedTime = v.currentTime; loadSource(currentLink, savedTime); v.play().catch(()=>{}); const resList = d.querySelector('#pk_p_res_list'); if (resList) { resList.innerHTML = renderQualityMenu(qualityList, currentResName); bindResEvents(); } return; } const nextCandidate = qualityList.find(q => { const url = q.link || q.url; return url !== currentLink && !failedUrls.has(url); }); if (nextCandidate) { if (resTxt) resTxt.textContent = `${L.str_compat_mode} (${nextCandidate.name})`; const toast = document.createElement('div'); toast.style.cssText = "position:absolute;top:20px;left:50%;transform:translateX(-50%);background:rgba(33, 150, 243, 0.9);color:#fff;padding:6px 12px;border-radius:20px;font-size:12px;z-index:100;animation:pkFadeIn 0.5s;"; toast.textContent = L.msg_fallback_report.replace('{n}', nextCandidate.name); box.appendChild(toast); setTimeout(()=>toast.remove(), 4000); currentResName = nextCandidate.name; currentLink = nextCandidate.link || nextCandidate.url; const savedTime = v.currentTime; loadSource(currentLink, savedTime); v.play().catch(()=>{}); const resList = d.querySelector('#pk_p_res_list'); if (resList) { resList.innerHTML = renderQualityMenu(qualityList, currentResName); bindResEvents(); } return; } if (errCode !== 4 && !e.force) { return; } const loader = d.querySelector('.pk-p-loading'); if (loader) loader.style.display = 'none'; const sharePreviewTip = getSharePreviewLimitTip(item); if (sharePreviewTip && !sharePreviewLimitTipShown) { sharePreviewLimitTipShown = true; showToast(sharePreviewTip, 'warning', 7000); } showSadBox(currentResName); }; v.addEventListener('error', (evt) => handleVideoError({ nativeEvent: evt, target: v, mediaToken: v._pkMediaToken })); v.addEventListener('loadstart', () => { const errOv = box.querySelector('.pk-err-ov'); if (errOv) errOv.remove(); const loader = d.querySelector('.pk-p-loading'); if (loader) loader.style.display = 'block'; }); (async () => { try { const initReqId = switchReqId; const initItem = item; const targetApiId = getPhysicalId(initItem); const newData = initItem._isShareItem ? await resolveSharePlayableFile(initItem) : await apiGet(targetApiId); if (isPlayerDestroyed || initReqId !== switchReqId || item !== initItem) return; const freshData = getBestSource(newData); qualityList = freshData.list; resList.innerHTML = renderQualityMenu(qualityList, currentResName); bindResEvents(); const shouldSwitchToFreshOriginal = currentResName === L.str_original && freshData.name === L.str_original && freshData.src && freshData.src !== currentLink; if (v.error || !currentLink || shouldSwitchToFreshOriginal || (currentResName === L.str_original && freshData.name !== L.str_original)) { const savedTime = parseFloat(v.currentTime || 0); const shouldReloadOfficialProgress = shouldSwitchToFreshOriginal && savedTime <= 1 && !hasUserSeeked && shouldLoadVideoProgressCache(); currentLink = freshData.src; currentResName = freshData.name; resTxt.textContent = currentResName; loadSource(currentLink, shouldReloadOfficialProgress ? null : savedTime); v.play().catch(()=>{}); resList.innerHTML = renderQualityMenu(qualityList, currentResName); bindResEvents(); } } catch (e) { } })(); const fmtT = (s) => { s = Math.max(0, s || 0); const h = Math.floor(s / 3600); const m = Math.floor((s % 3600) / 60); const sc = Math.floor(s % 60); return String(h).padStart(2, '0') + ":" + String(m).padStart(2, '0') + ":" + String(sc).padStart(2, '0'); }; const fmtFullT = fmtT; const togglePlay = () => { if (v.paused) v.play(); else v.pause(); }; const ThumbnailEngine = (() => { let shadowV = null; let canvas = null; let ctx = null; let isInit = false; let cacheStore = null; let currentReqId = 0; const BASE_HEIGHT = 180; const DISPLAY_HEIGHT = 120; const previewBox = d.querySelector('#pk_p_preview'); const imgBox = d.querySelector('#pk_p_img_box'); const previewTime = previewBox.querySelector('.pk-prev-time'); const init = async () => { if (isInit) return; if (window.localforage) { cacheStore = window.localforage.createInstance({ name: 'pk_thumbs', storeName: 'snapshots' }); } shadowV = document.createElement('video'); shadowV.muted = true; shadowV.crossOrigin = 'anonymous'; shadowV.style.display = 'none'; shadowV.preload = 'auto'; shadowV.src = currentLink; canvas = document.createElement('canvas'); canvas.width = 160; canvas.height = 90; ctx = canvas.getContext('2d'); shadowV.onerror = () => console.warn("[Thumb] Shadow player error"); isInit = true; }; const getCacheKey = (time) => `${getPhysicalId(item)}_${Math.floor(time)}`; const generate = async (time) => { if (!isInit) await init(); if (cacheStore) { const cachedBlob = await cacheStore.getItem(getCacheKey(time)); if (cachedBlob) return URL.createObjectURL(cachedBlob); } return new Promise((resolve, reject) => { const seekHandler = () => { try { const vw = shadowV.videoWidth || 160; const vh = shadowV.videoHeight || 90; const ratio = vw / vh; const renderH = BASE_HEIGHT; const renderW = Math.floor(renderH * ratio); if (canvas.width !== renderW || canvas.height !== renderH) { canvas.width = renderW; canvas.height = renderH; } ctx.drawImage(shadowV, 0, 0, renderW, renderH); canvas.toBlob((blob) => { if (cacheStore) cacheStore.setItem(getCacheKey(time), blob).catch(()=>{}); resolve(URL.createObjectURL(blob)); }, 'image/jpeg', 0.8); } catch (e) { reject(e); } finally { shadowV.removeEventListener('seeked', seekHandler); } }; shadowV.addEventListener('seeked', seekHandler); shadowV.currentTime = time; }); }; const show = async (clientX, rect) => { const boxRect = getLogicalRect(box); const logicalX = clientX; const pos = Math.max(0, Math.min(1, (logicalX - rect.left) / rect.width)); const targetTime = pos * v.duration; if (!isFinite(targetTime)) return; let left = logicalX - boxRect.left; const halfWidth = (imgBox.offsetWidth / 2) || 80; const minX = halfWidth + 10; const maxX = boxRect.width - halfWidth - 10; left = Math.max(minX, Math.min(maxX, left)); previewBox.style.left = `${left}px`; previewTime.textContent = fmtT(targetTime); previewBox.classList.add('show'); const myId = ++currentReqId; try { await sleep(50); if (myId !== currentReqId) return; const url = await generate(targetTime); if (myId !== currentReqId) return; const img = document.createElement('img'); img.src = url; const onImgReady = () => { if (myId !== currentReqId) return; if (img.naturalWidth && img.naturalHeight) { const ratio = img.naturalWidth / img.naturalHeight; imgBox.style.width = `${DISPLAY_HEIGHT * ratio}px`; imgBox.style.height = `${DISPLAY_HEIGHT}px`; } imgBox.style.display = 'flex'; img.classList.add('active'); imgBox.appendChild(img); const oldImages = imgBox.querySelectorAll('img'); if (oldImages.length > 1) { setTimeout(() => { for (let i = 0; i < oldImages.length - 1; i++) { oldImages[i].remove(); } }, 150); } }; if (img.complete) onImgReady(); else img.onload = onImgReady; } catch (e) { } }; const hide = () => { previewBox.classList.remove('show'); imgBox.style.display = 'none'; currentReqId++; setTimeout(() => { if (!previewBox.classList.contains('show')) { const imgs = imgBox.querySelectorAll('img'); imgs.forEach(i => i.remove()); } }, 500); }; const resetSource = (newUrl) => { if (shadowV) shadowV.src = newUrl; }; return { show, hide, resetSource }; })(); const updateState = () => { if (v.paused) { box.classList.add('paused'); btnPlay.innerHTML = mkSvg(icons.play); box.classList.remove('ui-hidden'); if (hideTimer) clearTimeout(hideTimer); } else { box.classList.remove('paused'); btnPlay.innerHTML = mkSvg(icons.pause); resetHideTimer(); } }; const updateVolUI = () => { slideVol.value = v.muted ? 0 : v.volume; btnVol.innerHTML = mkSvg((v.muted || v.volume === 0) ? icons.mute : icons.vol); }; const showVolumeIndicator = () => { const volInd = d.querySelector('#pk_p_vol_indicator'); const volVal = d.querySelector('#pk_p_vol_val'); const volIcon = volInd ? volInd.querySelector('div') : null; if (!volInd || !volVal) return; const isMutedVol = v.muted || v.volume === 0; if (volIcon) volIcon.innerHTML = mkSvg(isMutedVol ? icons.mute : icons.vol); volVal.textContent = Math.round((isMutedVol ? 0 : v.volume) * 100) + '%'; volInd.style.display = 'flex'; box.classList.add('pk-is-vol-active'); clearTimeout(v._volTimer); v._volTimer = setTimeout(() => { volInd.style.display = 'none'; box.classList.remove('pk-is-vol-active'); }, 1000); }; const applyPlayerVolumeDelta = (delta) => { v.muted = false; v.volume = Math.max(0, Math.min(1, v.volume + delta)); updateVolUI(); showVolumeIndicator(); gmSet('pk_vol_muted', false); gmSet('pk_vol_level', v.volume); }; let transcodeTimer = null; const destroyPlayer = () => { if (isPlayerDestroyed) return; isPlayerDestroyed = true; const pTip = document.getElementById('pk_p_plist_tip_global'); if (pTip) pTip.style.display = 'none'; document.removeEventListener('keydown', playerKeyHandler, true); document.removeEventListener('fullscreenchange', syncNativeFullscreenUI); unbindWebFullscreenScaleWatchers(); d.classList.remove('pk-web-fullscreen-ov'); box.classList.remove('pk-web-fullscreen'); document.documentElement.classList.remove('pk-player-web-fullscreen-lock'); document.body.classList.remove('pk-player-web-fullscreen-lock'); if (d._pkWebFullOldTransform !== undefined) { if (d._pkWebFullOldTransform) d.style.setProperty('transform', d._pkWebFullOldTransform, d._pkWebFullOldTransformPriority || ''); else d.style.removeProperty('transform'); if (d._pkWebFullOldTransformOrigin) d.style.setProperty('transform-origin', d._pkWebFullOldTransformOrigin, d._pkWebFullOldTransformOriginPriority || ''); else d.style.removeProperty('transform-origin'); delete d._pkWebFullOldTransform; delete d._pkWebFullOldTransformPriority; delete d._pkWebFullOldTransformOrigin; delete d._pkWebFullOldTransformOriginPriority; } if (document.pictureInPictureElement) { document.exitPictureInPicture().catch(() => {}); } window.removeEventListener('resize', onResizeTransform); if (transcodeTimer) { clearInterval(transcodeTimer); transcodeTimer = null; } if (activeHealthTimer) { clearInterval(activeHealthTimer); activeHealthTimer = null; } if (activeHlsObjectUrl) { try { URL.revokeObjectURL(activeHlsObjectUrl); } catch (e) {} activeHlsObjectUrl = null; } if (shouldSyncOfficialPlayHistory(item) && v.duration > 0 && v.currentTime > 5 && v.duration - v.currentTime > 5) { postOfficialPlayProgress(getPhysicalId(item), v.currentTime, v.duration).catch(() => {}); } v.pause(); v.muted = true; if (pkHls) { pkHls.stopLoad(); pkHls.detachMedia(); pkHls.destroy(); pkHls = null; } const targetId = item.id; const targetIdx = S.display.findIndex(x => x.id === targetId); if (targetIdx !== -1) { S.sel.clear(); S.sel.add(targetId); S.activeId = targetId; const rowTop = getItemScrollTopByIndex(targetIdx); const vpHeight = UI.vp.clientHeight; UI.vp.scrollTop = Math.max(0, rowTop - (vpHeight / 2) + (CONF.rowHeight / 2)); renderVisible(); updateStat(); } v.src = ""; v.load(); if (styleEl) styleEl.remove(); d._pkDestroyPlayer = null; d.remove(); }; d._pkDestroyPlayer = destroyPlayer; v.addEventListener('play', () => { S.navVideoBackArmed = false; updateState(); }); v.addEventListener('pause', updateState); const markStarted = () => { if (isPlayerDestroyed || isSwitching) return; if (box) { box.classList.add('pk-v-started'); stopSpinner(); updateState(); lastWorkingLink = currentLink; lastWorkingResName = currentResName; } }; v.addEventListener('playing', markStarted); v.addEventListener('seeked', () => { if(v.currentTime > 0.1) markStarted(); }); v.addEventListener('click', () => { markStarted(); togglePlay(); }); v.addEventListener('dblclick', (e) => { e.stopPropagation(); btnFull.click(); }); const blockMediaDragWhenErr = (e) => { if (!box.querySelector('.pk-err-dialog')) return; e.preventDefault(); e.stopPropagation(); }; v.addEventListener('dragstart', blockMediaDragWhenErr); const posterEl = d.querySelector('#pk_p_poster'); const loaderEl = d.querySelector('.pk-p-loading'); let shutterTargetTime = 0; let isPiPDesired = false; const stopSpinner = (force = false) => { const isErrorVisible = !!box.querySelector('.pk-err-dialog'); if ((shutterTargetTime > 0 || isErrorVisible) && !force) return; box.classList.remove('buffering'); if (v.paused && v.readyState >= 2) { box.classList.add('pk-v-started'); updateState(); } if (loaderEl) loaderEl.style.display = 'none'; if (posterEl) { posterEl.style.pointerEvents = 'none'; posterEl.style.opacity = '0'; setTimeout(() => { if(posterEl.style.opacity === '0') { posterEl.style.display = 'none'; posterEl.style.pointerEvents = 'auto'; } }, 450); } }; const showSpinner = () => { box.classList.add('buffering'); if (loaderEl) loaderEl.style.display = 'block'; }; v.addEventListener('waiting', showSpinner); v.addEventListener('stalled', showSpinner); v.addEventListener('playing', stopSpinner); v.addEventListener('seeked', stopSpinner); v.addEventListener('canplaythrough', stopSpinner); let lastTextUpdate = 0; const updateTimeUI = () => { requestAnimationFrame(() => { if (isDragSeek) return; if (v.currentTime > 0.1) stopSpinner(); const dur = v.duration; const cur = v.currentTime; const now = performance.now(); if (dur > 0) { const pct = (cur / dur) * 100; progFilled.style.width = `${pct}%`; } updateSharePreviewSoftLimitMarker(); maybeToastSharePreviewSoftLimit(); if (now - lastTextUpdate > 250) { tCur.textContent = fmtT(cur); if (dur > 0 && isFinite(dur)) tDur.textContent = fmtT(dur); lastTextUpdate = now; } }); }; v.addEventListener('timeupdate', updateTimeUI); v.addEventListener('durationchange', updateTimeUI); v.addEventListener('timeupdate', () => { if (shutterTargetTime > 0) { if (v.currentTime >= shutterTargetTime - 0.5 && v.readyState >= 3) { v._isRestarting = false; shutterTargetTime = 0; stopSpinner(true); } } }); let hasCheckedProgress = false; let lastSaveTime = 0; let lastOfficialProgressPostTime = 0; let lastOfficialProgressPostSecond = 0; let officialProgressPosting = false; const patchCurrentHistoryProgressSnapshot = (fileId, curT, totalT) => { if (!S.historyMode) return; const targetId = String(fileId || ''); if (!targetId) return; const progress = Math.round(Number(curT || 0)); const duration = Math.round(Number(totalT || 0)); const ts = Date.now(); const patchItem = (it) => { if (!it || it.id !== targetId) return; it._history_progress = progress; if (duration > 0) it._history_duration = duration; it._history_ts = ts; }; [S.items, S.display].forEach(list => { if (Array.isArray(list)) list.forEach(patchItem); }); const mapItem = S.itemMap && S.itemMap.get(targetId); if (mapItem) patchItem(mapItem); const patchHistoryCache = (cache) => { if (!cache || typeof cache.get !== 'function' || typeof cache.set !== 'function') return; const snap = cache.get('history_root'); if (!snap) return; const list = Array.isArray(snap) ? snap : (Array.isArray(snap.items) ? snap.items : null); if (!list) return; list.forEach(patchItem); if (Array.isArray(snap)) { cache.set('history_root', [...list]); } else { cache.set('history_root', { ...snap, items: [...list] }); } }; patchHistoryCache(S.cache); if (typeof globalCache !== 'undefined') patchHistoryCache(globalCache); if (typeof renderVisible === 'function') renderVisible(); if (typeof updateStat === 'function') updateStat(); }; const syncOfficialPlayProgress = (force = false) => { if (!shouldSyncOfficialPlayHistory(item)) return; const now = Date.now(); const pId = getPhysicalId(item); const curT = Math.floor(Number(v.currentTime || 0)); const totalT = Math.floor(Number(v.duration || 0)); if (!pId || !(curT > 1)) return; if (totalT > 0 && totalT - curT <= 5) return; if (officialProgressPosting) return; if (!force) { if (now - lastOfficialProgressPostTime < 20000) return; if (Math.abs(curT - lastOfficialProgressPostSecond) < 5) return; } lastOfficialProgressPostTime = now; lastOfficialProgressPostSecond = curT; officialProgressPosting = true; postOfficialPlayProgress(pId, curT, totalT) .then(ok => { if (ok) patchCurrentHistoryProgressSnapshot(pId, curT, totalT); }) .finally(() => { officialProgressPosting = false; }); }; const applyPendingIntroSkip = () => { const target = parseFloat(v._pkPendingIntroSkip || 0); if (!target || v._pkIntroSkipApplied || hasUserSeeked || v._isRestarting) return; const dur = v.duration || 0; if (!(dur > 0) || target >= dur) { v._pkPendingIntroSkip = 0; return; } v._pkIntroSkipApplied = true; v._pkPendingIntroSkip = 0; shutterTargetTime = target > 1 ? target : 0; try { v.currentTime = target; } catch (e) {} if (tCur) tCur.textContent = fmtT(target); if (progFilled && dur > 0) { progFilled.style.setProperty('width', `${Math.min(100, (target / dur) * 100)}%`, 'important'); } }; const applyProgress = () => { if (hasCheckedProgress || v.duration <= 0) return; hasCheckedProgress = true; applyPendingIntroSkip(); triggerResume(v, item); }; v.addEventListener('canplay', applyProgress, { once: true }); v.addEventListener('playing', () => { syncOfficialPlayProgress(false); }); v.addEventListener('timeupdate', () => { const now = Date.now(); if (now - lastSaveTime > 3000) { syncOfficialPlayProgress(false); lastSaveTime = now; } }); v.addEventListener('pause', () => { syncOfficialPlayProgress(true); }); v.addEventListener('loadedmetadata', () => { triggerResume(v, item); updateTimeUI(); const dur = v.duration; if (dur > 0 && isFinite(dur)) { const seconds = Math.round(dur); const pId = getPhysicalId(item); gmSet('pk_duration_' + pId, seconds); S.durationMap.set(pId, seconds); if (item.params) item.params.duration = seconds; if (typeof renderVisible === 'function') renderVisible(); } }); const enableMediaControls = () => { if (v.readyState >= 2) { if (btnSearch) btnSearch.style.setProperty('display', 'flex', 'important'); const btnPip = d.querySelector('#pk_p_pip'); if (btnPip && document.pictureInPictureEnabled && !v.disablePictureInPicture) { btnPip.style.setProperty('display', 'flex', 'important'); if (isPiPDesired && !document.pictureInPictureElement) { v.requestPictureInPicture().catch(() => { isPiPDesired = false; }); } } } }; v.addEventListener('loadeddata', enableMediaControls); v.addEventListener('canplay', enableMediaControls); if (v.readyState >= 2) enableMediaControls(); let isDragSeek = false; let hasUserSeeked = false; let initialTimeBeforeDrag = 0; let progRectCache = null; let lastSeekRequestTime = 0; let lastMouseX = 0; const seekIndicator = d.querySelector('#pk_p_seek_indicator'); let seekIndicatorTimer = null; const showSeekIndicatorAt = (targetTime) => { if (!seekIndicator || !v.duration || isNaN(v.duration)) return; seekIndicator.textContent = `${fmtFullT(targetTime)} / ${fmtFullT(v.duration)}`; seekIndicator.style.display = 'flex'; box.classList.add('pk-is-seeking'); clearTimeout(seekIndicatorTimer); seekIndicatorTimer = setTimeout(() => { if (!isDragSeek) { seekIndicator.style.display = 'none'; box.classList.remove('pk-is-seeking'); } }, 900); }; const updateVisualOnly = (clientX) => { if (!progRectCache || !v.duration) return 0; const logicalX = clientX; const pos = Math.max(0, Math.min(1, (logicalX - progRectCache.left) / progRectCache.width)); const targetTime = pos * v.duration; progFilled.style.setProperty('width', `${pos * 100}%`, 'important'); seekIndicator.textContent = `${fmtFullT(targetTime)} / ${fmtFullT(v.duration)}`; if (tCur) tCur.textContent = fmtT(targetTime); return targetTime; }; const updateVideoOnDemand = (targetTime) => { const now = performance.now(); if (v.paused && (now - lastSeekRequestTime > 40)) { v.currentTime = targetTime; lastSeekRequestTime = now; } }; const stopDragging = (isCancel = false) => { if (!isDragSeek) return; if (isCancel) { v.currentTime = initialTimeBeforeDrag; const revertPos = (initialTimeBeforeDrag / v.duration) * 100; progFilled.style.setProperty('width', `${revertPos}%`, 'important'); } else { const finalT = updateVisualOnly(lastMouseX); v.currentTime = finalT; if (finalT > 5) { syncOfficialPlayProgress(true); } } isDragSeek = false; progRectCache = null; document.body.classList.remove('pk-dragging'); box.classList.remove('pk-is-seeking'); if (typeof ThumbnailEngine !== 'undefined') { if (progArea && !progArea.matches(':hover')) { ThumbnailEngine.hide(); } } const thumb = box.querySelector('.pk-player-progress-thumb'); if (thumb) { thumb.classList.remove('pk-look-r', 'pk-look-l'); thumb.classList.add('pk-blink-anim', 'pk-blink-hold'); setTimeout(() => thumb.classList.remove('pk-blink-anim'), 200); setTimeout(() => thumb.classList.remove('pk-blink-hold'), 300); } seekIndicator.style.display = 'none'; updateState(); }; const onMouseMove = (e) => { if (!isDragSeek) return; const topBar = d.querySelector('.pk-player-top'); if (topBar) { const barRect = getLogicalRect(topBar); if (e.clientY >= barRect.top && e.clientY <= barRect.bottom) { stopDragging(true); return; } } const thumb = box.querySelector('.pk-player-progress-thumb'); if (thumb) { if (e.clientX > lastMouseX + 1) { thumb.classList.add('pk-look-r'); thumb.classList.remove('pk-look-l'); } else if (e.clientX < lastMouseX - 1) { thumb.classList.add('pk-look-l'); thumb.classList.remove('pk-look-r'); } } lastMouseX = e.clientX; const targetT = updateVisualOnly(e.clientX); updateVideoOnDemand(targetT); if (typeof ThumbnailEngine !== 'undefined' && progRectCache) { ThumbnailEngine.show(e.clientX, progRectCache); } }; progArea.addEventListener('mousedown', (e) => { if (!v.duration || isNaN(v.duration)) return; if (typeof ThumbnailEngine !== 'undefined') ThumbnailEngine.hide(); isDragSeek = true; hasUserSeeked = true; lastMouseX = e.clientX; initialTimeBeforeDrag = v.currentTime; progRectCache = getLogicalRect(progArea); document.body.classList.add('pk-dragging'); box.classList.add('pk-is-seeking'); seekIndicator.style.display = 'flex'; const targetT = updateVisualOnly(e.clientX); v.currentTime = targetT; e.preventDefault(); }); progArea.addEventListener('mousemove', (e) => { const rect = getLogicalRect(progArea); ThumbnailEngine.show(e.clientX, rect); }); progArea.addEventListener('mouseleave', () => { ThumbnailEngine.hide(); }); document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', () => stopDragging(false)); box.addEventListener('mouseleave', (e) => { if (isDragSeek) { const rect = getLogicalRect(box); const lx = e.clientX, ly = e.clientY; if (lx <= rect.left || lx >= rect.right || ly <= rect.top || ly >= rect.bottom) { stopDragging(true); } } }); btnPlay.onclick = togglePlay; btnClose.onclick = destroyPlayer; const syncNativeFullscreenUI = () => { const isNativeFullscreen = document.fullscreenElement === box; if (btnFull) { btnFull.innerHTML = mkSvg(isNativeFullscreen ? icons.exitFull : icons.full); } if (btnWebFull) { btnWebFull.style.display = isNativeFullscreen ? 'none' : ''; } }; btnFull.onclick = async (e) => { if (e) { e.preventDefault(); e.stopPropagation(); } try { if (!document.fullscreenElement) { if (box.classList.contains('pk-web-fullscreen')) { setWebFullscreen(false); } await box.requestFullscreen(); } else { await document.exitFullscreen(); } } catch (err) { console.warn('[Player] Fullscreen switch failed:', err); } finally { syncNativeFullscreenUI(); } }; document.addEventListener('fullscreenchange', syncNativeFullscreenUI); syncNativeFullscreenUI(); const subPanel = d.querySelector('#pk_sub_panel'); if (subPanel) { btnFull.addEventListener('mouseenter', () => subPanel.style.setProperty('display', 'none', 'important')); btnFull.addEventListener('mouseleave', () => subPanel.style.removeProperty('display')); } const btnPip = d.querySelector('#pk_p_pip'); if (btnPip) { if (!document.pictureInPictureEnabled || v.disablePictureInPicture) { btnPip.style.display = 'none'; } else { btnPip.onclick = async (e) => { e.stopPropagation(); try { if (document.pictureInPictureElement) { isPiPDesired = false; await document.exitPictureInPicture(); } else { isPiPDesired = true; await v.requestPictureInPicture(); window.focus(); } } catch (err) { console.error("PiP Error:", err); } }; } } btnSearch.onclick = (e) => { e.stopPropagation(); if (v) { v.pause(); setTimeout(() => { if (v) v.pause(); }, 100); } const posterImg = d.querySelector('#pk_p_poster img'); if (v.readyState >= 2 && v.videoWidth > 0) { startImageSearch(v, item.name, d, null); } else if (posterImg && posterImg.style.display !== 'none' && posterImg.src) { startImageSearch(posterImg, item.name, d, posterImg.src); } else { startImageSearch(v, item.name, d, null); } }; const initPosterImg = d.querySelector('#pk_p_poster img'); if (initPosterImg) { initPosterImg.addEventListener('dragstart', blockMediaDragWhenErr); if (initPosterImg.complete && initPosterImg.src && initPosterImg.src.startsWith('http')) { if (btnSearch) btnSearch.style.setProperty('display', 'flex', 'important'); } else { initPosterImg.addEventListener('load', () => { if (btnSearch) btnSearch.style.setProperty('display', 'flex', 'important'); }); } } const savedMute = gmGet('pk_vol_muted', false); const savedVol = parseFloat(gmGet('pk_vol_level', 1.0)); v.muted = savedMute; v.volume = (Number.isFinite(savedVol) && savedVol >= 0 && savedVol <= 1) ? savedVol : 1.0; if (slideVol) slideVol.value = v.volume; updateVolUI(); btnVol.onclick = () => { if (v.muted || v.volume <= 0) { const minVol = Math.max(0.01, Math.min(1, Number(CONF.playerVolumeStep) || 0.05)); v.muted = false; v.volume = minVol; if (slideVol) slideVol.value = String(minVol); gmSet('pk_vol_muted', false); gmSet('pk_vol_level', minVol); } else { v.muted = true; gmSet('pk_vol_muted', true); } updateVolUI(); showVolumeIndicator(); }; slideVol.oninput = (e) => { v.muted = false; const val = parseFloat(e.target.value); v.volume = val; updateVolUI(); gmSet('pk_vol_muted', false); gmSet('pk_vol_level', val); }; spdList.querySelectorAll('.pk-p-item').forEach(item => { item.onclick = () => { const s = parseFloat(item.dataset.spd); v.playbackRate = s; spdList.querySelector('.active').classList.remove('active'); item.classList.add('active'); d.querySelector('#pk_p_spd_txt').textContent = item.textContent; }; }); function bindResEvents() { resList.querySelectorAll('.pk-p-item').forEach(item => { item.onclick = () => { const link = item.dataset.link; if(link === currentLink) return; const curT = v.currentTime; const isPaused = v.paused; const curRate = v.playbackRate; box.classList.add('buffering'); if (loaderEl) loaderEl.style.display = 'block'; if (posterEl) { if (v.readyState >= 2 && v.currentTime > 0) { try { const canvas = document.createElement('canvas'); canvas.width = v.videoWidth; canvas.height = v.videoHeight; canvas.getContext('2d').drawImage(v, 0, 0); const posterImg = posterEl.querySelector('img'); if (posterImg) { posterImg.src = canvas.toDataURL('image/jpeg', 0.7); posterImg.style.filter = 'brightness(0.8)'; } } catch (e) { console.warn("[ClaritySwitch] Frame capture failed."); } } posterEl.style.transition = 'none'; posterEl.style.display = 'flex'; posterEl.style.opacity = '1'; posterEl.style.pointerEvents = 'auto'; } currentLink = link; currentResName = item.textContent.trim(); shutterTargetTime = curT > 0.1 ? curT : 0; v.pause(); loadSource(link, curT); v.playbackRate = curRate; if(!isPaused) v.play().catch(()=>{}); resList.innerHTML = renderQualityMenu(qualityList, currentResName); if(resTxt) resTxt.textContent = currentResName; bindResEvents(); }; }); } bindResEvents(); const subState = { hasSub: false, size: 24, pos: 10, offset: 0, bgOpacity: 0.6, track: null, blobUrl: null }; const createFreshSubtitleTrack = () => { if (v.textTracks) { for (let i = 0; i < v.textTracks.length; i++) { const t = v.textTracks[i]; if (t === subState.track || t.label === 'pk-subs') { try { t.oncuechange = null; } catch(e) {} try { t.mode = 'disabled'; } catch(e) {} } } } const txtEl = d.querySelector('#pk_sub_text'); if (txtEl) { txtEl.innerHTML = ''; txtEl.style.display = 'none'; } const targetTrack = v.addTextTrack("subtitles", "pk-subs", "en"); targetTrack.mode = 'hidden'; subState.track = targetTrack; return targetTrack; }; const processSubtitleFile = (file) => { if (!file) return; const ext = file.name.split('.').pop().toLowerCase(); if (!['srt', 'vtt', 'ass', 'ssa'].includes(ext)) { showToast(L.err_sub_drop_type); return; } const reader = new FileReader(); reader.onload = (evt) => { const buffer = evt.target.result; const encodings = ['utf-8', 'gbk', 'big5', 'utf-16le', 'shift_jis']; let text = ""; for (let enc of encodings) { try { const decoder = new TextDecoder(enc, { fatal: true }); text = decoder.decode(buffer); break; } catch (e) { if(enc === 'shift_jis') text = new TextDecoder('utf-8').decode(buffer); } } if (subState.blobUrl) URL.revokeObjectURL(subState.blobUrl); let vttText = ""; if (ext === 'ass' || ext === 'ssa') { vttText = convertAssToVtt(text); } else { vttText = convertSrtToVtt(text); } const blob = new Blob([vttText], { type: 'text/vtt' }); subState.blobUrl = URL.createObjectURL(blob); const targetTrack = createFreshSubtitleTrack(); targetTrack.oncuechange = () => { const cues = targetTrack.activeCues; const txtEl = d.querySelector('#pk_sub_text'); if (cues && cues.length > 0 && txtEl) { const text = cues[cues.length - 1].text; txtEl.innerHTML = text.replace(/<[^>]+>/g, '').replace(/\n/g, '
'); txtEl.style.display = 'block'; } else if (txtEl) { txtEl.style.display = 'none'; } }; const vttLines = vttText.split('\n'); let cueStart = null, cueEnd = null, cueText = []; const timeReg = /(\d{2}):(\d{2}):(\d{2})[.,](\d{3})\s*-->\s*(\d{2}):(\d{2}):(\d{2})[.,](\d{3})/; while (targetTrack.cues && targetTrack.cues.length > 0) targetTrack.removeCue(targetTrack.cues[0]); vttLines.forEach(line => { const match = line.match(timeReg); if (match) { if (cueStart !== null && cueText.length > 0) { try { targetTrack.addCue(new VTTCue(cueStart, cueEnd, cueText.join('\n'))); } catch(e){} } cueStart = parseInt(match[1])*3600 + parseInt(match[2])*60 + parseInt(match[3]) + parseInt(match[4])/1000; cueEnd = parseInt(match[5])*3600 + parseInt(match[6])*60 + parseInt(match[7]) + parseInt(match[8])/1000; cueText = []; } else if (line.trim() !== '' && !line.trim().startsWith('WEBVTT') && !/^\d+$/.test(line.trim())) { if (cueStart !== null) cueText.push(line.trim()); } }); if (cueStart !== null && cueText.length > 0) { try { targetTrack.addCue(new VTTCue(cueStart, cueEnd, cueText.join('\n'))); } catch(e){} } subState.hasSub = true; subState.track = targetTrack; subState.track.mode = 'hidden'; updateSubStyle(); d.querySelector('#pk_sub_toggle').checked = true; d.querySelector('#pk_sub_name').textContent = file.name; updateSubStyle(); }; reader.readAsArrayBuffer(file); }; box.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); box.style.boxShadow = "inset 0 0 50px var(--pk-pri)"; }); box.addEventListener('dragleave', (e) => { e.preventDefault(); e.stopPropagation(); box.style.boxShadow = "0 25px 50px rgba(0,0,0,0.5)"; }); box.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); box.style.boxShadow = "0 25px 50px rgba(0,0,0,0.5)"; const file = e.dataTransfer.files[0]; processSubtitleFile(file); }); const subStyleTag = document.createElement('style'); document.head.appendChild(subStyleTag); const updateSubStyle = () => { const txt = d.querySelector('#pk_sub_text'); const layer = d.querySelector('#pk_sub_render_layer'); const toggle = d.querySelector('#pk_sub_toggle'); if (txt && layer) { txt.style.fontSize = `${subState.size}px`; txt.style.backgroundColor = `rgba(0,0,0,${subState.bgOpacity})`; layer.style.paddingBottom = `${subState.pos / 2}%`; const isShow = toggle ? toggle.checked : true; layer.style.display = isShow ? 'flex' : 'none'; } if (subState.track) { subState.track.mode = 'hidden'; } }; const fileInp = d.querySelector('#pk_sub_file'); d.querySelector('#pk_sub_local_btn').onclick = (e) => { e.stopPropagation(); fileInp.click(); }; fileInp.onchange = (e) => { processSubtitleFile(e.target.files[0]); }; const cleanSubText = (text) => { return text.replace(/\{[^}]*?\}/g, '') .replace(/<\/?i>/g, '') .replace(/\\N/gi, '\n') .replace(/\r\n/g, '\n') .trim(); }; const convertAssToVtt = (assText) => { let vtt = "WEBVTT\n\n"; const lines = assText.split('\n'); let inEvents = false; let count = 1; const fmtTime = (t) => { if (!t) return "00:00:00.000"; const parts = t.trim().split('.'); const hms = parts[0].split(':'); const ms = (parts[1] || '00').padEnd(3, '0'); return `${hms[0].padStart(2,'0')}:${hms[1]}:${hms[2]}.${ms}`; }; for (let line of lines) { line = line.trim(); if (line.startsWith('[Events]')) { inEvents = true; continue; } if (!inEvents || !line.startsWith('Dialogue:')) continue; const parts = line.split(','); if (parts.length < 10) continue; const start = fmtTime(parts[1]); const end = fmtTime(parts[2]); const rawText = parts.slice(9).join(','); const text = cleanSubText(rawText); if (text) { vtt += `${count++}\n${start} --> ${end}\n${text}\n\n`; } } return vtt; }; const convertSrtToVtt = (srtText) => { if (/^WEBVTT/i.test(srtText)) return srtText; let vtt = "WEBVTT\n\n"; const text = srtText.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); const regex = /(\d{2}:\d{2}:\d{2}[,.]\d{3})\s*-->\s*(\d{2}:\d{2}:\d{2}[,.]\d{3})/g; let match; const cues = []; while ((match = regex.exec(text)) !== null) { cues.push({ start: match[1], end: match[2], index: match.index, endOfLine: regex.lastIndex }); } let count = 1; for (let i = 0; i < cues.length; i++) { const cue = cues[i]; const nextCue = cues[i + 1]; const contentStart = cue.endOfLine; const contentEnd = nextCue ? nextCue.index : text.length; let rawContent = text.substring(contentStart, contentEnd); rawContent = rawContent.replace(/\n+\s*\d+\s*$/, ''); const cleanContent = cleanSubText(rawContent); const vttStart = cue.start.replace(/,/g, '.'); const vttEnd = cue.end.replace(/,/g, '.'); if (cleanContent) { vtt += `${count++}\n${vttStart} --> ${vttEnd}\n${cleanContent}\n\n`; } } return vtt; }; const autoMatchSubtitle = async (videoItem) => { const parentId = videoItem.parent_id; if (!parentId) return; const videoNameBase = videoItem.name.substring(0, videoItem.name.lastIndexOf('.')); let files = []; if (typeof globalCache !== 'undefined' && globalCache.has(parentId)) { files = globalCache.get(parentId); } else { try { files = await apiList(parentId, 100, null, null, false, true); } catch(e) { return; } } if (!files || files.length === 0) return; const subFiles = files.filter(f => !f.trashed && f.kind !== 'drive#folder' && /\.(srt|vtt|ass|ssa)$/i.test(f.name) ); if (subFiles.length === 0) return; let targetSub = subFiles.find(f => { const subBase = f.name.substring(0, f.name.lastIndexOf('.')); return subBase === videoNameBase; }); if (!targetSub) { targetSub = subFiles.find(f => f.name.includes(videoNameBase)); } if (targetSub) { const box = d.querySelector('#pk_p_box'); const toast = document.createElement('div'); toast.style.cssText = "position:absolute;top:80px;right:20px;background:rgba(0,0,0,0.6);color:#fff;padding:6px 12px;border-radius:4px;font-size:12px;pointer-events:none;animation:pkFadeIn 0.5s;z-index:90;"; toast.textContent = L.msg_auto_sub_load.replace('{n}', targetSub.name); box.appendChild(toast); setTimeout(()=>toast.remove(), 4000); try { let link = targetSub.web_content_link; if (!link) { const detail = await apiGet(targetSub.id); link = detail.web_content_link; } const res = await fetch(link); const buffer = await res.arrayBuffer(); const encodings = ['utf-8', 'gbk', 'big5', 'utf-16le', 'shift_jis']; let text = ""; for (let enc of encodings) { try { const decoder = new TextDecoder(enc, { fatal: true }); text = decoder.decode(buffer); break; } catch (e) { if(enc === 'shift_jis') text = new TextDecoder('utf-8').decode(buffer); } } let vttText = ""; const subExt = targetSub.name.split('.').pop().toLowerCase(); if (subExt === 'ass' || subExt === 'ssa') { vttText = convertAssToVtt(text); } else { vttText = convertSrtToVtt(text); } const blob = new Blob([vttText], { type: 'text/vtt' }); if (subState.blobUrl) URL.revokeObjectURL(subState.blobUrl); subState.blobUrl = URL.createObjectURL(blob); const targetTrack = createFreshSubtitleTrack(); targetTrack.oncuechange = () => { const cues = targetTrack.activeCues; const txtEl = d.querySelector('#pk_sub_text'); if (cues && cues.length > 0 && txtEl) { const text = cues[cues.length - 1].text; txtEl.innerHTML = text.replace(/<[^>]+>/g, '').replace(/\n/g, '
'); txtEl.style.display = 'block'; } else if (txtEl) { txtEl.style.display = 'none'; } }; const vttLines = vttText.split('\n'); let cueStart = null, cueEnd = null, cueText = []; const timeReg = /(\d{2}):(\d{2}):(\d{2})[.,](\d{3})\s*-->\s*(\d{2}):(\d{2}):(\d{2})[.,](\d{3})/; while (targetTrack.cues && targetTrack.cues.length > 0) targetTrack.removeCue(targetTrack.cues[0]); vttLines.forEach(line => { const match = line.match(timeReg); if (match) { if (cueStart !== null && cueText.length > 0) { try { targetTrack.addCue(new VTTCue(cueStart, cueEnd, cueText.join('\n'))); } catch(e){} } cueStart = parseInt(match[1])*3600 + parseInt(match[2])*60 + parseInt(match[3]) + parseInt(match[4])/1000; cueEnd = parseInt(match[5])*3600 + parseInt(match[6])*60 + parseInt(match[7]) + parseInt(match[8])/1000; cueText = []; } else if (line.trim() !== '' && !line.trim().startsWith('WEBVTT') && !/^\d+$/.test(line.trim())) { if (cueStart !== null) cueText.push(line.trim()); } }); if (cueStart !== null && cueText.length > 0) { try { targetTrack.addCue(new VTTCue(cueStart, cueEnd, cueText.join('\n'))); } catch(e){} } subState.hasSub = true; subState.track = targetTrack; subState.track.mode = 'hidden'; updateSubStyle(); const toggle = d.querySelector('#pk_sub_toggle'); if(toggle) toggle.checked = true; const nameLabel = d.querySelector('#pk_sub_name'); if(nameLabel) nameLabel.textContent = targetSub.name; updateSubStyle(); } catch (e) { console.warn("[AutoSub] Load failed", e); } } }; const btnCloudSub = d.querySelector('#pk_sub_cloud_btn'); btnCloudSub.onclick = async (e) => { e.stopPropagation(); const subPanel = d.querySelector('#pk_sub_panel'); const subTrigger = d.querySelector('#pk_sub_trigger'); if (subPanel && subTrigger) { subPanel.style.setProperty('display', 'none', 'important'); const clearDisplay = () => { subPanel.style.removeProperty('display'); subTrigger.removeEventListener('mouseleave', clearDisplay); }; subTrigger.addEventListener('mouseleave', clearDisplay); } if (v && !v.paused) v.pause(); const originalText = btnCloudSub.textContent; btnCloudSub.textContent = L.loading; btnCloudSub.style.opacity = "0.6"; btnCloudSub.style.pointerEvents = "none"; let startPath = [{ id: '', name: L.btn_nav_home }]; let targetFolderId = ''; try { let targetItem = item; if (S.offlineMode || S.uploadMode || S.recentMode || item.kind === 'drive#task') { const realFileId = (item.kind === 'drive#task' || S.offlineMode || S.uploadMode) ? (item.file_id || (item.params && item.params.file_id)) : item.id; if (realFileId) { try { targetItem = await apiGet(realFileId); } catch(err) { console.warn("[CloudSub] Failed to resolve task file:", err); } } } if (targetItem.parent_id && targetItem.parent_id !== 'root') { targetFolderId = targetItem.parent_id; } if (targetItem._lineage && Array.isArray(targetItem._lineage) && targetItem._lineage.length > 0) { startPath = targetItem._lineage.map(x => ({ id: x.id || '', name: x.name })); if (startPath.length > 0 && startPath[0].id !== '' && startPath[0].id !== 'root') { startPath.unshift({ id: '', name: L.btn_nav_home }); } } else if (targetFolderId) { const trace = []; let curr = targetFolderId; let safety = 6; while (curr && curr !== 'root' && safety > 0) { try { const f = await apiGet(curr); trace.unshift({ id: f.id, name: f.name }); curr = f.parent_id; } catch(e) { break; } safety--; } if (trace.length > 0) { startPath = [{ id: '', name: L.btn_nav_home }, ...trace]; } } else if (S.path && S.path.length > 0) { const cleanPath = S.path.filter(p => !p.id.startsWith('virtual_') && !p.id.includes('_root') && p.id !== 'analyze_root'); if (cleanPath.length > 0) { startPath = cleanPath; if (startPath[0].id !== '' && startPath[0].id !== 'root') { startPath.unshift({ id: '', name: L.btn_nav_home }); } } } } catch (error) { console.error("[CloudSub] Path resolve error:", error); targetFolderId = ''; startPath = [{ id: '', name: L.btn_nav_home }]; } finally { btnCloudSub.textContent = originalText; btnCloudSub.style.opacity = "1"; btnCloudSub.style.pointerEvents = "auto"; } showFolderSelector( targetFolderId, async (id, name, subItem) => { const box = d.querySelector('#pk_p_box'); const toast = document.createElement('div'); toast.style.cssText = "position:absolute;top:80px;right:20px;background:rgba(0,0,0,0.8);color:#fff;padding:8px 16px;border-radius:4px;font-size:13px;pointer-events:none;z-index:90;display:flex;align-items:center;gap:8px;"; toast.innerHTML = `
${L.msg_dl_sub}`; box.appendChild(toast); try { let link = subItem.web_content_link; if (!link) { const detail = await apiGet(subItem.id); link = detail.web_content_link; } const res = await fetch(link); const buffer = await res.arrayBuffer(); const encodings = ['utf-8', 'gbk', 'big5', 'utf-16le', 'shift_jis', 'windows-1252']; let text = ""; for (let enc of encodings) { try { const decoder = new TextDecoder(enc, { fatal: true }); text = decoder.decode(buffer); break; } catch (e) { if(enc === 'windows-1252') text = new TextDecoder('utf-8').decode(buffer); } } let vttText = ""; const subExt = subItem.name.split('.').pop().toLowerCase(); if (subExt === 'ass' || subExt === 'ssa') { vttText = convertAssToVtt(text); } else { vttText = convertSrtToVtt(text); } const blob = new Blob([vttText], { type: 'text/vtt' }); if (subState.blobUrl) URL.revokeObjectURL(subState.blobUrl); subState.blobUrl = URL.createObjectURL(blob); const targetTrack = createFreshSubtitleTrack(); targetTrack.oncuechange = () => { const cues = targetTrack.activeCues; const txtEl = d.querySelector('#pk_sub_text'); if (cues && cues.length > 0 && txtEl) { const text = cues[cues.length - 1].text; txtEl.innerHTML = text.replace(/<[^>]+>/g, '').replace(/\n/g, '
'); txtEl.style.display = 'block'; } else if (txtEl) { txtEl.style.display = 'none'; } }; const vttLines = vttText.split('\n'); let cueStart = null, cueEnd = null, cueText = []; const timeReg = /(\d{2}):(\d{2}):(\d{2})[.,](\d{3})\s*-->\s*(\d{2}):(\d{2}):(\d{2})[.,](\d{3})/; while (targetTrack.cues && targetTrack.cues.length > 0) targetTrack.removeCue(targetTrack.cues[0]); vttLines.forEach(line => { const match = line.match(timeReg); if (match) { if (cueStart !== null && cueText.length > 0) { try { targetTrack.addCue(new VTTCue(cueStart, cueEnd, cueText.join('\n'))); } catch(e){} } cueStart = parseInt(match[1])*3600 + parseInt(match[2])*60 + parseInt(match[3]) + parseInt(match[4])/1000; cueEnd = parseInt(match[5])*3600 + parseInt(match[6])*60 + parseInt(match[7]) + parseInt(match[8])/1000; cueText = []; } else if (line.trim() !== '' && !line.trim().startsWith('WEBVTT') && !/^\d+$/.test(line.trim())) { if (cueStart !== null) cueText.push(line.trim()); } }); if (cueStart !== null && cueText.length > 0) { try { targetTrack.addCue(new VTTCue(cueStart, cueEnd, cueText.join('\n'))); } catch(e){} } subState.hasSub = true; subState.track = targetTrack; subState.track.mode = 'hidden'; updateSubStyle(); const toggle = d.querySelector('#pk_sub_toggle'); if(toggle) toggle.checked = true; const nameLabel = d.querySelector('#pk_sub_name'); if(nameLabel) nameLabel.textContent = subItem.name; updateSubStyle(); toast.innerHTML = `✅ ${L.msg_auto_sub_load.replace('{n}', subItem.name)}`; setTimeout(() => toast.remove(), 3000); } catch (err) { console.error(err); toast.innerHTML = `❌ ${L.err_sub_dl_fail}`; setTimeout(() => toast.remove(), 4000); } }, startPath, (f) => /\.(srt|vtt|ass|ssa)$/i.test(f.name), L.title_sel_sub ); }; const loadJSZip = () => { if (window.JSZip) return Promise.resolve(window.JSZip); return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js'; script.onload = () => resolve(window.JSZip); script.onerror = () => reject(new Error(L.msg_jszip_fail)); document.head.appendChild(script); }); }; const cleanFilename = (name) => { let n = name.toLowerCase(); n = n.replace(/\.[^/.]+$/, ""); n = n.replace(/^(\d{2,4}|[a-z]\d{2,4})[\.\s\-\_]+/, ""); const garbage = [ /\b(1080p|720p|2160p|4k|uhd|hd)\b.*/, /\b(bluray|web-dl|webrip|remux|hdtv)\b.*/, /\b(x264|x265|hevc|h264|aac|dts|ac3)\b.*/, /\[.*?\]/g, /\(.*?\)/g, /\{.*?\}/g ]; garbage.forEach(g => n = n.replace(g, '')); n = n.replace(/[\._\+]/g, ' ').trim(); const episodeMatch = n.match(/(.*?)s\d+e\d+/); if (episodeMatch) return episodeMatch[0]; return n; }; const btnSearchSub = d.querySelector('#pk_sub_search_btn'); btnSearchSub.onclick = async (e) => { e.stopPropagation(); const subPanel = d.querySelector('#pk_sub_panel'); const subTrigger = d.querySelector('#pk_sub_trigger'); if (subPanel && subTrigger) { subPanel.style.setProperty('display', 'none', 'important'); const clearDisplay = () => { subPanel.style.removeProperty('display'); subTrigger.removeEventListener('mouseleave', clearDisplay); }; subTrigger.addEventListener('mouseleave', clearDisplay); } const searchOv = document.createElement('div'); searchOv.className = 'pk-sub-search-modal'; searchOv.style.cssText = ` position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(20,20,20,0.95); border: 1px solid #444; border-radius: 8px; width: 380px; height: 450px; display: flex; flex-direction: column; box-shadow: 0 10px 40px rgba(0,0,0,0.8); z-index: 60; padding: 15px; backdrop-filter: blur(10px); `; box.appendChild(searchOv); searchOv.onclick = (evt) => evt.stopPropagation(); const keyword = cleanFilename(item.name); searchOv.innerHTML = `
`; const resultList = searchOv.querySelector('#pk_sub_search_list'); const input = searchOv.querySelector('#pk_sub_search_input'); const doSearch = (query) => { const cleanQ = query.trim(); if (!cleanQ) return; const plusQ = encodeURIComponent(cleanQ).replace(/%20/g, '+'); const spaceQ = encodeURIComponent(cleanQ); const curLang = L.lang_code; let engines = []; if (curLang === 'zh') { engines = [ { name: 'Assrt', url: `https://assrt.net/sub/?searchword=${plusQ}` }, { name: 'SubHD', url: `https://subhd.tv/search/${spaceQ}` }, { name: 'OpenSubtitles', url: `https://www.opensubtitles.org/zh/search2/sublanguageid-kor/moviename-${plusQ}` }, ]; } else if (curLang === 'tc') { engines = [ { name: 'R3Sub', url: `https://r3sub.com/search.php?s=${plusQ}` }, { name: 'SubHD', url: `https://subhd.tv/search/${spaceQ}` }, { name: 'Assrt', url: `https://assrt.net/sub/?searchword=${plusQ}` } ]; } else if (curLang === 'ko') { engines = [ { name: 'iSubtitles', url: `https://isubtitles.org/search?q=${plusQ}` }, { name: 'OpenSubtitles', url: `https://www.opensubtitles.org/ko/search2/sublanguageid-kor/moviename-${plusQ}` }, { name: 'SubtitleCat', url: `https://www.subtitlecat.com/index.php?search=${plusQ}` } ]; } else if (curLang === 'ja') { engines = [ { name: 'OpenSubtitles', url: `https://www.opensubtitles.org/ja/search2/sublanguageid-jpn/moviename-${plusQ}` }, { name: 'MovieSubtitles', url: `https://www.moviesubtitles.org/search.php?q=${plusQ}` }, { name: 'Anime Tosho', url: `https://animetosho.org/search?q=${plusQ}` } ]; } else if (curLang === 'id') { engines = [ { name: 'OpenSubtitles', url: `https://www.opensubtitles.org/id/search2/sublanguageid-ind/moviename-${plusQ}` }, { name: 'Samehadaku', url: `https://samehadaku.li/?s=${plusQ}` }, { name: 'Anoboy', url: `https://anoboy.be/?s=${plusQ}` } ]; } else if (curLang === 'ms') { engines = [ { name: 'OpenSubtitles', url: `https://www.opensubtitles.org/ms/search2/sublanguageid-may/moviename-${plusQ}` }, { name: 'PencuriMovie', url: `https://pencurimovie.my/?s=${plusQ}` }, { name: 'Oh Flix', url: `https://vvww.ohflix.my.id/?s=${plusQ}` } ]; } else { engines = [ { name: 'OpenSubtitles', url: `https://www.opensubtitles.org/en/search2/sublanguageid-eng/moviename-${plusQ}` }, { name: 'MovieSubtitles', url: `https://www.moviesubtitles.org/search.php?q=${plusQ}` }, { name: 'iSubtitles', url: `https://isubtitles.org/search?q=${plusQ}` } ]; } let html = `
`; engines.forEach(e => { html += ` ${L.btn_go_search.replace('{n}', e.name)} `; }); html += `
${L.tip_manual_sub}
`; resultList.innerHTML = html; }; try { await loadJSZip(); doSearch(input.value); } catch (err) { resultList.innerHTML = `
${L.msg_jszip_fail}
`; } input.oninput = () => doSearch(input.value); input.onkeydown = (ev) => { ev.stopPropagation(); }; searchOv.querySelector('#pk_sub_search_close').onclick = () => searchOv.remove(); }; d.querySelector('#pk_sub_toggle').onchange = (e) => { updateSubStyle(); if (v.textTracks) { Array.from(v.textTracks).forEach(t => { if (t !== subState.track) t.mode = 'disabled'; }); } }; if (v.textTracks) { v.textTracks.addEventListener('addtrack', (e) => { if (subState.hasSub && e.track !== subState.track) { e.track.mode = 'disabled'; } }); } const sizeVal = d.querySelector('#pk_sub_size_val'); d.querySelector('#pk_sub_size_dec').onclick = (e) => { e.stopPropagation(); subState.size = Math.max(12, subState.size - 2); sizeVal.textContent = subState.size; updateSubStyle(); }; d.querySelector('#pk_sub_size_inc').onclick = (e) => { e.stopPropagation(); subState.size = Math.min(80, subState.size + 2); sizeVal.textContent = subState.size; updateSubStyle(); }; d.querySelector('#pk_sub_pos').oninput = (e) => { e.stopPropagation(); subState.pos = parseInt(e.target.value); updateSubStyle(); }; d.querySelector('#pk_sub_bg_opacity').oninput = (e) => { e.stopPropagation(); subState.bgOpacity = parseInt(e.target.value) / 100; updateSubStyle(); }; const timeVal = d.querySelector('#pk_sub_time_val'); const adjustOffset = (delta) => { subState.offset += delta; timeVal.textContent = subState.offset.toFixed(1) + " " + L.unit_sec; if (subState.track && subState.track.cues) { const cues = Array.from(subState.track.cues); cues.forEach(cue => { cue.startTime += delta; cue.endTime += delta; }); } }; d.querySelector('#pk_sub_time_dec').onclick = (e) => { e.stopPropagation(); adjustOffset(-0.5); }; d.querySelector('#pk_sub_time_inc').onclick = (e) => { e.stopPropagation(); adjustOffset(0.5); }; d.querySelector('#pk_sub_panel').onclick = (e) => e.stopPropagation(); d.querySelectorAll('.pk-sub-tab').forEach(t => { t.onclick = () => { d.querySelectorAll('.pk-sub-tab, .pk-sub-pane').forEach(el => el.classList.remove('active')); t.classList.add('active'); d.querySelector('#' + t.dataset.target).classList.add('active'); }; }); const curPMode = gmGet('pk_play_mode', 'stop'); const pRadios = d.querySelectorAll('input[name="pk_pmode"]'); pRadios.forEach(r => { if (r.value === curPMode) r.checked = true; r.onchange = () => gmSet('pk_play_mode', r.value); }); let transformState = { rotate: 0, flipH: 1, flipV: 1, ratio: 'default' }; const applyTransform = () => { if (document.pictureInPictureElement === v) { v.style.transform = 'none'; return; } if (transformState.ratio === 'default') { v.style.objectFit = 'contain'; v.style.width = '100%'; v.style.height = box.classList.contains('plist-active') ? (document.fullscreenElement ? 'calc(100% - 84px)' : '100%') : '100%'; v.style.aspectRatio = 'auto'; v.style.margin = '0'; v.style.inset = 'auto'; } else { v.style.objectFit = 'fill'; v.style.aspectRatio = transformState.ratio; v.style.width = 'auto'; v.style.height = 'auto'; v.style.maxWidth = '100%'; v.style.maxHeight = '100%'; v.style.margin = 'auto'; v.style.inset = '0'; } let autoScale = 1; if (Math.abs(transformState.rotate) % 180 !== 0) { const boxW = box.clientWidth; const boxH = box.clientHeight; const vW = v.offsetWidth || boxW; const vH = v.offsetHeight || boxH; if (vW > 0 && vH > 0) { const scaleW = boxW / vH; const scaleH = boxH / vW; autoScale = Math.min(scaleW, scaleH); } } v.style.transform = `translateZ(0) rotate(${transformState.rotate}deg) scale(${autoScale}) scale(${transformState.flipH}, ${transformState.flipV})`; }; const onResizeTransform = () => requestAnimationFrame(applyTransform); window.addEventListener('resize', onResizeTransform); d.querySelectorAll('#pk_ratio_opts .pk-size-btn').forEach(btn => { btn.onclick = (e) => { d.querySelectorAll('#pk_ratio_opts .pk-size-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); transformState.ratio = btn.dataset.ratio; applyTransform(); }; }); d.querySelector('#pk_btn_rot_l').onclick = () => { transformState.rotate -= 90; applyTransform(); }; d.querySelector('#pk_btn_rot_r').onclick = () => { transformState.rotate += 90; applyTransform(); }; d.querySelector('#pk_btn_flip_h').onclick = () => { transformState.flipH *= -1; applyTransform(); }; d.querySelector('#pk_btn_flip_v').onclick = () => { transformState.flipV *= -1; applyTransform(); }; const opVal = d.querySelector('#pk_op_val'); const edVal = d.querySelector('#pk_ed_val'); let valOp = parseInt(gmGet('pk_skip_intro', 0)) || 0; let valEd = parseInt(gmGet('pk_skip_outro', 0)) || 0; opVal.textContent = valOp + " " + L.unit_sec; edVal.textContent = valEd + " " + L.unit_sec; const updateSkip = (type, delta) => { if (type === 'op') { valOp = Math.max(0, Math.min(3600, valOp + delta)); opVal.textContent = valOp + " " + L.unit_sec; gmSet('pk_skip_intro', valOp); } else { valEd = Math.max(0, Math.min(3600, valEd + delta)); edVal.textContent = valEd + " " + L.unit_sec; gmSet('pk_skip_outro', valEd); } }; d.querySelector('#pk_op_dec').onclick = () => updateSkip('op', -5); d.querySelector('#pk_op_inc').onclick = () => updateSkip('op', 5); d.querySelector('#pk_ed_dec').onclick = () => updateSkip('ed', -5); d.querySelector('#pk_ed_inc').onclick = () => updateSkip('ed', 5); d.querySelector('#pk_op_mark').onclick = (e) => { e.stopPropagation(); const markTime = Math.max(0, Math.floor(v.currentTime)); valOp = markTime; opVal.textContent = valOp + " " + L.unit_sec; gmSet('pk_skip_intro', valOp); }; d.querySelector('#pk_ed_mark').onclick = (e) => { e.stopPropagation(); const dur = typeof getEffectiveVideoDuration === 'function' ? getEffectiveVideoDuration() : Number(v.duration || 0); if (!(Number.isFinite(dur) && dur > 0)) return; const cur = Number(v.currentTime || 0); if (!(Number.isFinite(cur) && cur >= 0)) return; const markTime = Math.max(0, Math.floor(dur - cur)); if (markTime <= 0) return; valEd = markTime; edVal.textContent = valEd + " " + L.unit_sec; gmSet('pk_skip_outro', valEd); }; const getEffectiveVideoDuration = () => { const nativeDur = Number(v.duration || 0); if (Number.isFinite(nativeDur) && nativeDur > 0) return nativeDur; const pId = getPhysicalId(item); const metaDur = Number((item.params && item.params.duration) || S.durationMap.get(pId) || gmGet('pk_duration_' + pId, 0) || 0); return Number.isFinite(metaDur) && metaDur > 0 ? metaDur : 0; }; const handlePlayerEnded = (fromOutroSkip = false) => { const mode = gmGet('pk_play_mode', 'stop'); if (mode === 'single_loop') { v.currentTime = 0; v.play().catch(()=>{}); } else if (mode === 'list_loop') { const nextIdx = (curListIdx + 1) % totalInList; if (totalInList > 1) softSwitch(nextIdx); else { v.currentTime = 0; v.play().catch(()=>{}); } } else if (fromOutroSkip) { const dur = getEffectiveVideoDuration(); try { if (dur > 0 && Number.isFinite(dur)) v.currentTime = Math.max(0, dur - 0.05); } catch (e) {} v.pause(); if (typeof updateState === 'function') updateState(); if (typeof stopSpinner === 'function') stopSpinner(true); } }; let hasTriggeredEnd = false; v.addEventListener('timeupdate', () => { const skipEd = parseInt(gmGet('pk_skip_outro', 0)) || 0; const skipOp = parseInt(gmGet('pk_skip_intro', 0)) || 0; const dur = getEffectiveVideoDuration(); const cur = Number(v.currentTime || 0); if (skipEd > 0 && dur > 0 && !hasTriggeredEnd) { if (skipEd >= dur || (skipOp + skipEd) >= dur || cur < (dur / 2)) return; if (dur - cur <= skipEd) { hasTriggeredEnd = true; handlePlayerEnded(true); } } }); v.addEventListener('play', () => hasTriggeredEnd = false); v.addEventListener('seeking', () => hasTriggeredEnd = false); v.addEventListener('enterpictureinpicture', applyTransform); v.addEventListener('leavepictureinpicture', () => { if (typeof isSwitching !== 'undefined' && !isSwitching) { isPiPDesired = false; } applyTransform(); }); v.onended = () => { handlePlayerEnded(false); }; const makeEditable = (el, type, callback) => { el.ondblclick = (e) => { e.stopPropagation(); const oldText = el.textContent; const oldVal = type === 'offset' ? parseFloat(oldText) : parseInt(oldText); el.innerHTML = ``; const input = el.querySelector('input'); input.focus(); input.select(); const finish = () => { const val = parseFloat(input.value); if (isNaN(val)) { el.textContent = oldText; } else { let finalVal = type === 'offset' ? val : Math.round(val); if (type !== 'offset') finalVal = Math.max(0, finalVal); if (type === 'offset') el.textContent = finalVal.toFixed(1) + " " + L.unit_sec; else el.textContent = finalVal; callback(finalVal); } el.ondblclick = (evt) => { evt.stopPropagation(); makeEditable(el, type, callback).ondblclick(evt); }; }; input.onblur = finish; input.onkeydown = (ev) => { ev.stopPropagation(); if (ev.key === 'Enter') { input.blur(); } }; input.onclick = (ev) => ev.stopPropagation(); }; }; makeEditable(d.querySelector('#pk_sub_size_val'), 'int', (val) => { subState.size = val; updateSubStyle(); }); makeEditable(d.querySelector('#pk_sub_time_val'), 'offset', (val) => { const delta = val - subState.offset; adjustOffset(delta); }); makeEditable(d.querySelector('#pk_op_val'), 'int', (val) => { valOp = Math.min(3600, val); gmSet('pk_skip_intro', valOp); d.querySelector('#pk_op_val').textContent = valOp + " " + L.unit_sec; }); makeEditable(d.querySelector('#pk_ed_val'), 'int', (val) => { valEd = Math.min(3600, val); gmSet('pk_skip_outro', valEd); d.querySelector('#pk_ed_val').textContent = valEd + " " + L.unit_sec; }); updateSubStyle(); const playerKeyHandler = (e) => { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; if (!document.getElementById('pk-player-ov')) return; const k = String(e.key || ''); const ku = k.toUpperCase(); if (k === 'F11' || k === 'F12' || ((e.ctrlKey || e.metaKey) && e.shiftKey && (ku === 'I' || ku === 'J' || ku === 'C'))) return; e.stopPropagation(); e.preventDefault(); resetHideTimer(); switch(e.key) { case ' ': case 'k': togglePlay(); break; case 'ArrowRight': if (e.ctrlKey || e.metaKey) { const nextIdx = (curListIdx + 1) % totalInList; softSwitch(nextIdx); } else { const targetTime = Math.min(v.duration || 0, v.currentTime + 10); v.currentTime = targetTime; if (tCur) tCur.textContent = fmtT(targetTime); if (progFilled && v.duration) progFilled.style.setProperty('width', `${(targetTime / v.duration) * 100}%`, 'important'); showSeekIndicatorAt(targetTime); } break; case 'ArrowLeft': if (e.ctrlKey || e.metaKey) { const prevIdx = (curListIdx - 1 + totalInList) % totalInList; softSwitch(prevIdx); } else { const targetTime = Math.max(0, v.currentTime - 10); v.currentTime = targetTime; if (tCur) tCur.textContent = fmtT(targetTime); if (progFilled && v.duration) progFilled.style.setProperty('width', `${(targetTime / v.duration) * 100}%`, 'important'); showSeekIndicatorAt(targetTime); } break; case 'p': case 'P': const btnPip = d.querySelector('#pk_p_pip'); if (btnPip && btnPip.style.display !== 'none') btnPip.click(); break; case 'e': case 'E': if (pTab) pTab.click(); break; case 'ArrowUp': case 'ArrowDown': applyPlayerVolumeDelta(e.key === 'ArrowUp' ? CONF.playerVolumeStep : -CONF.playerVolumeStep); break; case 'f': case 'F': if (btnSearch && btnSearch.style.display !== 'none') btnSearch.click(); break; case 'Enter': btnFull.click(); break; case 'Escape': e.preventDefault(); e.stopPropagation(); if (typeof e.stopImmediatePropagation === 'function') e.stopImmediatePropagation(); if (document.fullscreenElement) { document.exitFullscreen(); } else if (box && box.classList.contains('pk-web-fullscreen')) { setWebFullscreen(false); } else { destroyPlayer(); } break; } }; document.addEventListener('keydown', playerKeyHandler, true); const initDur = (item.params && item.params.duration) || S.durationMap.get(item.id) || gmGet('pk_duration_' + item.id, 0); if (tDur && initDur > 0) tDur.textContent = fmtT(initDur); loadSource(currentLink, null); setTimeout(() => { if (isPlayerDestroyed) return; const p = v.play(); if (p !== undefined) { p.catch(() => updateState()); } if (startFullscreen) { const box = d.querySelector('#pk_p_box'); const btnFull = d.querySelector('#pk_p_full'); if (box && box.requestFullscreen) { box.requestFullscreen().then(() => { if(btnFull) btnFull.innerHTML = mkSvg(icons.exitFull); }).catch(err => console.warn("Fullscreen auto-resume failed", err)); } } }, 100); autoMatchSubtitle(item); } let isImageOpening = false; async function showImage(startItem) { if (S.trashMode) return; if (document.querySelector('.pk-img-ov')) return; isImageOpening = true; let lastDirection = 1; const item = startItem; const imgList = S.display.filter(i => { if (i.isHeader) return false; if (S.offlineMode && i.phase !== 'PHASE_TYPE_COMPLETE') return false; if (S.uploadMode && i.status !== 'DONE') return false; return i.mime_type && i.mime_type.startsWith('image'); }); if (imgList.length === 0) return; let curIdx = imgList.findIndex(i => i.id === startItem.id); if (curIdx === -1) curIdx = 0; const d = document.createElement('div'); d.className = 'pk-img-ov'; d.tabIndex = 0; const icons = { close: '', full: '', exitFull: '', prev: '', next: '', rotate: '', flipH: '', flipV: '', searchlens: ``, leftArr: ``, rightArr: ``, upArr: `` }; const renderImgListItems = () => { const RANGE = 150; const start = Math.max(0, curIdx - RANGE); const end = Math.min(imgList.length, curIdx + RANGE + 1); return imgList.slice(start, end).map((v, i) => { const absIdx = start + i; const coverSrc = (v.thumbnail_link && v.thumbnail_link !== v.icon_link) ? v.thumbnail_link : ''; const phSrc = v.icon_link || v.thumbnail_link || ''; const phSvg = getIcon(v); return `
${phSrc ? `` : phSvg}
${coverSrc ? `` : ``}
`; }).join(''); }; const listFixStyle = ``; d.innerHTML = listFixStyle + `
${icons.flipV}
${icons.flipH}
${icons.rotate}
${icons.full}
${icons.close}
${curIdx + 1} / ${imgList.length}
${icons.leftArr}
${renderImgListItems()}
${icons.rightArr}
`; document.body.appendChild(d); const box = d.querySelector('#pk_img_box'); const img = d.querySelector('#pk_img_el'); const viewport = d.querySelector('#pk_img_viewport'); const title = d.querySelector('#pk_img_title'); const loader = d.querySelector('#pk_img_load'); const btnFull = d.querySelector('#pk_img_full'); const btnRot = d.querySelector('#pk_img_rot'); const btnMirror = d.querySelector('#pk_img_mirror'); const btnFlipV = d.querySelector('#pk_img_flip_v'); const btnSearch = d.querySelector('#pk_img_search'); let scale = 1, transX = 0, transY = 0, rotation = 0, flipH = 1, flipV = 1, isDrag = false, startX, startY; let isLongImageMode = false; const updateTransform = () => { if (isLongImageMode) return; img.style.transform = `translate(${transX}px, ${transY}px) scale(${scale}) rotate(${rotation}deg) scaleX(${flipH}) scaleY(${flipV})`; }; const resetView = (keepOrientation = false) => { scale = 1; transX = 0; transY = 0; if (!keepOrientation) { rotation = 0; flipH = 1; flipV = 1; } isLongImageMode = false; viewport.classList.remove('pk-long-image-mode'); viewport.classList.remove('pk-fit-mode'); if(viewport.scrollTop) viewport.scrollTop = 0; img.style.transition = 'none'; img.style.width = '100%'; img.style.height = '100%'; img.style.objectFit = 'contain'; img.style.maxWidth = 'none'; img.style.cursor = 'grab'; if (btnRot) btnRot.style.display = 'flex'; if (btnMirror) btnMirror.style.display = 'flex'; if (btnFlipV) btnFlipV.style.display = 'flex'; updateTransform(); requestAnimationFrame(() => { requestAnimationFrame(() => { img.style.transition = ''; }); }); }; let imgLoadId = 0; const loadCurrent = async (scrollMode = 'smooth') => { if (typeof updateImgPlistUI === 'function') { updateImgPlistUI(scrollMode); } imgLoadId++; const myId = imgLoadId; resetView(); img.style.opacity = '0'; const currentItem = imgList[curIdx]; title.textContent = `[${curIdx + 1}/${imgList.length}] ${currentItem.name}`; btnSearch.style.display = 'none'; btnSearch.onclick = (e) => { e.stopPropagation(); const thumbUrl = currentItem.thumbnail_link ? currentItem.thumbnail_link.replace('SIZE_MEDIUM', 'SIZE_LARGE') : (currentItem.icon_link || ''); const thumbImg = new Image(); thumbImg.crossOrigin = 'anonymous'; thumbImg.src = thumbUrl; startImageSearch(thumbImg, currentItem.name, d, thumbUrl); }; loader.style.display = 'block'; const checkLongImage = () => { if (myId !== imgLoadId) return; btnSearch.style.display = 'flex'; const nw = img.naturalWidth; const nh = img.naturalHeight; if (nw > 0 && nh > 0) { if (nh / nw > 2.5) { isLongImageMode = true; viewport.classList.add('pk-long-image-mode'); img.style.transform = 'none'; if (btnRot) btnRot.style.display = 'none'; if (btnMirror) btnMirror.style.display = 'none'; if (btnFlipV) btnFlipV.style.display = 'none'; } } img.style.opacity = '1'; }; img.removeAttribute('crossorigin'); if (currentItem.thumbnail_link) { img.src = currentItem.thumbnail_link; const handleThumb = () => { if (myId !== imgLoadId) return; checkLongImage(); loader.style.display = 'none'; }; img.onerror = () => { if (myId !== imgLoadId) return; img.style.opacity = '0'; }; if (img.complete) handleThumb(); else img.onload = handleThumb; } else { img.removeAttribute('src'); img.style.opacity = '0'; } let targetUrl = currentItem.web_content_link; if (!targetUrl && !currentItem._resolved) { try { const targetApiId = ((S.offlineMode && currentItem.kind === 'drive#task') || (S.uploadMode && currentItem.file_id)) ? currentItem.file_id : currentItem.id; const fullItem = currentItem._isShareItem ? await resolveSharePlayableFile(currentItem) : await apiGet(targetApiId); if (fullItem) { if (fullItem.thumbnail_link) currentItem.thumbnail_link = fullItem.thumbnail_link; if (fullItem.icon_link) currentItem.icon_link = fullItem.icon_link; if (myId === imgLoadId) requestAnimationFrame(() => updateImgPlistUI(false)); } if (myId !== imgLoadId) return; targetUrl = fullItem.web_content_link; currentItem.web_content_link = targetUrl; currentItem._resolved = true; } catch (e) { console.warn("API Error", e); } } if (myId !== imgLoadId) return; const performFinalRender = () => { if (myId !== imgLoadId) return; if (targetUrl && targetUrl !== currentItem.thumbnail_link) { img.crossOrigin = 'anonymous'; img.src = targetUrl; } else { img.crossOrigin = 'anonymous'; } if (img.complete && img.naturalWidth > 0) { checkLongImage(); } else { img.addEventListener('load', checkLongImage, { once: true }); } btnSearch.onclick = (e) => { e.stopPropagation(); const thumbUrl = currentItem.thumbnail_link ? currentItem.thumbnail_link.replace('SIZE_MEDIUM', 'SIZE_LARGE') : (currentItem.icon_link || ''); const thumbImg = new Image(); thumbImg.crossOrigin = 'anonymous'; thumbImg.src = thumbUrl; startImageSearch(thumbImg, currentItem.name, d, thumbUrl); }; const mainDir = lastDirection, sideDir = -lastDirection, DEPTH = 5; const quickLoad = (url) => { if(!url) return; const p = new Image(); p.src = url; }; const getIdx = (offset) => (curIdx + offset + imgList.length) % imgList.length; quickLoad(imgList[getIdx(sideDir)].thumbnail_link); for (let i = 1; i <= DEPTH; i++) { if (myId !== imgLoadId) return; const next = imgList[getIdx(i * mainDir)]; quickLoad(next.thumbnail_link); if (next.web_content_link) { const p = new Image(); p.crossOrigin = 'anonymous'; p.src = next.web_content_link; } } }; if (targetUrl && targetUrl !== currentItem.thumbnail_link) { const tempImg = new Image(); tempImg.crossOrigin = 'anonymous'; tempImg.onload = performFinalRender; tempImg.onerror = performFinalRender; tempImg.src = targetUrl; } else { performFinalRender(); } }; d.addEventListener('wheel', (e) => { if (isLongImageMode) return; e.preventDefault(); const delta = e.deltaY > 0 ? -0.1 : 0.1; scale = Math.max(0.1, Math.min(10, scale + delta)); updateTransform(); }); img.onclick = () => { if (isLongImageMode) { viewport.classList.toggle('pk-fit-mode'); } }; d.addEventListener('wheel', (e) => { if (isLongImageMode) return; e.preventDefault(); const delta = e.deltaY > 0 ? -0.1 : 0.1; let newScale = Math.max(0.1, Math.min(10, scale + delta)); if (img.naturalWidth && viewport) { const vw = viewport.clientWidth; const vh = viewport.clientHeight; const iw = img.naturalWidth; const ih = img.naturalHeight; const baseRatio = Math.min(vw / iw, vh / ih); let curW = iw * baseRatio * newScale; let curH = ih * baseRatio * newScale; if (Math.abs(rotation % 180) === 90) [curW, curH] = [curH, curW]; const limitX = curW > vw ? (curW - vw) / 2 : 0; const limitY = curH > vh ? (curH - vh) / 2 : 0; transX = Math.max(-limitX, Math.min(limitX, transX)); transY = Math.max(-limitY, Math.min(limitY, transY)); } scale = newScale; updateTransform(); }); img.onclick = () => { if (isLongImageMode) { viewport.classList.toggle('pk-fit-mode'); } }; img.addEventListener('mousedown', (e) => { if (e.button !== 0 || (isLongImageMode && !viewport.classList.contains('pk-fit-mode'))) return; e.preventDefault(); isDrag = true; img.style.cursor = 'grabbing'; startX = e.clientX - transX; startY = e.clientY - transY; }); document.addEventListener('mousemove', (e) => { if (!isDrag || isLongImageMode) return; let tx = e.clientX - startX; let ty = e.clientY - startY; if (img.naturalWidth && viewport) { const vw = viewport.clientWidth; const vh = viewport.clientHeight; const iw = img.naturalWidth; const ih = img.naturalHeight; const baseRatio = Math.min(vw / iw, vh / ih); let curW = iw * baseRatio * scale; let curH = ih * baseRatio * scale; if (Math.abs(rotation % 180) === 90) [curW, curH] = [curH, curW]; const limitX = curW > vw ? (curW - vw) / 2 : 0; const limitY = curH > vh ? (curH - vh) / 2 : 0; tx = Math.max(-limitX, Math.min(limitX, tx)); ty = Math.max(-limitY, Math.min(limitY, ty)); } transX = tx; transY = ty; updateTransform(); }); document.addEventListener('mouseup', () => { isDrag = false; if (!isLongImageMode && img) img.style.cursor = 'grab'; }); const resizeHandler = () => { if (isLongImageMode) return; resetView(true); }; window.addEventListener('resize', resizeHandler); d._pkResizeHandler = resizeHandler; d._pkImgList = imgList; d._pkGetCurIdx = () => curIdx; Object.defineProperty(d, '_pkCurIdx', { get: () => curIdx, configurable: true }); d.querySelector('#pk_img_close').onclick = (e) => { e.stopPropagation(); S.closeImageOverlay(); }; btnFull.onclick = (e) => { e.stopPropagation(); box.classList.toggle('full'); const isNowFull = box.classList.contains('full'); btnFull.innerHTML = isNowFull ? icons.exitFull : icons.full; btnFull.setAttribute('data-pk-tip', isNowFull ? L.tip_minimize : L.tip_maximize); if (isLongImageMode && viewport) { viewport.scrollTop = 0; } else { setTimeout(() => resetView(true), 210); } }; btnRot.onclick = (e) => { e.stopPropagation(); if (isLongImageMode) return; rotation += 90; updateTransform(); }; if (btnMirror) { btnMirror.onclick = (e) => { e.stopPropagation(); if (isLongImageMode) return; flipH *= -1; img.style.transition = 'none'; updateTransform(); requestAnimationFrame(() => { requestAnimationFrame(() => { img.style.transition = ''; }); }); }; } if (btnFlipV) { btnFlipV.onclick = (e) => { e.stopPropagation(); if (isLongImageMode) return; flipV *= -1; img.style.transition = 'none'; updateTransform(); requestAnimationFrame(() => { requestAnimationFrame(() => { img.style.transition = ''; }); }); }; } const plist = d.querySelector('#pk_img_plist'); const pTab = d.querySelector('#pk_img_plist_tab'); const pScroll = d.querySelector('#pk_img_plist_scroll'); let pTip = document.getElementById('pk_p_plist_tip_global'); if (!pTip) { pTip = document.createElement('div'); pTip.id = 'pk_p_plist_tip_global'; pTip.className = 'pk-p-plist-tip'; document.body.appendChild(pTip); } const pTxt = d.querySelector('#pk_img_idx_txt'); const updateImgPlistUI = (scrollType = 'smooth') => { if (pTxt) pTxt.textContent = `${curIdx + 1} / ${imgList.length}`; const RANGE = 150; const desiredStart = Math.max(0, curIdx - RANGE); const desiredEnd = Math.min(imgList.length, curIdx + RANGE + 1); const prevStart = parseInt(pScroll.dataset.pkStart || '-1', 10); const prevEnd = parseInt(pScroll.dataset.pkEnd || '-1', 10); const needRebuild = !Number.isFinite(prevStart) || !Number.isFinite(prevEnd) || curIdx < prevStart || curIdx >= prevEnd || pScroll.childElementCount === 0 || pScroll.dataset.pkTotal !== String(imgList.length); if (needRebuild) { pScroll.innerHTML = renderImgListItems(); pScroll.dataset.pkStart = String(desiredStart); pScroll.dataset.pkEnd = String(desiredEnd); pScroll.dataset.pkTotal = String(imgList.length); pScroll.querySelectorAll('.pk-p-plist-item').forEach(el => { el.onclick = (e) => { e.stopPropagation(); const idx = parseInt(e.currentTarget.dataset.idx, 10); if (idx === curIdx) return; curIdx = idx; loadCurrent('instant'); }; el.onmouseenter = (e) => { if (plist.classList.contains('open')) { const name = e.currentTarget.dataset.name; const size = e.currentTarget.dataset.size; pTip.innerHTML = `${name}
${size}`; pTip.style.display = 'block'; } }; el.onmousemove = (e) => { if (pTip.style.display === 'block') { const tW = pTip.offsetWidth || 150; pTip.style.left = (e.clientX - (tW / 2)) + 'px'; pTip.style.top = (e.clientY - 60) + 'px'; } }; el.onmouseleave = () => { pTip.style.display = 'none'; }; }); } const itemsInDom = pScroll.querySelectorAll('.pk-p-plist-item'); itemsInDom.forEach((el) => { const absIdx = parseInt(el.dataset.idx, 10); const isActive = absIdx === curIdx; el.classList.toggle('active', isActive); if (scrollType !== false && isActive && plist.classList.contains('open')) { if (scrollType === 'instant') { pScroll.style.scrollBehavior = 'auto'; } else { pScroll.style.scrollBehavior = 'smooth'; } el.scrollIntoView({ behavior: scrollType === 'instant' ? 'auto' : 'smooth', block: 'nearest', inline: 'center' }); if (scrollType === 'instant') { setTimeout(() => { pScroll.style.scrollBehavior = 'smooth'; }, 50); } } }); const sl = Math.ceil(pScroll.scrollLeft); const sw = pScroll.scrollWidth; const cw = pScroll.clientWidth; if (sw <= cw) { d.querySelector('#pk_img_plist_L').style.setProperty('display', 'none', 'important'); d.querySelector('#pk_img_plist_R').style.setProperty('display', 'none', 'important'); } else { if (sl <= 5) d.querySelector('#pk_img_plist_L').style.setProperty('display', 'none', 'important'); else d.querySelector('#pk_img_plist_L').style.setProperty('display', 'flex', 'important'); if (sl + cw >= sw - 5) d.querySelector('#pk_img_plist_R').style.setProperty('display', 'none', 'important'); else d.querySelector('#pk_img_plist_R').style.setProperty('display', 'flex', 'important'); } const btnPrev = d.querySelector('#pk_img_prev'); const btnNext = d.querySelector('#pk_img_next'); if (btnPrev) btnPrev.style.setProperty('display', curIdx === 0 ? 'none' : 'flex', 'important'); if (btnNext) btnNext.style.setProperty('display', curIdx === imgList.length - 1 ? 'none' : 'flex', 'important'); }; pTab.onclick = (e) => { e.stopPropagation(); const willOpen = !plist.classList.contains('open'); plist.classList.toggle('open'); if (typeof box !== 'undefined') box.classList.toggle('plist-active'); pTab.setAttribute('data-pk-tip', plist.classList.contains('open') ? L.tip_plist_close : L.tip_plist_open); if (!isLongImageMode) resetView(); if (willOpen) { updateImgPlistUI(false); pScroll.style.scrollBehavior = 'auto'; const activeItem = pScroll.querySelector('.active'); if (activeItem) { const targetLeft = activeItem.offsetLeft - (pScroll.clientWidth / 2) + (activeItem.clientWidth / 2); pScroll.scrollLeft = targetLeft; } setTimeout(() => { pScroll.style.scrollBehavior = 'smooth'; }, 50); setTimeout(() => updateImgPlistUI(false), 100); } }; const handleListWheel = (e) => { e.stopPropagation(); e.preventDefault(); pScroll.scrollBy({ left: e.deltaY > 0 ? 300 : -300, behavior: 'smooth' }); }; pScroll.removeEventListener('wheel', handleListWheel); pScroll.addEventListener('wheel', handleListWheel, { passive: false }); pScroll.addEventListener('scroll', () => { requestAnimationFrame(() => updateImgPlistUI(false)); }, { passive: true }); const btnListL = d.querySelector('#pk_img_plist_L'); const btnListR = d.querySelector('#pk_img_plist_R'); if (btnListL) { btnListL.onclick = (e) => { e.stopPropagation(); pScroll.scrollBy({ left: -400, behavior: 'smooth' }); setTimeout(() => updateImgPlistUI(false), 300); }; } if (btnListR) { btnListR.onclick = (e) => { e.stopPropagation(); pScroll.scrollBy({ left: 400, behavior: 'smooth' }); setTimeout(() => updateImgPlistUI(false), 300); }; } setTimeout(() => updateImgPlistUI(false), 100); const goPrev = () => { if (curIdx > 0) { lastDirection = -1; curIdx--; loadCurrent(); } }; const goNext = () => { if (curIdx < imgList.length - 1) { lastDirection = 1; curIdx++; loadCurrent(); } }; d.querySelector('#pk_img_prev').onclick = (e) => { e.stopPropagation(); goPrev(); }; d.querySelector('#pk_img_next').onclick = (e) => { e.stopPropagation(); goNext(); }; d.focus(); d.addEventListener('keydown', (e) => { if (e.key === 'Escape') { S.closeImageOverlay(); } else if (e.key === 'ArrowLeft') goPrev(); else if (e.key === 'ArrowRight') goNext(); else if (e.key === 'f' || e.key === 'F') { if (btnSearch && btnSearch.style.display !== 'none') btnSearch.click(); } else if (e.key === 'e' || e.key === 'E') { if (pTab) pTab.click(); } else if (e.key === 'm' || e.key === 'M') { e.preventDefault(); if (btnFull) btnFull.click(); } else if (e.key === 'r' || e.key === 'R') { e.preventDefault(); if (!isLongImageMode && btnRot) btnRot.click(); } else if (e.key === 'h' || e.key === 'H') { e.preventDefault(); if (!isLongImageMode && btnMirror) btnMirror.click(); } else if (e.key === 'v' || e.key === 'V') { e.preventDefault(); if (!isLongImageMode && btnFlipV) btnFlipV.click(); } }); try { await loadCurrent(); } catch (e) { console.error(e); } finally { isImageOpening = false; } } let isImageSearchRunning = false; async function startImageSearch(mediaElement, fileName, containerElement, originalLink) { if (isImageSearchRunning) return; isImageSearchRunning = true; try { window.focus(); if (containerElement) containerElement.focus(); } catch (e) {} const L = getStrings(); const isVideo = mediaElement.tagName === 'VIDEO'; const MAX_SIDE = 1000; let progressTask = null; if (typeof FloatBarManager !== 'undefined') { progressTask = FloatBarManager.create(L.str_processing); } if (isVideo && !mediaElement.paused) mediaElement.pause(); try { let finalBlob = null; const cvs = document.createElement('canvas'); const ctx = cvs.getContext('2d'); const drawScaled = (source, srcW, srcH) => { let w = srcW, h = srcH; if (w === 0 || h === 0) throw new Error("Media dimensions not ready"); if (w > MAX_SIDE || h > MAX_SIDE) { const ratio = Math.min(MAX_SIDE / w, MAX_SIDE / h); w = Math.floor(w * ratio); h = Math.floor(h * ratio); } cvs.width = w; cvs.height = h; ctx.drawImage(source, 0, 0, w, h); }; let fetchUrl = originalLink; const isLocalData = fetchUrl && (fetchUrl.startsWith('blob:') || fetchUrl.startsWith('data:')); const BLOB_TYPE = 'image/jpeg'; const BLOB_QUALITY = 0.85; if (isVideo) { const sourceW = mediaElement.videoWidth; const sourceH = mediaElement.videoHeight; drawScaled(mediaElement, sourceW, sourceH); try { finalBlob = await new Promise((resolve, reject) => { cvs.toBlob(b => b ? resolve(b) : reject(new Error("Empty")), BLOB_TYPE, BLOB_QUALITY); }); } catch (err) { throw new Error("Tainted: Video CORS Blocked"); } } else { let canvasSuccess = false; try { const sourceW = mediaElement.naturalWidth || mediaElement.width; const sourceH = mediaElement.naturalHeight || mediaElement.height; if (sourceW > 0 && sourceH > 0) { drawScaled(mediaElement, sourceW, sourceH); finalBlob = await new Promise((resolve, reject) => { try { cvs.toBlob(b => b ? resolve(b) : reject(new Error("Tainted")), BLOB_TYPE, BLOB_QUALITY); } catch (e) { reject(e); } }); canvasSuccess = !!finalBlob; } } catch (canvasErr) {} if (!canvasSuccess && fetchUrl && !isLocalData) { try { const res = await fetch(fetchUrl, { mode: 'cors', credentials: 'omit' }); if (!res.ok && res.status !== 206) throw new Error(`Fetch HTTP ${res.status}`); finalBlob = await res.blob(); } catch (fetchErr) { let fetchRetry = 0; let fetchSuccess = false; while (fetchRetry < 3 && !fetchSuccess) { try { finalBlob = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: fetchUrl, responseType: "blob", timeout: 30000, headers: { "Referer": "https://mypikpak.com/", "User-Agent": navigator.userAgent }, onload: (res) => { if (res.status === 200 || res.status === 206) resolve(res.response); else reject(new Error(`HTTP ${res.status}`)); }, onerror: () => reject(new Error("Network Error")), ontimeout: () => reject(new Error("Timeout")) }); }); fetchSuccess = true; } catch (err) { fetchRetry++; if (fetchRetry < 3) { await new Promise(r => setTimeout(r, 1500)); } } } } } if (!finalBlob) { throw new Error(L.err_network_break); } if (finalBlob.size > 8 * 1024 * 1024) { try { const bmp = await createImageBitmap(finalBlob); drawScaled(bmp, bmp.width, bmp.height); bmp.close(); finalBlob = await new Promise((resolve, reject) => { cvs.toBlob(b => b ? resolve(b) : reject(new Error("Compression Failed")), BLOB_TYPE, BLOB_QUALITY); }); } catch (e) {} } } if (!finalBlob) throw new Error(L.err_capture); const uploadAndGetUrl = async () => { if (typeof GM_xmlhttpRequest === 'undefined') throw new Error("Missing GM_xmlhttpRequest"); const FAST_TIMEOUT = 4000; const uploadTask = (url, formData, parseType, stageText) => { return new Promise((resolve, reject) => { if (progressTask) progressTask.update(stageText); GM_xmlhttpRequest({ method: "POST", url: url, data: formData, timeout: FAST_TIMEOUT, responseType: parseType === 'json' ? 'json' : 'text', onload: (res) => { if (res.status === 200) resolve(res); else reject(new Error(`HTTP ${res.status}`)); }, onerror: () => reject(new Error("Network Error")), ontimeout: () => reject(new Error("Timeout")) }); }); }; const tryNode1 = async () => { const fd = new FormData(); fd.append('files[]', finalBlob, `pk_1.jpg`); const res = await uploadTask( "https://uguu.se/upload.php", fd, 'json', L.str_upload_1 ); if (res.response && res.response.success && res.response.files?.[0]?.url) { return res.response.files[0].url; } throw new Error("Uguu API Error"); }; const tryNode2 = async () => { const fd = new FormData(); fd.append('reqtype', 'fileupload'); fd.append('time', '1h'); fd.append('fileToUpload', finalBlob, `pk_2.jpg`); const res = await uploadTask( "https://litterbox.catbox.moe/resources/internals/api.php", fd, 'text', L.str_upload_2 ); return res.responseText.trim(); }; const tryNode3 = async () => { const fd = new FormData(); fd.append('reqtype', 'fileupload'); fd.append('userhash', ''); fd.append('fileToUpload', finalBlob, `pk_3.jpg`); const res = await uploadTask( "https://catbox.moe/user/api.php", fd, 'text', L.str_upload_3 ); return res.responseText.trim(); }; try { return await tryNode1(); } catch (e1) { try { return await tryNode2(); } catch (e2) { try { return await tryNode3(); } catch (e3) { console.error("All upload nodes failed:", e1, e2, e3); throw new Error("All Upload Hosts Failed"); } } } }; try { const imgUrl = await uploadAndGetUrl(); if (!imgUrl || !imgUrl.startsWith('http')) { throw new Error("Invalid URL returned."); } const currentEngine = gmGet('pk_search_engine', 'google'); const encUrl = encodeURIComponent(imgUrl); let jumpUrl = ''; let engineName = ''; switch (currentEngine) { case 'yandex': jumpUrl = `https://yandex.com/images/search?rpt=imageview&url=${encUrl}`; engineName = 'Yandex'; break; case 'saucenao': jumpUrl = `https://saucenao.com/search.php?db=999&url=${encUrl}`; engineName = 'SauceNAO'; break; case 'tracemoe': { const cleanUrl = imgUrl.replace(/^https?:\/\//, ''); const proxyUrl = `https://wsrv.nl/?url=${cleanUrl}&output=jpg`; jumpUrl = `https://trace.moe/?url=${encodeURIComponent(proxyUrl)}`; engineName = 'trace.moe'; break; } case 'google': default: jumpUrl = `https://lens.google.com/uploadbyurl?url=${encUrl}`; engineName = 'Google Lens'; break; } if (progressTask) { progressTask.update(L.str_redirecting.replace('Google Lens', engineName)); const el = document.querySelector('.pk-float-bar-item .pk-spin-lg'); if (el) el.style.borderColor = '#4CAF50'; } await sleep(600); window.open(jumpUrl, '_blank'); } catch (err) { if (progressTask) progressTask.update(L.str_upload_fail_copy); let fallbackBlob = null; try { const bmp = await createImageBitmap(finalBlob); const tmpCvs = document.createElement('canvas'); tmpCvs.width = bmp.width; tmpCvs.height = bmp.height; tmpCvs.getContext('2d').drawImage(bmp, 0, 0); bmp.close(); fallbackBlob = await new Promise(r => tmpCvs.toBlob(r, 'image/png')); } catch (e) { fallbackBlob = await new Promise(r => cvs.toBlob(r, 'image/png')); } const MAX_CLIPBOARD_SIZE = 19.5 * 1024 * 1024; if (fallbackBlob.size > MAX_CLIPBOARD_SIZE) { try { const ratio = Math.sqrt(MAX_CLIPBOARD_SIZE / fallbackBlob.size); const bmp = await createImageBitmap(fallbackBlob); const newW = Math.floor(bmp.width * ratio); const newH = Math.floor(bmp.height * ratio); const tmpCvs = document.createElement('canvas'); tmpCvs.width = newW; tmpCvs.height = newH; tmpCvs.getContext('2d').drawImage(bmp, 0, 0, newW, newH); bmp.close(); fallbackBlob = await new Promise(r => tmpCvs.toBlob(r, 'image/png')); } catch (e) {} } try { const item = new ClipboardItem({ 'image/png': fallbackBlob }); await navigator.clipboard.write([item]); } catch (clipErr) { console.error("Clipboard write failed:", clipErr); if (progressTask) progressTask.update(L.err_clipboard_failed); await sleep(1000); } const ctrlVPhrase = (navigator.platform.toUpperCase().indexOf('MAC') >= 0) ? 'Cmd+V' : 'Ctrl+V'; const hintText = L.msg_manual_paste.replace('{cmd}', `${ctrlVPhrase}`); if (progressTask) progressTask.destroy(); const fallbackOv = document.createElement('div'); fallbackOv.className = 'pk-search-running-mask'; fallbackOv.style.cssText = 'position:absolute; inset:0; z-index:2147483647; background:rgba(0,0,0,0.85); display:flex; align-items:center; justify-content:center; flex-direction:column; gap:20px; border-radius:inherit;'; fallbackOv.innerHTML = `
⚠️
${L.msg_copy_success}
${hintText}
`; const fsEl = document.fullscreenElement || document.webkitFullscreenElement; if (fsEl) { fsEl.appendChild(fallbackOv); } else if (containerElement) { containerElement.appendChild(fallbackOv); } else { document.body.appendChild(fallbackOv); } await sleep(2500); fallbackOv.remove(); const currentEngine = gmGet('pk_search_engine', 'google'); let manualUrl = ''; switch (currentEngine) { case 'yandex': manualUrl = 'https://yandex.com/images/'; break; case 'saucenao': manualUrl = 'https://saucenao.com/'; break; case 'tracemoe': manualUrl = 'https://trace.moe/'; break; case 'google': default: manualUrl = 'https://lens.google.com/upload'; break; } window.open(manualUrl, '_blank'); } } catch (e) { console.error("Search Error:", e); if (progressTask) progressTask.destroy(); const errorMsg = e.message.includes('Tainted') ? L.err_cors_blocked : e.message; if (typeof showToast !== 'undefined') { showToast(`${errorMsg}`, 'error'); } } finally { if (progressTask) progressTask.destroy(); isImageSearchRunning = false; } } const FILTER_EXTS = { video: ['mp4','mkv','avi','mov','wmv','flv','webm','ts','m4v','3gp','mpg','mpeg','rm','rmvb','asf','vob','dat','divx','f4v','m2ts','mts','tp','trp','ogv','mpe','m2v','m3u8'], audio: ['mp3','wav','flac','aac','ogg','wma','ape','m4a','amr','opus','m4b','alac','aiff','mid','midi','ra','dts','ac3','dsf','dff'], image: ['jpg','jpeg','png','gif','bmp','webp','svg','tif','tiff','ico','heic','heif','raw','cr2','nef','arw','dng','orf','avif','psd','ai','eps','jfif','jpe'], document: ['txt','html','pdf','pptx','chm','docx','xlsx','htm','doc','dwg','mdb','ppt','xls','rtf','odt','ods','odp','epub','mobi','azw3','djvu','cbz','cbr','md','log','csv','xml','json'], software: ['apk','exe','ipa','dmg','rpm','deb','msi','pkg','xapk','apks','aab','jar','bin','sh','bat','cmd'], archive: ['zip','rar','7z','tar','gz','iso','cab','bz2','xz','tgz','wim','esd','img','zst','lzh'], torrent: ['torrent'] }; const FILTER_NAMES = { video: L.cat_video, audio: L.cat_audio, image: L.cat_image, document: L.cat_document, software: L.cat_software, archive: L.cat_archive, torrent: L.cat_torrent, other: L.cat_other }; const fIcons = { all: ``, video: ``, audio: ``, image: ``, document: ``, software: ``, archive: ``, torrent: ``, other: `` }; const isShareInsightFilterContext = () => !!(S.shareParseMode && S.shareParseInsightMode); const getCurrentFilterState = () => { if (isShareInsightFilterContext()) { if (!S.shareParseInsightFilterState) S.shareParseInsightFilterState = { active: false, cat: 'all', ext: 'all' }; return S.shareParseInsightFilterState; } if (!S.filterState) S.filterState = { active: false, cat: 'all', ext: 'all' }; return S.filterState; }; const commitFilterChange = () => { S.sel.clear(); refresh(); }; const renderActiveFilterUI = () => { if (!UI.filterBar) return; const state = getCurrentFilterState(); const cat = state.cat; if (cat === 'all') return; UI.filterCatLabel.textContent = FILTER_NAMES[cat] || (L.cat_other); if (cat === 'other') { UI.filterExtsWrap.style.display = 'none'; } else { UI.filterExtsWrap.style.display = 'flex'; const exts = FILTER_EXTS[cat] ||[]; const mainExts = exts.slice(0, 3); const moreExts = exts.slice(3); let html = `${L.cat_all}`; let displayExts = [...mainExts]; if (state.ext !== 'all' && moreExts.includes(state.ext)) { displayExts[2] = state.ext; } displayExts.forEach(e => { html += `${e}`; }); UI.filterExtsMain.innerHTML = html; if (moreExts.length > 0) { UI.filterExtsMoreBtn.style.display = 'flex'; } else { UI.filterExtsMoreBtn.style.display = 'none'; } UI.filterExtsMain.querySelectorAll('.pk-f-ext').forEach(span => { span.onclick = (e) => { e.stopPropagation(); getCurrentFilterState().ext = span.dataset.ext; renderActiveFilterUI(); commitFilterChange(); }; }); } }; const showFilterCatPopup = (triggerEl, e) => { e.stopPropagation(); const existing = document.querySelector('#pk-filter-cat-pop'); if (existing) { existing.remove(); return; } const pop = document.createElement('div'); pop.id = 'pk-filter-cat-pop'; pop.style.cssText = ` position: absolute; background: var(--pk-bg); border: 1px solid var(--pk-bd); border-radius: 8px; padding: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); z-index: 2147483647; width: 420px; display: flex; flex-direction: column; `; if (document.querySelector('.pk-ov')?.classList.contains('pk-dark')) pop.classList.add('pk-dark'); const state = getCurrentFilterState(); pop.innerHTML = `
${L.title_file_filter}
${fIcons.all} ${L.cat_all}
${fIcons.video} ${L.cat_video}
${fIcons.audio} ${L.cat_audio}
${fIcons.image} ${L.cat_image}
${fIcons.document} ${L.cat_document}
${fIcons.software} ${L.cat_software}
${fIcons.archive} ${L.cat_archive}
${fIcons.torrent} ${L.cat_torrent}
${fIcons.other} ${L.cat_other}
`; document.body.appendChild(pop); const updatePosition = () => { if (!pop.isConnected) return; const rect = getLogicalRect(triggerEl); let popLeft = rect.left; if (popLeft + 420 > window.innerWidth) popLeft = window.innerWidth - 430; pop.style.top = (rect.bottom + 5) + 'px'; pop.style.left = popLeft + 'px'; }; updatePosition(); window.addEventListener('resize', updatePosition); const cleanup = () => { window.removeEventListener('resize', updatePosition); document.removeEventListener('mousedown', closer); pop.remove(); }; pop.querySelectorAll('.pk-fc-btn').forEach(btn => { btn.onclick = (ev) => { ev.stopPropagation(); const cat = btn.dataset.cat; const st = getCurrentFilterState(); st.cat = cat; st.ext = 'all'; st.active = (cat !== 'all'); cleanup(); if (st.active) { renderActiveFilterUI(); } commitFilterChange(); }; }); const closer = (ev) => { if (!pop.contains(ev.target) && !triggerEl.contains(ev.target)) { cleanup(); } }; setTimeout(() => document.addEventListener('mousedown', closer), 10); }; if (UI.filterBtn) UI.filterBtn.onclick = (e) => showFilterCatPopup(UI.filterBtn, e); if (UI.filterCatLabel) UI.filterCatLabel.onclick = (e) => showFilterCatPopup(UI.filterCatLabel, e); if (UI.filterExtsMoreBtn) { UI.filterExtsMoreBtn.onclick = (e) => { e.stopPropagation(); const existing = document.querySelector('#pk-filter-more-pop'); if (existing) { existing.remove(); return; } const pop = document.createElement('div'); pop.id = 'pk-filter-more-pop'; pop.style.cssText = ` position: absolute; background: var(--pk-bg); border: 1px solid var(--pk-bd); border-radius: 8px; padding: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); z-index: 2147483647; max-width: 340px; display: flex; flex-wrap: wrap; gap: 8px; `; if (document.querySelector('.pk-ov')?.classList.contains('pk-dark')) pop.classList.add('pk-dark'); const state = getCurrentFilterState(); const exts = FILTER_EXTS[state.cat] ||[]; const mainExts = exts.slice(0, 3); const moreExts = exts.slice(3); let displayExts = [...mainExts]; if (state.ext !== 'all' && moreExts.includes(state.ext)) { displayExts[2] = state.ext; } const dropdownExts = exts.filter(ex => !displayExts.includes(ex)); pop.innerHTML = dropdownExts.map(ex => `${ex}`).join(''); document.body.appendChild(pop); const updatePosition = () => { if (!pop.isConnected) return; const rect = getLogicalRect(UI.filterExtsWrap); let popLeft = rect.left; if (popLeft + 340 > window.innerWidth) popLeft = window.innerWidth - 350; pop.style.top = (rect.bottom + 5) + 'px'; pop.style.left = popLeft + 'px'; }; updatePosition(); window.addEventListener('resize', updatePosition); const cleanup = () => { window.removeEventListener('resize', updatePosition); document.removeEventListener('mousedown', closer); pop.remove(); }; pop.querySelectorAll('.pk-f-ext').forEach(span => { span.onclick = (ev) => { ev.stopPropagation(); getCurrentFilterState().ext = span.dataset.ext; cleanup(); renderActiveFilterUI(); commitFilterChange(); }; }); const closer = (ev) => { if (!pop.contains(ev.target) && !UI.filterExtsMoreBtn.contains(ev.target)) { cleanup(); } }; setTimeout(() => document.addEventListener('mousedown', closer), 10); }; } if (UI.filterExitBtn) { UI.filterExitBtn.onclick = () => { const st = getCurrentFilterState(); st.active = false; st.cat = 'all'; st.ext = 'all'; UI.filterBtn.style.display = 'flex'; UI.filterActiveUI.style.display = 'none'; commitFilterChange(); }; } const getHistory = () => { try { return JSON.parse(gmGet('pk_search_history', '[]')); } catch { return []; } }; const saveHistory = (txt) => { if (!txt) return; let list = getHistory(); list = list.filter(x => x !== txt); list.unshift(txt); if (list.length > 3) list = list.slice(0, 3); gmSet('pk_search_history', JSON.stringify(list)); }; const renderHistory = () => { const list = getHistory(); if (list.length === 0) { UI.searchHist.style.display = 'none'; return; } let html = `
${L.title_search_hist}${L.btn_clear_hist}
`; list.forEach(txt => { html += `
${esc(txt)}
`; }); UI.searchHist.innerHTML = html; UI.searchHist.style.display = 'flex'; UI.searchHist.querySelector('#pk-hist-del').onclick = (e) => { e.stopPropagation(); gmSet('pk_search_history', '[]'); UI.searchHist.style.display = 'none'; }; UI.searchHist.querySelectorAll('.pk-select-item').forEach(el => { el.onclick = (e) => { const val = el.querySelector('span').textContent; UI.searchInput.value = val; performSearch(val); UI.searchHist.style.display = 'none'; }; }); }; const performSearch = (val) => { const txt = val.trim(); if (!txt) { if (S.search && UI.searchClear) UI.searchClear.click(); return; } const isGlobal = UI.chkGlobal && UI.chkGlobal.checked && !S.uploadMode; if (txt) { saveHistory(txt); if (isGlobal && !S.preSearchPath) { S.preSearchPath = [...S.path]; } if (isGlobal) { S.sort = 'modified_time'; S.dir = 1; S.path = [ { id: '', name: L.btn_nav_home }, { id: 'virtual_search_root', name: L.str_search_results } ]; renderCrumb(); } } S.search = txt; syncLocalUploadVisibility(); if (S.dupMode) { if (S.pinnedDupPath) { S.pinnedDupPath = null; S.clearSelection(); UI.selDupFolder.value = ""; const invertChk = document.getElementById('pk-dup-invert'); if(invertChk) { invertChk.checked = false; invertChk.disabled = true; invertChk.parentNode.style.opacity = '0.5'; } } renderDupView(); updateStat(); } else if (isGlobal) { if (globalNeedsSync) { setLoad(true); updateLoadTxt(L.str_analyzing); setTimeout(async () => { if (!S.scanning) { S.scanning = true; UI.stopBtn.onclick = () => { S.scanning = false; updateLoadTxt(L.str_stopping); if (UI.chkGlobal) UI.chkGlobal.checked = false; }; try { await runFlattenScanOperation(true, [], true); globalNeedsSync = false; } catch (e) { console.error("[GlobalSearch] Sync Error:", e); } finally { S.scanning = false; } } load(false, true).finally(() => setLoad(false)); }, 50); } else { load(false, false); } } else { refresh(); } UI.searchClear.style.display = txt ? 'flex' : 'none'; if (UI.searchHist && typeof UI.searchHist._pkClose === 'function') UI.searchHist._pkClose(); else if (UI.searchHist) UI.searchHist.style.display = 'none'; UI.searchInput.blur(); }; if (UI.searchInput) { UI.searchInput.oninput = (e) => { const val = e.target.value.trim(); UI.searchClear.style.display = val ? 'flex' : 'none'; if (!val && S.search && UI.searchClear) { UI.searchClear.click(); } }; const searchHist = UI.searchHist; const searchWrap = UI.searchInput.closest('.pk-search') || UI.searchInput.parentNode; let searchHistRaf = 0; let searchHistOpen = false; if (searchHist) { searchHist.style.display = 'none'; searchHist.style.visibility = ''; searchHist.style.pointerEvents = ''; } const isSearchHistOpen = () => { return !!searchHist && searchHistOpen && getHistory()?.length > 0; }; const requestPlaceSearchHist = () => { if (!isSearchHistOpen()) return; if (searchHistRaf) cancelAnimationFrame(searchHistRaf); searchHistRaf = requestAnimationFrame(() => { searchHistRaf = requestAnimationFrame(() => { searchHistRaf = 0; if (isSearchHistOpen()) placeSearchHist(); }); }); }; const placeSearchHist = () => { if (!searchHist || !searchWrap) return; if (!searchHist._pkOriginParent) searchHist._pkOriginParent = searchWrap; if (searchHist.parentNode !== document.body) { document.body.appendChild(searchHist); } searchHist.classList.toggle('pk-dark', el.classList.contains('pk-dark')); const wrapRect = typeof getLogicalRect === 'function' ? getLogicalRect(searchWrap) : searchWrap.getBoundingClientRect(); const pad = 8; searchHist.dataset.pkPortal = '1'; searchHist.style.position = 'fixed'; searchHist.style.left = '0'; searchHist.style.top = '0'; searchHist.style.right = 'auto'; searchHist.style.bottom = 'auto'; searchHist.style.marginTop = '0'; searchHist.style.zIndex = '10080'; searchHist.style.transformOrigin = 'top left'; searchHist.style.width = Math.max(Math.round(wrapRect.width), 220) + 'px'; searchHist.style.display = 'flex'; searchHist.style.visibility = 'hidden'; searchHist.style.pointerEvents = 'none'; const histRect = searchHist.getBoundingClientRect(); const popW = histRect.width; const popH = histRect.height; const winW = window.innerWidth; const winH = window.innerHeight; let left = wrapRect.left; let top = wrapRect.bottom + 6; if (left + popW > winW - pad) left = winW - pad - popW; if (left < pad) left = pad; if (top + popH > winH - pad) { top = wrapRect.top - popH - 6; } if (top < pad) top = pad; searchHist.style.left = Math.round(left) + 'px'; searchHist.style.top = Math.round(top) + 'px'; searchHist.style.visibility = ''; searchHist.style.pointerEvents = ''; }; const closeSearchHist = () => { if (!searchHist) return; searchHistOpen = false; if (searchHistRaf) cancelAnimationFrame(searchHistRaf); searchHistRaf = 0; searchHist.style.display = 'none'; searchHist.style.visibility = ''; searchHist.style.pointerEvents = ''; }; if (searchHist) searchHist._pkClose = closeSearchHist; UI.searchInput.addEventListener('keydown', (e) => { if (e.key === 'Escape' && isSearchHistOpen()) { e.preventDefault(); e.stopPropagation(); closeSearchHist(); } }); UI.searchInput.onfocus = () => { renderHistory(); if (getHistory()?.length > 0) { searchHistOpen = true; searchHist.style.display = 'flex'; requestPlaceSearchHist(); } else { closeSearchHist(); } }; document.addEventListener('mousedown', (e) => { if (!UI || !UI.searchInput || !searchHist) return; if (!searchHist.contains(e.target) && !searchWrap.contains(e.target)) { closeSearchHist(); } }, true); if (!window.__pkSearchHistPortalBound) { window.addEventListener('resize', () => { if (isSearchHistOpen()) requestPlaceSearchHist(); }, { passive: true }); document.addEventListener('scroll', () => { if (isSearchHistOpen()) requestPlaceSearchHist(); }, true); if (window.visualViewport) { window.visualViewport.addEventListener('resize', () => { if (isSearchHistOpen()) requestPlaceSearchHist(); }, { passive: true }); window.visualViewport.addEventListener('scroll', () => { if (isSearchHistOpen()) requestPlaceSearchHist(); }, { passive: true }); } window.__pkSearchHistPortalBound = true; } if (window.ResizeObserver && !searchWrap.__pkSearchHistRO) { searchWrap.__pkSearchHistRO = new ResizeObserver(() => { if (!searchHistOpen) return; if (isSearchHistOpen()) requestPlaceSearchHist(); }); searchWrap.__pkSearchHistRO.observe(searchWrap); } UI.searchInput.onkeydown = (e) => { e.stopPropagation(); if (e.key === 'Enter') { performSearch(e.target.value); } }; if (UI.searchBtn) { UI.searchBtn.onclick = () => { performSearch(UI.searchInput.value); }; } if (UI.searchClear) { UI.searchClear.onclick = async () => { const wasGlobalChecked = UI.chkGlobal ? UI.chkGlobal.checked : false; if (!S.search && UI.searchInput.value) { UI.searchInput.value = ''; UI.searchClear.style.display = 'none'; UI.searchInput.focus(); return; } const searchWasActive = !!S.search; const isInVirtualStack = S.path.some(node => node.id === 'virtual_search_root'); let requiresReload = false; UI.searchInput.value = ''; S.search = ''; S.lastGlobalResults = []; syncLocalUploadVisibility(); try { const prefStore = JSON.parse(gmGet('pk_folder_sort_prefs', '{}')); if (prefStore['virtual_search_root']) { delete prefStore['virtual_search_root']; gmSet('pk_folder_sort_prefs', JSON.stringify(prefStore)); } } catch(e) {} if ((wasGlobalChecked || isInVirtualStack) && searchWasActive) { const currentFolder = S.path[S.path.length - 1]; if (currentFolder.id !== 'virtual_search_root' && currentFolder.id !== '') { const traceStack = []; let ptrId = currentFolder.id; let ptrName = currentFolder.name; let safety = 100; traceStack.unshift({ id: ptrId, name: ptrName }); while (ptrId && ptrId !== 'root' && safety > 0) { if (globalParentIndex.has(ptrId)) { const parent = globalParentIndex.get(ptrId); ptrId = parent.id; ptrName = parent.name; if (ptrId === 'root' || ptrId === '') break; traceStack.unshift({ id: ptrId, name: ptrName }); } else { const node = S.itemMap.get(ptrId); if (node && node._lineage && node._lineage.length > 0) { const ancestors = node._lineage.filter(x => x.id !== '' && x.id !== 'root'); for (let k = ancestors.length - 1; k >= 0; k--) { traceStack.unshift(ancestors[k]); } } break; } safety--; } const realPath = [{ id: '', name: L.btn_nav_home }, ...traceStack]; S.path = realPath; } else if (isInVirtualStack || S.preSearchPath) { S.path = S.preSearchPath ? [...S.preSearchPath] : [{ id: '', name: L.btn_nav_home }]; } requiresReload = true; } S.preSearchPath = null; UI.searchClear.style.display = 'none'; UI.searchHist.style.display = 'none'; if (S.dupMode && !isInVirtualStack) { if (S.pinnedDupPath) { S.pinnedDupPath = null; S.clearSelection(); UI.selDupFolder.value = ""; const invertChk = document.getElementById('pk-dup-invert'); if(invertChk) { invertChk.checked = false; invertChk.disabled = true; invertChk.parentNode.style.opacity = '0.5'; } } renderDupView(); updateStat(); } else if (S.isFlattened && !isInVirtualStack) { refresh(); updateStat(); } else { if (requiresReload) { const targetNode = S.path[S.path.length - 1]; const targetKey = S.getRealCacheKey(targetNode.id); const cachedData = (typeof globalCache !== 'undefined') ? (globalCache.get(targetKey) || globalCache.get('')) : null; if (cachedData) { if (cachedData.items) S.items = [...cachedData.items]; else if (Array.isArray(cachedData)) S.items = [...cachedData]; S.itemMap.clear(); for (const it of S.items) S.itemMap.set(it.id, it); } refresh(); setLoad(true); const p = load(false, true); if (UI.chkGlobal) UI.chkGlobal.checked = wasGlobalChecked; await p; } else { refresh(); updateStat(); } } }; } } UI.chkGlobal.onchange = async (e) => { if (e.target.checked) { if (S.movingIds && S.movingIds.size > 0) { e.target.checked = false; showAlert(L.msg_op_blocked_moving); return; } const isSuppressed = gmGet('pk_suppress_global_warn', false); if (!isSuppressed && !hasShownGlobalWarnSession) { const userChoice = await new Promise((resolve) => { const m = showModal(`

${L.title_confirm}

${esc(L.msg_global_warn).replace(/\n/g, '
')}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { Object.assign(modalBox.style, { width: '420px', padding: '30px', height: 'auto', minHeight: 'auto' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '26px', right: '26px' }); } m.querySelector('#cfm_cancel').onclick = () => { m.remove(); resolve({ ok: false }); }; m.querySelector('.pk-modal-close').onclick = () => { m.remove(); resolve({ ok: false }); }; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#cfm_ok').click(); } }); m.querySelector('#cfm_ok').onclick = () => { const isChecked = m.querySelector('#pk_warn_ignore').checked; m.remove(); resolve({ ok: true, suppress: isChecked }); }; }); if (!userChoice.ok) { e.target.checked = false; return; } hasShownGlobalWarnSession = true; if (userChoice.suppress) { gmSet('pk_suppress_global_warn', true); } } if (!isGlobalIndexReady || globalNeedsSync) { S.scanning = true; UI.stopBtn.onclick = () => { S.scanning = false; updateLoadTxt(L.str_stopping); UI.chkGlobal.checked = false; }; await runFlattenScanOperation(true); } refresh(); if (S._flattenEnterTopRaf) cancelAnimationFrame(S._flattenEnterTopRaf); if (S.isFlattened && isGridView()) { S._flattenEnterTopRaf = requestAnimationFrame(() => { S._flattenEnterTopRaf = 0; if (UI.vp && UI.vp.scrollTop !== 0) UI.vp.scrollTop = 0; }); } } else { if (S.scanning) { S.scanning = false; updateLoadTxt(L.str_stopping); } refresh(); } }; const runFlattenScanOperation = async (isSyncOnly = false, specificTargets =[], isSilent = false) => { S.scanId = (S.scanId || 0) + 1; const myScanId = S.scanId; let fileMap = new Map(); let processedFolders = 0; if (S.scanAbortController) S.scanAbortController.abort(); S.scanAbortController = new AbortController(); const signal = S.scanAbortController.signal; setLoad(true); const isPartialScan = specificTargets && specificTargets.length > 0; let rootNodes =[]; if (isPartialScan) { updateLoadTxt(L.msg_init_scan_sel); specificTargets.forEach(item => { if (item.kind === 'drive#folder') { rootNodes.push({ id: item.id, name: item.name, lineage:[{ id: item.id, name: item.name }], retryCount: 0 }); } else if (!isSyncOnly) { item._lineage =[]; fileMap.set(item.id, item); } }); } else { updateLoadTxt(`${L.str_scanning} 0`); const startNode = isSyncOnly ? { id: '', name: 'Root' } : S.path[S.path.length - 1]; rootNodes =[{ id: startNode.id || '', name: startNode.name || 'Root', lineage: [], retryCount: 0 }]; } UI.stopBtn.onclick = () => { S.scanning = false; if (S.scanAbortController) S.scanAbortController.abort(); updateLoadTxt(L.str_stopping); if (S.isFlattened) { UI.scan.style.display = 'none'; UI.btnExit.style.display = 'flex'; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'none'; if (UI.btnExport) UI.btnExport.style.display = 'none'; } else { UI.scan.style.display = 'flex'; UI.btnExit.style.display = 'none'; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'flex'; if (UI.btnExport) UI.btnExport.style.display = 'flex'; if(UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'flex'; if(UI.lblGlobal) UI.lblGlobal.style.display = 'flex'; if(UI.chkGlobal) UI.chkGlobal.checked = false; S.isFlattened = false; setTimeout(() => { if (typeof resumeBackgroundDiscovery === 'function') { resumeBackgroundDiscovery(); } }, 1000); } }; S.scanning = true; syncLocalUploadVisibility(); try { await coreRecursiveEngine(rootNodes, { signal: signal, onFile: (f, parent) => { if (!isSyncOnly) { f._lineage = parent.lineage ||[]; fileMap.set(f.id, f); } }, onFolder: (folder, filesInFolder) => { processedFolders++; indexParents(folder.id, folder.name, filesInFolder); if (typeof globalLineageMap !== 'undefined') { globalLineageMap.set(folder.id, folder.lineage); } }, onProgress: (st) => { const folderText = isPartialScan ? L.status_scanning_selection.replace('{n}', st.folders + " " + L.unit_folders) : `${L.str_scanning} ${st.folders} ${L.unit_folders}`; const retryTag = st.isRetrying ? `\n[ ${L.str_retries} ]` : ""; const statusInfo = ` | ${L.str_files}: ${st.files} | ${L.str_speed}: ${st.currentConcurrency} | ${L.str_cached} ${st.cacheHits} ${L.unit_folders}`; updateLoadTxt(folderText + statusInfo + retryTag); } }); if (S.scanning && !signal.aborted && myScanId === S.scanId) { const didBuildFullGlobalIndex = isSyncOnly && !isPartialScan; if (didBuildFullGlobalIndex) { globalNeedsSync = false; isGlobalIndexReady = true; } if (!isSyncOnly) { updateLoadTxt(L.str_merging); let tempItems = Array.from(fileMap.values()); const rawScanTotal = tempItems.length; const hasActiveScanFilter = !!(S.scanFilter && ((S.scanFilter.keyword || '').trim() || S.scanFilter.minBytes > 0 || S.scanFilter.maxBytes > 0)); if (S.scanFilter && !isSyncOnly) { const { minBytes, maxBytes, keyword } = S.scanFilter; const kwList = keyword ? keyword.toLowerCase().split(/[,,]/).map(k => k.trim()).filter(k => k) : []; tempItems = tempItems.filter(item => { if (kwList.length > 0) { const fullLowerName = (item.name || "").toLowerCase(); const lastDot = fullLowerName.lastIndexOf('.'); const nameWithoutExt = (item.kind !== 'drive#folder' && lastDot > 0) ? fullLowerName.substring(0, lastDot) : fullLowerName; if (kwList.some(k => nameWithoutExt.includes(k))) return false; } const sz = parseInt(item.size || 0); if (sz < minBytes) return false; if (maxBytes > 0 && sz > maxBytes) return false; return true; }); } const total = tempItems.length; if (!isSyncOnly && !isSilent && total === 0) { setLoad(false); S.scanning = false; showToast(rawScanTotal > 0 && hasActiveScanFilter ? L.msg_scan_filter_empty : L.msg_no_files); UI.scan.style.display = 'flex'; UI.btnExit.style.display = 'none'; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'flex'; if (UI.btnExport) UI.btnExport.style.display = 'flex'; if (UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'flex'; if (UI.lblGlobal) UI.lblGlobal.style.display = 'flex'; if (UI.chkGlobal) UI.chkGlobal.checked = false; return; } S.items = new Array(total); S.itemMap.clear(); let lastYield = performance.now(); for (let i = 0; i < total; i++) { const item = tempItems[i]; S.items[i] = item; S.itemMap.set(item.id, item); if (item.starred || (item.tags && item.tags.some(t => t.name === 'STAR'))) { S.starredSet.add(item.id); } if (i % 5000 === 0 && performance.now() - lastYield > 16) { updateLoadTxt(`${L.str_merging} ${Math.round((i / total) * 100)}%`); await sleep(0); lastYield = performance.now(); } } rememberFolderFirstBeforeStrictMode(); S.isFlattened = true; if (typeof applyResolvedViewMode === 'function') applyResolvedViewMode('file_insight'); if (typeof applyResolvedSortState === 'function') applyResolvedSortState('file_insight'); UI.chkAll.checked = false; S.clearSelection(); UI.scan.style.display = 'none'; UI.btnExit.style.display = 'flex'; if (UI.btnNewFolder) UI.btnNewFolder.style.display = 'none'; if (UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'none'; if (UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if (UI.crumb) UI.crumb.style.setProperty('display', 'none', 'important'); updateLoadTxt(L.str_rendering); await refresh(); if (S._flattenEnterTopRaf) cancelAnimationFrame(S._flattenEnterTopRaf); if (S.isFlattened && isGridView()) { S._flattenEnterTopRaf = requestAnimationFrame(() => { S._flattenEnterTopRaf = 0; if (UI.vp && UI.vp.scrollTop !== 0) UI.vp.scrollTop = 0; }); } const msg = L.msg_scan_done.replace('{n}', total).replace('{f}', processedFolders); if (!isSilent) showToast(msg.replace(/\n+/g, ' '), 'success', 3200); } } } catch (e) { if (e.name !== 'AbortError' && myScanId === S.scanId) { showAlert(`${L.str_error_crit}: ${e.message}`); } if (!isSyncOnly && myScanId === S.scanId) { UI.scan.style.display = 'flex'; UI.btnExit.style.display = 'none'; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'flex'; if (UI.btnExport) UI.btnExport.style.display = 'flex'; UI.lblGlobal.style.display = 'flex'; } if (myScanId === S.scanId) UI.chkGlobal.checked = false; } finally { if (myScanId === S.scanId) { setLoad(false); S.scanning = false; S.scanAbortController = null; if (typeof DurationProber !== 'undefined') DurationProber.checkAndRun(); } } }; const openScanDupModal = async (initialTab) => { if (S.loading || S.scanning) return; const curFolderId = S.path[S.path.length - 1].id || ''; if (isPathBusy(curFolderId)) { showAlert(L.msg_op_blocked_moving); return; } S.wasGlobalChecked = UI.chkGlobal ? UI.chkGlobal.checked : false; const selectedTargets = S.getSelectedIds() .map(id => S.itemMap.get(id)) .filter(Boolean); const lastMin = gmGet('pk_scan_last_min', 0); const lastMax = gmGet('pk_scan_last_max', ''); const lastUnit = gmGet('pk_scan_last_unit', 'MB'); const lastKeyword = gmGet('pk_scan_last_keyword', ''); let currentStrict = gmGet('pk_dup_strictness', 'strict'); const scanTxt = L.btn_scan; const dupTxt = L.tip_dup; const L_min = L.lbl_ana_min; const L_max = L.lbl_ana_max; let scanTargetDesc = selectedTargets.length > 0 ? L.lbl_scan_selected.replace('{n}', selectedTargets.length) : L.lbl_scan_current; let dupTargetDesc = selectedTargets.length > 0 ? L.lbl_dup_selected.replace('{n}', selectedTargets.length) : L.lbl_dup_current; const m = showModal(`

${L.title_file_analysis}

${scanTxt}
${dupTxt}
${scanTargetDesc}
${L.lbl_keyword_filter}
${L_min}
${CONF.crumbIcons.down.replace('points="6 9 12 15 18 9"', 'points="18 15 12 9 6 15"')}
${CONF.crumbIcons.down}
-
${L_max}
${CONF.crumbIcons.down.replace('points="6 9 12 15 18 9"', 'points="18 15 12 9 6 15"')}
${CONF.crumbIcons.down}
${lastUnit}
MB
GB
TB
${dupTargetDesc}
${L.label_dup_strictness}
${currentStrict === 'loose' ? L.opt_loose : L.opt_strict}${CONF.crumbIcons.down}
${L.opt_strict}
${L.opt_loose}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { Object.assign(modalBox.style, { width: 'auto', padding: '0', overflow: 'visible', height: 'auto', minHeight: 'auto' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '26px', right: '26px' }); } m.querySelectorAll('.pk-s-tab').forEach(tab => { tab.onclick = () => { m.querySelectorAll('.pk-s-tab').forEach(t => t.classList.remove('act')); tab.classList.add('act'); const curMode = tab.dataset.val; m.querySelector('#pane_scan').style.display = curMode === 'scan' ? 'block' : 'none'; m.querySelector('#pane_dup').style.display = curMode === 'dup' ? 'flex' : 'none'; }; }); const inpMin = m.querySelector('#sc_val_min'); const inpMax = m.querySelector('#sc_val_max'); const unitBtn = m.querySelector('#sc_unit_btn'); const unitMenu = m.querySelector('#sc_unit_menu'); const unitTxt = m.querySelector('#sc_unit_txt'); let currentUnit = lastUnit; const syncLimitBorder = (inp) => { if (!inp) return; inp.style.borderColor = String(inp.value || '').trim() ? 'var(--pk-pri)' : 'var(--pk-bd)'; }; [inpMin, inpMax].forEach(inp => { syncLimitBorder(inp); inp.addEventListener('input', () => syncLimitBorder(inp)); }); m.querySelector('#sc_inc_min').onclick = (e) => { e.stopPropagation(); inpMin.value = (parseInt(inpMin.value) || 0) + 1; inpMin.dispatchEvent(new Event('input')); }; m.querySelector('#sc_dec_min').onclick = (e) => { e.stopPropagation(); inpMin.value = Math.max(0, (parseInt(inpMin.value) || 1) - 1); inpMin.dispatchEvent(new Event('input')); }; m.querySelector('#sc_inc_max').onclick = (e) => { e.stopPropagation(); inpMax.value = (parseInt(inpMax.value) || 0) + 1; inpMax.dispatchEvent(new Event('input')); }; m.querySelector('#sc_dec_max').onclick = (e) => { e.stopPropagation(); inpMax.value = Math.max(0, (parseInt(inpMax.value) || 1) - 1); inpMax.dispatchEvent(new Event('input')); }; unitBtn.onclick = (e) => { e.stopPropagation(); unitMenu.style.display = unitMenu.style.display === 'block' ? 'none' : 'block'; }; m.querySelectorAll('.pk-ana-item').forEach(item => { item.onclick = () => { m.querySelectorAll('.pk-ana-item').forEach(i => i.classList.remove('act')); item.classList.add('act'); currentUnit = item.dataset.v; unitTxt.textContent = currentUnit; unitMenu.style.display = 'none'; }; }); const closeMenu = () => { if (unitMenu) unitMenu.style.display = 'none'; }; setTimeout(() => document.addEventListener('click', closeMenu), 0); const _orgRemove = m.remove.bind(m); m.remove = () => { document.removeEventListener('click', closeMenu); _orgRemove(); }; if (S.dupConfig) { m.querySelector('#scan_video').checked = S.dupConfig.video; m.querySelector('#scan_image').checked = S.dupConfig.image; m.querySelector('#scan_other').checked = S.dupConfig.other; } const scStrictTrigger = m.querySelector('#cs_sc_strict .pk-select-trigger'); const scStrictMenu = m.querySelector('#cs_sc_strict .pk-select-menu'); const scStrictTxt = m.querySelector('#txt_sc_strict'); scStrictTrigger.onclick = (e) => { e.stopPropagation(); scStrictMenu.style.display = scStrictMenu.style.display === 'block' ? 'none' : 'block'; }; m.querySelectorAll('#cs_sc_strict .pk-select-item').forEach(item => { item.onclick = (e) => { e.stopPropagation(); m.querySelectorAll('#cs_sc_strict .pk-select-item').forEach(i => i.classList.remove('act')); item.classList.add('act'); currentStrict = item.dataset.val; scStrictTxt.textContent = item.textContent; scStrictMenu.style.display = 'none'; }; }); const saveScanInputs = () => { gmSet('pk_scan_last_min', parseInt(inpMin.value) || 0); gmSet('pk_scan_last_max', inpMax.value.trim()); gmSet('pk_scan_last_unit', currentUnit); gmSet('pk_scan_last_keyword', m.querySelector('#sc_keyword').value.trim()); gmSet('pk_dup_strictness', currentStrict); }; m.querySelector('#sc_cancel').onclick = () => { saveScanInputs(); m.remove(); }; m.querySelector('.pk-modal-close').onclick = () => { saveScanInputs(); m.remove(); }; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#sc_start').click(); } }); m.querySelector('#sc_start').onclick = async () => { const mode = m.querySelector('.pk-s-tab.act').dataset.val; saveScanInputs(); if (mode === 'scan') { const vMin = parseInt(inpMin.value) || 0; const vMax = parseInt(inpMax.value) || 0; const kw = m.querySelector('#sc_keyword').value.trim(); if (vMin < 0 || (vMax > 0 && vMin > vMax)) { inpMin.style.borderColor = '#d93025'; if (vMax > 0 && vMin > vMax) inpMax.style.borderColor = '#d93025'; return; } gmSet('pk_scan_last_min', vMin); gmSet('pk_scan_last_max', vMax > 0 ? vMax : ''); gmSet('pk_scan_last_unit', currentUnit); gmSet('pk_scan_last_keyword', kw); let mult = 1; if (currentUnit === 'MB') mult = 1024 * 1024; else if (currentUnit === 'GB') mult = 1024 * 1024 * 1024; else if (currentUnit === 'TB') mult = 1024 * 1024 * 1024 * 1024; S.scanFilter = { minBytes: Math.floor(vMin * mult), maxBytes: vMax > 0 ? Math.floor(vMax * mult) : 0, keyword: kw }; m.remove(); S.search = ''; if (UI.searchInput) UI.searchInput.value = ''; if (UI.searchClear) UI.searchClear.style.display = 'none'; if (UI.chkSearchPath) UI.chkSearchPath.checked = false; S.scanning = true; syncLocalUploadVisibility(); UI.scan.style.display = 'none'; UI.btnExit.style.display = 'flex'; if(UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'none'; if (UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if (UI.chkGlobal) UI.chkGlobal.checked = false; UI.stopBtn.onclick = () => { S.scanning = false; if (S.scanAbortController) S.scanAbortController.abort(); updateLoadTxt(L.str_stopping); if (S.isFlattened) { UI.scan.style.display = 'none'; UI.btnExit.style.display = 'flex'; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'none'; if (UI.btnExport) UI.btnExport.style.display = 'none'; } else { UI.scan.style.display = 'flex'; UI.btnExit.style.display = 'none'; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'flex'; if (UI.btnExport) UI.btnExport.style.display = 'flex'; if(UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'flex'; if (UI.lblGlobal) UI.lblGlobal.style.display = 'flex'; if (UI.chkGlobal) UI.chkGlobal.checked = false; S.isFlattened = false; setTimeout(() => { if (typeof resumeBackgroundDiscovery === 'function') { resumeBackgroundDiscovery(); } }, 1000); } }; S.lastScanTargets = selectedTargets; await runFlattenScanOperation(false, selectedTargets, false); } else { S.dupConfig = { video: m.querySelector('#scan_video').checked, image: m.querySelector('#scan_image').checked, other: m.querySelector('#scan_other').checked }; if (!S.dupConfig.video && !S.dupConfig.image && !S.dupConfig.other) return; m.remove(); S.scanning = true; syncLocalUploadVisibility(); S.scanId = (S.scanId || 0) + 1; const myScanId = S.scanId; let fileMap = new Map(); let processedFolders = 0; if (S.scanAbortController) S.scanAbortController.abort(); S.scanAbortController = new AbortController(); const signal = S.scanAbortController.signal; setLoad(true); const isPartialScan = selectedTargets.length > 0; let rootNodes =[]; if (isPartialScan) { updateLoadTxt(L.msg_init_scan_sel); selectedTargets.forEach(item => { if (item.kind === 'drive#folder') { rootNodes.push({ id: item.id, name: item.name, lineage:[{ id: item.id, name: item.name }], retryCount: 0 }); } else { item._lineage =[]; fileMap.set(item.id, item); } }); } else { updateLoadTxt(`${L.str_scanning} 0`); const startNode = S.path[S.path.length - 1]; rootNodes =[{ id: startNode.id || '', name: startNode.name || 'Root', lineage: [], retryCount: 0 }]; } UI.stopBtn.onclick = () => { S.scanning = false; if (S.scanAbortController) S.scanAbortController.abort(); updateLoadTxt(L.str_stopping); setLoad(false); }; try { await coreRecursiveEngine(rootNodes, { signal: signal, onFile: (f, parent) => { f._lineage = parent.lineage ||[]; fileMap.set(f.id, f); }, onFolder: (folder, filesInFolder) => { processedFolders++; if (typeof globalCache !== 'undefined' && !globalCache.has(folder.id)) { globalCache.set(folder.id, [...filesInFolder]); } indexParents(folder.id, folder.name, filesInFolder); if (typeof globalLineageMap !== 'undefined') { globalLineageMap.set(folder.id, folder.lineage); } }, onProgress: (st) => { const folderText = isPartialScan ? L.status_scanning_selection.replace('{n}', st.folders + " " + L.unit_folders) : `${L.str_scanning} ${st.folders} ${L.unit_folders}`; const retryTag = st.isRetrying ? `\n[ ${L.str_retries} ]` : ""; const statusInfo = ` | ${L.str_files}: ${st.files} | ${L.str_speed}: ${st.currentConcurrency} | ${L.str_cached} ${st.cacheHits} ${L.unit_folders}`; updateLoadTxt(folderText + statusInfo + retryTag); } }); if (S.scanning && !signal.aborted && myScanId === S.scanId) { updateLoadTxt(L.str_merging); const tempItems = Array.from(fileMap.values()); const total = tempItems.length; S.items = new Array(total); S.itemMap.clear(); let lastYield = performance.now(); for (let i = 0; i < total; i++) { const item = tempItems[i]; S.items[i] = item; S.itemMap.set(item.id, item); if (item.starred || (item.tags && item.tags.some(t => t.name === 'STAR'))) { S.starredSet.add(item.id); } if (i % 5000 === 0 && performance.now() - lastYield > 16) { updateLoadTxt(`${L.str_merging} ${Math.round((i / total) * 100)}%`); await sleep(0); lastYield = performance.now(); } } rememberFolderFirstBeforeStrictMode(); S.dupMode = true; if (typeof applyResolvedViewMode === 'function') applyResolvedViewMode('file_dup'); S.groupSortOp = 'path'; S.groupSortDir = 1; S.isFlattened = false; S.sort = 'modified_time'; S.dir = 1; UI.chkAll.checked = false; S.clearSelection(); S.search = ''; S.lastGlobalResults = []; if (UI.searchInput) UI.searchInput.value = ''; if (UI.searchClear) UI.searchClear.style.display = 'none'; if (UI.searchHist) UI.searchHist.style.display = 'none'; UI.scan.style.display = 'none'; UI.btnExit.style.display = 'flex'; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'none'; UI.lblGlobal.style.display = 'none'; UI.chkGlobal.checked = false; if (UI.btnNewFolder) UI.btnNewFolder.style.display = 'none'; if (UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'none'; if (UI.crumb) UI.crumb.style.setProperty('display', 'none', 'important'); if (UI.lblSearchPath) UI.lblSearchPath.style.display = 'flex'; updateLoadTxt(L.str_rendering); S.display = [...S.items]; await refresh(); if (S._enterTopRaf) cancelAnimationFrame(S._enterTopRaf); S._enterTopRaf = requestAnimationFrame(() => { S._enterTopRaf = 0; if (UI.vp && UI.vp.scrollTop !== 0) UI.vp.scrollTop = 0; }); } } catch (e) { if (e.name !== 'AbortError' && myScanId === S.scanId) { showAlert(`${L.str_error_crit}: ${e.message}`); } if (myScanId === S.scanId) { UI.scan.style.display = 'flex'; UI.btnExit.style.display = 'none'; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'flex'; if (UI.btnExport) UI.btnExport.style.display = 'flex'; UI.lblGlobal.style.display = 'flex'; } } finally { if (myScanId === S.scanId) { setLoad(false); S.scanning = false; S.scanAbortController = null; if (typeof DurationProber !== 'undefined') DurationProber.checkAndRun(); } } } }; }; UI.scan.onclick = () => openScanDupModal('scan'); const onOfflineFilterChange = () => { if (S.offlineMode) { S.offlineFilters = { running: UI.chkOffRun.checked, failed: UI.chkOffFail.checked, complete: UI.chkOffOk.checked }; refresh(); updateStat(); } }; if (UI.chkOffRun) UI.chkOffRun.onchange = onOfflineFilterChange; if (UI.chkOffFail) UI.chkOffFail.onchange = onOfflineFilterChange; if (UI.chkOffOk) UI.chkOffOk.onchange = onOfflineFilterChange; const onUploadFilterChange = () => { if (S.uploadMode) { S.uploadFilters = { running: UI.chkUpRun.checked, paused: UI.chkUpPause.checked, complete: UI.chkUpDone.checked }; refresh(); updateStat(); } }; if (UI.chkUpRun) UI.chkUpRun.onchange = onUploadFilterChange; if (UI.chkUpPause) UI.chkUpPause.onchange = onUploadFilterChange; if (UI.chkUpDone) UI.chkUpDone.onchange = onUploadFilterChange; const onDupFilterChange = () => { if(S.dupMode) { const useGridVisualGuard = isGridView() && UI.win; if (useGridVisualGuard) { UI.win.classList.add('pk-grid-scrolling', 'pk-view-switching'); if (typeof markGridScrolling === 'function') markGridScrolling(); } if (S.pinnedDupPath) { S.pinnedDupPath = null; S.clearSelection(); UI.selDupFolder.value = ""; const invertChk = document.getElementById('pk-dup-invert'); if(invertChk) { invertChk.checked = false; invertChk.disabled = true; invertChk.parentNode.style.opacity = '0.5'; } } renderDupView(); if (useGridVisualGuard) { if (typeof markGridScrolling === 'function') markGridScrolling(); requestAnimationFrame(() => { requestAnimationFrame(() => { if (UI.win) UI.win.classList.remove('pk-view-switching'); }); }); } } }; UI.chkName.onchange = onDupFilterChange; UI.chkSim.onchange = onDupFilterChange; UI.chkHash.onchange = onDupFilterChange; if (UI.chkSearchPath) { UI.chkSearchPath.onchange = () => { if (S.shareParseMode && S.shareParseInsightMode) { syncShareParseInsightMirror(); renderList(); updateStat(); return; } if (S.dupMode && S.search) { renderDupView(); } else if ((S.isFlattened || S.analyzeMode) && S.search) { refresh(); } }; } UI.btnExit.onclick = async () => { await S.exitVirtualNavMode(); if (typeof S.wasGlobalChecked !== 'undefined' && UI.chkGlobal) { UI.chkGlobal.checked = S.wasGlobalChecked; } setTimeout(() => { if (typeof resumeBackgroundDiscovery === 'function') { resumeBackgroundDiscovery(); } }, 1500); }; UI.cols.forEach(c => c.onclick = () => { if (S.dupMode) return; const cur = S.path[S.path.length - 1] || { id: '' }; if (S.analyzeMode && cur.id === 'analyze_root' && S.analyzeSimGroups) return; const k = c.dataset.k; applyManualSortSelection(k); }); if (UI.btnFolderFirst) { S.renderFolderFirst = () => { if (UI.btnFolderFirst) UI.btnFolderFirst.style.color = S.folderFirst ? 'var(--pk-pri)' : '#666'; if (UI.btnGridFolderFirst) UI.btnGridFolderFirst.style.color = S.folderFirst ? 'var(--pk-pri)' : '#666'; }; UI.btnFolderFirst.onmouseenter = () => { if (!S.folderFirst) UI.btnFolderFirst.style.color = 'var(--pk-fg)'; }; UI.btnFolderFirst.onmouseleave = () => { if (!S.folderFirst) UI.btnFolderFirst.style.color = '#666'; }; const nameWrap = el.querySelector('#pk-name-text-wrap'); if (nameWrap) { nameWrap.onmouseenter = () => { if (S.sort !== 'name') nameWrap.style.color = 'var(--pk-fg)'; }; nameWrap.onmouseleave = () => { if (S.sort !== 'name') nameWrap.style.color = '#666'; }; } S.renderFolderFirst(); UI.btnFolderFirst.onclick = (e) => { e.stopPropagation(); toggleFolderFirst(); }; } const btnInvert = document.getElementById('pk-btn-invert'); if (btnInvert) { btnInvert.onclick = (e) => { e.stopPropagation(); if (S.display.length === 0) return; S.invertSelection(); renderVisible(); updateStat(); }; btnInvert.onmouseenter = () => btnInvert.style.color = 'var(--pk-pri)'; btnInvert.onmouseleave = () => btnInvert.style.color = 'var(--pk-fg)'; } S.handleSelectAll = (e) => { if (!e || !e.target) { if (UI.chkAll) UI.chkAll.checked = !UI.chkAll.checked; } S.activeId = null; S.lastSelIdx = -1; const totalVisible = S.getSelectableCount(); const selectedCount = S.getSelectedCount(); const isSelectAllAction = selectedCount < totalVisible; if (UI.chkAll) { UI.chkAll.checked = isSelectAllAction; UI.chkAll.indeterminate = false; } if (isSelectAllAction) { S.setAllSelection(true); } else { S.setAllSelection(false); } requestAnimationFrame(() => { renderVisible(); updateStat(); if (UI.chkAll) { UI.chkAll.checked = isSelectAllAction; UI.chkAll.indeterminate = false; } }); }; if (UI.btnAnaSelect || UI.btnAnaSort || UI.btnDupSmart || UI.btnDupSort) { const pop = document.createElement('div'); pop.className = 'pk-ana-pop'; pop.innerHTML = `
${L.opt_keep_new}
${L.opt_keep_old}
${L.opt_keep_large}
${L.opt_keep_small}
${L.opt_keep_short}
${L.opt_keep_long}
`; const sortPop = document.createElement('div'); sortPop.className = 'pk-ana-pop'; sortPop.innerHTML = `
${L.opt_sort_time}
${L.opt_sort_size}
${L.opt_sort_path}
${L.opt_sort_name}
`; UI.win.appendChild(pop); UI.win.appendChild(sortPop); let activeTargetBtn = null; let activePop = null; const updatePopPos = () => { if (!activePop || activePop.style.display !== 'flex' || !activeTargetBtn) return; const winRect = getLogicalRect(UI.win); const btnRect = getLogicalRect(activeTargetBtn); let left = btnRect.left - winRect.left; const top = btnRect.bottom - winRect.top; const winWidth = winRect.width; const popWidth = activePop === sortPop ? 200 : 340; if (left + popWidth > winWidth - 10) { left = btnRect.right - winRect.left - popWidth; } activePop.style.left = Math.max(10, left) + 'px'; activePop.style.top = (top + 5) + 'px'; }; const togglePop = (e, btn, targetPop) => { e.stopPropagation(); const isVisible = (targetPop.style.display === 'flex' && activeTargetBtn === btn); if (activePop && activePop !== targetPop) activePop.style.display = 'none'; if (isVisible) { targetPop.style.display = 'none'; activeTargetBtn = null; activePop = null; } else { if (targetPop === pop && S.analyzeMode && !S.hasShownAnaWarn) { showToast(L.msg_ana_warn, 'warning', 6000); S.hasShownAnaWarn = true; } if (targetPop === sortPop) { if (!S.groupSortOp) { S.groupSortOp = 'path'; S.groupSortDir = 1; } sortPop.querySelectorAll('.pk-sort-opt').forEach(el => { let baseTxt = ""; if (el.dataset.op === 'time') baseTxt = L.opt_sort_time; if (el.dataset.op === 'size') baseTxt = L.opt_sort_size; if (el.dataset.op === 'path') baseTxt = L.opt_sort_path; if (el.dataset.op === 'name') baseTxt = L.opt_sort_name; if (el.dataset.op === S.groupSortOp) { el.textContent = baseTxt + (S.groupSortDir === 1 ? ' ▼' : ' ▲'); el.style.color = 'var(--pk-pri)'; } else { el.textContent = baseTxt; el.style.color = ''; } }); } targetPop.style.display = 'flex'; activeTargetBtn = btn; activePop = targetPop; updatePopPos(); } }; if (UI.btnAnaSelect) UI.btnAnaSelect.onclick = (e) => togglePop(e, UI.btnAnaSelect, pop); if (UI.btnAnaSort) UI.btnAnaSort.onclick = (e) => togglePop(e, UI.btnAnaSort, sortPop); if (UI.btnDupSmart) UI.btnDupSmart.onclick = (e) => togglePop(e, UI.btnDupSmart, pop); if (UI.btnDupSort) UI.btnDupSort.onclick = (e) => togglePop(e, UI.btnDupSort, sortPop); window.addEventListener('resize', updatePopPos); pop.querySelectorAll('.pk-ana-opt').forEach(opt => { opt.onclick = () => { const op = opt.dataset.op; const nextSel = []; const isBetter = (type, curW, curM) => { if (type === 'new') return new Date(curM.modified_time) > new Date(curW.modified_time); if (type === 'old') return new Date(curM.modified_time) < new Date(curW.modified_time); if (type === 'large') return BigInt(curM.size || 0) > BigInt(curW.size || 0); if (type === 'small') return BigInt(curM.size || 0) < BigInt(curW.size || 0); if (type === 'short') return curM.name.length < curW.name.length; if (type === 'long') return curM.name.length > curW.name.length; return false; }; if (S.analyzeMode && S.analyzeSimGroups) { S.analyzeSimGroups.forEach(g => { const members = g.ids.map(id => S.itemMap.get(id)).filter(Boolean); if (members.length < 2) return; let winner = members[0]; members.forEach(m => { if (isBetter(op, winner, m)) winner = m; }); members.forEach(m => { if (m.id !== winner.id) nextSel.push(m.id); }); }); } else if (S.dupMode && S.dupGroups) { const itemMap = new Map(); S.display.forEach(d => { if (d.isHeader) return; const gIdx = S.dupGroups.get(d.id); if (gIdx !== undefined) { if (!itemMap.has(gIdx)) itemMap.set(gIdx, []); itemMap.get(gIdx).push(d); } }); itemMap.forEach(members => { if (members.length < 2) return; let winner = members[0]; members.forEach(m => { if (isBetter(op, winner, m)) winner = m; }); members.forEach(m => { if (m.id !== winner.id) nextSel.push(m.id); }); }); } S.setExplicitSelection(nextSel); pop.style.display = 'none'; activeTargetBtn = null; activePop = null; renderVisible(); updateStat(); }; }); sortPop.querySelectorAll('.pk-sort-opt').forEach(opt => { opt.onclick = (e) => { e.stopPropagation(); const op = opt.dataset.op; if (S.groupSortOp === op) { S.groupSortDir *= -1; } else { S.groupSortOp = op; S.groupSortDir = 1; } sortPop.querySelectorAll('.pk-sort-opt').forEach(el => { let baseTxt = ""; if (el.dataset.op === 'time') baseTxt = L.opt_sort_time; if (el.dataset.op === 'size') baseTxt = L.opt_sort_size; if (el.dataset.op === 'path') baseTxt = L.opt_sort_path; if (el.dataset.op === 'name') baseTxt = L.opt_sort_name; if (el.dataset.op === S.groupSortOp) { el.textContent = baseTxt + (S.groupSortDir === 1 ? ' ▼' : ' ▲'); el.style.color = 'var(--pk-pri)'; } else { el.textContent = baseTxt; el.style.color = ''; } }); const cmp = (a, b) => { let res = 0; if (S.groupSortOp === 'time') { res = new Date(b.modified_time) - new Date(a.modified_time); } else if (S.groupSortOp === 'size') { const sa = BigInt(a.size || 0), sb = BigInt(b.size || 0); res = sa < sb ? 1 : (sa > sb ? -1 : 0); } else if (S.groupSortOp === 'path') { const pa = a._dupFullPath || a._pathStr || a.path || ""; const pb = b._dupFullPath || b._pathStr || b.path || ""; res = pa.localeCompare(pb); } else if (S.groupSortOp === 'name') { res = a.name.localeCompare(b.name); } return res * S.groupSortDir; }; const newDisplay = []; let currentGroupHeader = null; let currentGroupMembers = []; const flushGroup = () => { if (currentGroupHeader) newDisplay.push(currentGroupHeader); if (currentGroupMembers.length > 0) { const getDupPath = it => it._dupFullPath || it._pathStr || it.path || ""; let orderedMembers = currentGroupMembers; if (S.pinnedDupPath) { const pinnedMembers = []; const otherMembers = []; currentGroupMembers.forEach(it => { if (getDupPath(it) === S.pinnedDupPath) pinnedMembers.push(it); else otherMembers.push(it); }); if (pinnedMembers.length > 0) { pinnedMembers.sort(cmp); otherMembers.sort(cmp); orderedMembers = pinnedMembers.concat(otherMembers); } else { orderedMembers = currentGroupMembers.slice().sort(cmp); } } else { orderedMembers = currentGroupMembers.slice().sort(cmp); } let lastPath = null; orderedMembers.forEach((it, idx) => { const curPath = getDupPath(it); if (idx > 0 && curPath === lastPath && curPath !== "") { it._isSameFolder = true; } else { it._isSameFolder = false; } lastPath = curPath; }); newDisplay.push(...orderedMembers); } currentGroupHeader = null; currentGroupMembers = []; }; S.display.forEach(d => { if (d.isHeader) { flushGroup(); currentGroupHeader = d; } else { currentGroupMembers.push(d); } }); flushGroup(); S.display = newDisplay; clearGroupedGridMetaCache(); clearGridStableRows(UI.in); renderVisible(); }; }); document.addEventListener('mousedown', (e) => { const isClickInsideBtn = (UI.btnAnaSelect && UI.btnAnaSelect.contains(e.target)) || (UI.btnAnaSort && UI.btnAnaSort.contains(e.target)) || (UI.btnDupSmart && UI.btnDupSmart.contains(e.target)) || (UI.btnDupSort && UI.btnDupSort.contains(e.target)); if (!pop.contains(e.target) && !sortPop.contains(e.target) && !isClickInsideBtn) { pop.style.display = 'none'; sortPop.style.display = 'none'; activeTargetBtn = null; activePop = null; } }); } if (UI.btnShareParseSave) { UI.btnShareParseSave.onclick = handleShareParseSaveSelected; } if (UI.btnShareParseInsight) { UI.btnShareParseInsight.onclick = handleShareParseInsightStart; } if (UI.btnShareParseStopScan) { UI.btnShareParseStopScan.onclick = handleShareParseInsightStop; } if (UI.btnShareParseBackList) { UI.btnShareParseBackList.onclick = handleShareParseInsightBack; } UI.btnRefresh.onclick = async () => { updateQuotaUI(); if (S.isFlattened) { if (!S.scanning) { S.scanning = true; UI.scan.style.display = 'none'; UI.btnExit.style.display = 'flex'; UI.stopBtn.onclick = () => { S.scanning = false; updateLoadTxt(L.str_stopping); UI.scan.style.display = 'none'; UI.btnExit.style.display = 'flex'; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'none'; if (UI.btnExport) UI.btnExport.style.display = 'none'; if(UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'none'; if (UI.lblGlobal) UI.lblGlobal.style.display = 'none'; }; runFlattenScanOperation(false, S.lastScanTargets, true).catch(e => { console.error(e); S.scanning = false; }); } return; } const cur = S.path[S.path.length - 1]; const intent = UI.chkGlobal ? UI.chkGlobal.checked : false; if (cur.id === 'analyze_root') { setLoad(true); updateLoadTxt(L.str_refreshing); await sleep(200); await load(); return; } if (S.historyMode && cur.id === 'history_root') { if (S.abortController) S.abortController.abort(); S.historyRefreshFirstPageOnly = true; S.clearSelection(); S.activeId = null; if (UI.chkAll) { UI.chkAll.checked = false; UI.chkAll.indeterminate = false; } S.items = []; S.display = []; S.itemMap.clear(); S.cache.delete('history_root'); if (typeof globalCache !== 'undefined') { globalCache.delete('history_root'); globalCache.delete('history_session'); } setLoad(true, true); updateLoadTxt(L.loading_detail); refresh(); updateStat(); const p = load(false, true); if (UI.chkGlobal) UI.chkGlobal.checked = intent; try { await p; } finally { S.historyRefreshFirstPageOnly = false; } return; } if (S.recentMode && S.path.length === 1 && cur.id === 'recent_root') { if (S.abortController) S.abortController.abort(); S.recentLoadNextPageOnly = false; S.recentLoadingNextPage = false; S.recentRefreshFirstPageOnly = false; S.clearSelection(); S.activeId = null; if (UI.chkAll) { UI.chkAll.checked = false; UI.chkAll.indeterminate = false; } S.items = []; S.display = []; S.itemMap.clear(); S.recentResultItems = null; S.cache.delete('recent_root'); if (UI.vp) UI.vp.scrollTop = 0; if (typeof globalCache !== 'undefined') { globalCache.delete('recent_root'); globalCache.delete('recent_session'); } setLoad(true, true); updateLoadTxt(L.loading_detail); refresh(); updateStat(); const p = load(false, true); if (UI.chkGlobal) UI.chkGlobal.checked = intent; try { await p; } finally { S.recentRefreshFirstPageOnly = false; } return; } if (cur.id) S.cache.delete(cur.id); const p = load(false, true); if (UI.chkGlobal) UI.chkGlobal.checked = intent; await p; }; if (UI.btnAnalyze) { UI.btnAnalyze.onclick = async () => { if (S.trashMode) return; const curFolderId = S.path[S.path.length - 1].id || ''; if (isPathBusy(curFolderId)) { showAlert(L.msg_op_blocked_moving); return; } S.search = ''; if (UI.searchInput) UI.searchInput.value = ''; if (UI.searchClear) UI.searchClear.style.display = 'none'; S.wasGlobalChecked = UI.chkGlobal ? UI.chkGlobal.checked : false; const lastMin = gmGet('pk_analyze_last_min', 0); const lastMax = gmGet('pk_analyze_last_max', ''); const lastUnit = gmGet('pk_analyze_last_unit', 'GB'); const lastKeyword = gmGet('pk_analyze_last_keyword', ''); const lastSim = gmGet('pk_analyze_last_sim', 1.0); const lastAlgo = gmGet('pk_analyze_last_algo', 'sim'); const result = await new Promise((resolve) => { const L_min = L.lbl_ana_min; const L_max = L.lbl_ana_max; const selectedCount = S.getSelectedCount(); const analyzeTargetDesc = selectedCount > 0 ? L.lbl_analyze_selected.replace('{n}', selectedCount) : L.lbl_analyze_current; const anaSimTargetDesc = selectedCount > 0 ? L.lbl_ana_sim_selected.replace('{n}', selectedCount) : L.lbl_ana_sim_current; const m = showModal(`

${L.btn_analyze}

${L.opt_ana_large}
${L.opt_ana_sim}
${analyzeTargetDesc}
${L.lbl_keyword_filter}
${L_min}
${CONF.crumbIcons.down.replace('points="6 9 12 15 18 9"', 'points="18 15 12 9 6 15"')}
${CONF.crumbIcons.down}
-
${L_max}
${CONF.crumbIcons.down.replace('points="6 9 12 15 18 9"', 'points="18 15 12 9 6 15"')}
${CONF.crumbIcons.down}
${lastUnit}
MB
GB
TB
`); let currentMode = 'large'; let currentSim = lastSim; const updateAlgoLabel = () => { const lblEl = m.querySelector('#cs_ana_sim .pk-select-label'); if (lblEl) lblEl.textContent = L.lbl_threshold; }; m.querySelectorAll('input[name="ana_sim_algo"]').forEach(r => r.addEventListener('change', updateAlgoLabel)); updateAlgoLabel(); m.querySelector('#pk_algo_help').onclick = (e) => { e.stopPropagation(); showAlert(L.algo_help_content, L.title_algo_help); }; m.querySelectorAll('.pk-s-tab').forEach(tab => { tab.onclick = () => { m.querySelectorAll('.pk-s-tab').forEach(t => t.classList.remove('act')); tab.classList.add('act'); currentMode = tab.dataset.val; m.querySelector('#ana_pane_large').style.display = currentMode === 'large' ? 'block' : 'none'; m.querySelector('#ana_pane_similar').style.display = currentMode === 'similar' ? 'block' : 'none'; }; }); const simTrigger = m.querySelector('#cs_ana_sim .pk-select-trigger'); const simMenu = m.querySelector('#cs_ana_sim .pk-select-menu'); const simTxt = m.querySelector('#txt_ana_sim'); simTrigger.onclick = (e) => { e.stopPropagation(); simMenu.style.display = simMenu.style.display === 'block' ? 'none' : 'block'; }; m.querySelectorAll('#cs_ana_sim .pk-select-item').forEach(item => { item.onclick = (e) => { e.stopPropagation(); m.querySelectorAll('#cs_ana_sim .pk-select-item').forEach(i => i.classList.remove('act')); item.classList.add('act'); currentSim = parseFloat(item.dataset.val); gmSet('pk_analyze_last_sim', currentSim); simTxt.textContent = (currentSim <= 0.5) ? L.opt_loose : L.opt_strict; simMenu.style.display = 'none'; }; }); const inpMin = m.querySelector('#an_val_min'); const inpMax = m.querySelector('#an_val_max'); const btn = m.querySelector('#an_unit_btn'); const syncLimitBorder = (inp) => { if (!inp) return; inp.style.borderColor = String(inp.value || '').trim() ? 'var(--pk-pri)' : 'var(--pk-bd)'; }; [inpMin, inpMax].forEach(inp => { syncLimitBorder(inp); inp.addEventListener('input', () => syncLimitBorder(inp)); }); m.querySelector('#an_inc_min').onclick = (e) => { e.stopPropagation(); inpMin.value = (parseInt(inpMin.value) || 0) + 1; inpMin.dispatchEvent(new Event('input')); }; m.querySelector('#an_dec_min').onclick = (e) => { e.stopPropagation(); inpMin.value = Math.max(0, (parseInt(inpMin.value) || 1) - 1); inpMin.dispatchEvent(new Event('input')); }; m.querySelector('#an_inc_max').onclick = (e) => { e.stopPropagation(); inpMax.value = (parseInt(inpMax.value) || 0) + 1; inpMax.dispatchEvent(new Event('input')); }; m.querySelector('#an_dec_max').onclick = (e) => { e.stopPropagation(); inpMax.value = Math.max(0, (parseInt(inpMax.value) || 1) - 1); inpMax.dispatchEvent(new Event('input')); }; const menu = m.querySelector('#an_unit_menu'); const txt = m.querySelector('#an_unit_txt'); let currentUnit = lastUnit; btn.onclick = (e) => { e.stopPropagation(); menu.style.display = menu.style.display === 'block' ? 'none' : 'block'; }; m.querySelectorAll('.pk-ana-item').forEach(item => { item.onclick = () => { m.querySelectorAll('.pk-ana-item').forEach(i => i.classList.remove('act')); item.classList.add('act'); currentUnit = item.dataset.v; txt.textContent = currentUnit; menu.style.display = 'none'; }; }); const closeMenu = () => { if(menu) menu.style.display = 'none'; if(simMenu) simMenu.style.display = 'none'; }; setTimeout(() => document.addEventListener('click', closeMenu), 0); const _orgRemove = m.remove.bind(m); m.remove = () => { document.removeEventListener('click', closeMenu); _orgRemove(); }; setTimeout(() => m.focus(), 50); const kHandler = (e) => { if (e.key === 'Enter') m.querySelector('#an_confirm').click(); if (e.key === 'Escape') m.querySelector('#an_cancel').click(); }; inpMin.onkeydown = kHandler; inpMax.onkeydown = kHandler; const saveAnalyzeInputs = () => { gmSet('pk_analyze_last_min', parseInt(inpMin.value) || 0); gmSet('pk_analyze_last_max', inpMax.value.trim()); gmSet('pk_analyze_last_unit', currentUnit); gmSet('pk_analyze_last_keyword', m.querySelector('#an_keyword').value.trim()); const algo = m.querySelector('input[name="ana_sim_algo"]:checked')?.value; if (algo) gmSet('pk_analyze_last_algo', algo); }; m.querySelector('#an_cancel').onclick = () => { saveAnalyzeInputs(); m.remove(); resolve(null); }; m.querySelector('.pk-modal-close').onclick = () => { saveAnalyzeInputs(); m.remove(); resolve(null); }; m.querySelector('#an_confirm').onclick = () => { if (currentMode === 'large') { const vMin = parseInt(inpMin.value) || 0; const vMax = parseInt(inpMax.value) || 0; const kw = m.querySelector('#an_keyword').value.trim(); if (vMin < 0 || (vMax > 0 && vMin > vMax)) { inpMin.style.borderColor = '#d93025'; if (vMax > 0 && vMin > vMax) inpMax.style.borderColor = '#d93025'; return; } saveAnalyzeInputs(); let mult = 1; if (currentUnit === 'MB') mult = 1024 * 1024; else if (currentUnit === 'GB') mult = 1024 * 1024 * 1024; else if (currentUnit === 'TB') mult = 1024 * 1024 * 1024 * 1024; m.remove(); resolve({ mode: 'large', minBytes: Math.floor(vMin * mult), maxBytes: vMax > 0 ? Math.floor(vMax * mult) : 0, keyword: kw }); } else { const algo = m.querySelector('input[name="ana_sim_algo"]:checked').value; gmSet('pk_analyze_last_algo', algo); m.remove(); resolve({ mode: 'similar', threshold: currentSim, algo: algo }); } }; const modalBox = m.querySelector('.pk-modal'); if (modalBox) { Object.assign(modalBox.style, { width: '540px', height: 'auto', minHeight: 'auto', overflow: 'visible', paddingBottom: '30px' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '22px', right: '22px' }); } }); if (result === null) return; const isSimMode = result.mode === 'similar'; S.analyzeSimGroups = null; const minBytes = result.minBytes || 0; const maxBytes = result.maxBytes || 0; const simThreshold = result.threshold || 0.9; const simAlgo = result.algo || 'sim'; setLoad(true); isGUISensitive = true; let nodeMap = new Map(); const largeFolders = []; const startNodes = []; const getRealLineage = (item) => { if (item._lineage && item._lineage.length > 0) { return item._lineage; } const cleanPath = S.path.filter(p => p.id !== 'analyze_root' && p.id !== 'virtual_search_root'); return [...cleanPath, { id: item.id, name: item.name }]; }; const analyzeSelectedIds = S.getSelectedIds(); if (analyzeSelectedIds.length > 0) { analyzeSelectedIds.forEach(id => { const item = S.itemMap.get(id); if (item && item.kind === 'drive#folder') { const fullLineage = getRealLineage(item); startNodes.push({ id: item.id, name: item.name, icon_link: item.icon_link, starred: item.starred, tags: item.tags, lineage: fullLineage, retryCount: 0, _pathStr: fullLineage.map(x => x.name).join('/') }); } }); } else { const subFolders = S.items.filter(it => it.kind === 'drive#folder'); if (subFolders.length > 0) { subFolders.forEach(item => { const fullLineage = getRealLineage(item); startNodes.push({ id: item.id, name: item.name, icon_link: item.icon_link, starred: item.starred, tags: item.tags, lineage: fullLineage, retryCount: 0, _pathStr: fullLineage.map(x => x.name).join('/') }); }); } else { const cur = S.path[S.path.length - 1]; const cleanPath = S.path.filter(p => p.id !== 'analyze_root' && p.id !== 'virtual_search_root'); if (cleanPath.length === 0) cleanPath.push({ id: '', name: L.btn_nav_home }); const rootName = cur.name || 'Root'; const actualCur = S.itemMap.get(cur.id); startNodes.push({ id: cur.id || '', name: rootName, icon_link: cur.icon_link, starred: actualCur ? actualCur.starred : false, tags: actualCur ? actualCur.tags :[], lineage: cleanPath, retryCount: 0, _pathStr: cleanPath.map(x => x.name).join('/') }); } } if (startNodes.length === 0) { setLoad(false); isGUISensitive = false; showToast(L.msg_analyze_only_normal_dir); return; } startNodes.forEach(n => { nodeMap.set(n.id, { id: n.id, name: n.name, icon_link: n.icon_link, starred: n.starred, tags: n.tags, size: 0, parentId: null, marked: false, _pathStr: n._pathStr, lineage: n.lineage, isRoot: true, files:[] }); }); const cacheKey = '__analyze_nodeMap_' + startNodes.map(n => n.id).join('_'); let useCache = false; if (typeof globalCache !== 'undefined' && globalCache.has(cacheKey) && globalDirtyFolders.size === 0) { const cachedArr = globalCache.get(cacheKey); nodeMap = new Map(cachedArr); useCache = true; } const propagateSize = (parentId, addSize) => { let curId = parentId; while (curId !== null && nodeMap.has(curId)) { const node = nodeMap.get(curId); node.size += addSize; if (!node.isRoot && node.size >= minBytes && !node.marked) { node.marked = true; largeFolders.push(node); } curId = node.parentId; } }; S.scanId = (S.scanId || 0) + 1; const myScanId = S.scanId; if (S.scanAbortController) S.scanAbortController.abort(); S.scanAbortController = new AbortController(); const signal = S.scanAbortController.signal; let isRunning = true; UI.stopBtn.onclick = () => { isRunning = false; S.scanning = false; if (S.scanAbortController) S.scanAbortController.abort(); updateLoadTxt(L.str_stopping); }; S.scanning = true; syncLocalUploadVisibility(); let totalFilesScanned = 0; let totalDirsScanned = 0; try { if (!useCache) { await coreRecursiveEngine(startNodes, { signal: signal, onFolder: (folder, filesInFolder, nextSubFolders) => { totalDirsScanned++; nextSubFolders.forEach(sub => { if (!nodeMap.has(sub.id)) { const fullPathStr = sub.lineage.map(x => x.name).join('/'); nodeMap.set(sub.id, { id: sub.id, name: sub.name, icon_link: sub.icon_link, starred: sub.starred, tags: sub.tags, size: 0, parentId: folder.id, marked: false, _pathStr: fullPathStr, lineage: sub.lineage, isRoot: false, files:[] }); } }); }, onFile: (file, parent) => { totalFilesScanned++; const sz = Number(file.size || 0); if (sz > 0) { propagateSize(parent.id, sz); } if (nodeMap.has(parent.id)) { const fingerprint = file.hash ? `${file.hash}_${sz}` : `${file.name}_${sz}`; let curId = parent.id; while (curId !== null && nodeMap.has(curId)) { nodeMap.get(curId).files.push(fingerprint); curId = nodeMap.get(curId).parentId; } } }, onProgress: (st) => { updateLoadTxt(`${L.str_scanning} ${st.folders} ${L.unit_folders} | ${L.str_files}: ${st.files} | ${L.str_speed}: ${st.currentConcurrency} | ${L.str_cached} ${st.cacheHits} ${L.unit_folders}`); } }); if (!isRunning || signal.aborted || myScanId !== S.scanId) { throw new Error('StoppedByUser'); } if (typeof globalCache !== 'undefined') { globalCache.set(cacheKey, Array.from(nodeMap.entries())); } } else { Array.from(nodeMap.values()).forEach(node => { if (!node.isRoot && node.size >= minBytes) { largeFolders.push(node); } }); } } catch (e) { if (isRunning && e.message !== 'StoppedByUser' && e.name !== 'AbortError') { showAlert(`${L.str_error}: ${e.message}`); setLoad(false); } } finally { isGUISensitive = false; S.scanning = false; S.scanAbortController = null; if (!isRunning) { setLoad(false); return; } let viewItems = []; if (isSimMode) { updateLoadTxt(`${L.str_analyzing}...`); Array.from(nodeMap.values()).forEach(node => { node._ancestorSet = new Set(node.lineage ? node.lineage.map(p => p.id) : []); if (node.parentId && nodeMap.has(node.parentId)) { const parent = nodeMap.get(node.parentId); if (parent.files.length === node.files.length) { parent.isShell = true; } } }); const isDescendant = (childId, parentId) => { const childNode = nodeMap.get(childId); return childNode ? childNode._ancestorSet.has(parentId) : false; }; const folderArr = Array.from(nodeMap.values()) .filter(f => !f.isShell && (f.files.length >= 2 || f.size > 1024 * 1024)) .map(f => { const counts = new Map(); f.files.forEach(h => counts.set(h, (counts.get(h) || 0) + 1)); return { ...f, fileCounts: counts, _keys: Array.from(counts.keys()), totalFiles: f.files.length }; }) .sort((a, b) => b.totalFiles - a.totalFiles); const invertedIndex = new Map(); folderArr.forEach((f, i) => { f.fileCounts.forEach((_, hash) => { let arr = invertedIndex.get(hash); if (!arr) { arr = []; invertedIndex.set(hash, arr); } arr.push(i); }); }); const totalDocs = folderArr.length; const weightMap = new Map(); invertedIndex.forEach((arr, hash) => { const df = arr.length; let w = 1.0; if (totalDocs >= 20 && (df / totalDocs) > 0.05) { w = 0.05; } weightMap.set(hash, w); }); folderArr.forEach(f => { let wt = 0; f.fileCounts.forEach((count, hash) => { wt += count * weightMap.get(hash); }); f.weightedTotal = wt; }); let groups =[]; const assigned = new Set(); const total = folderArr.length; const candidateSeen = new Uint32Array(total); let lastYieldTime = performance.now(); updateLoadTxt(`${L.str_analyzing}... 0%`); try { if (simAlgo === 'name') { const nameGroups = new Map(); const cleanFolderName = (oldName) => { let cleanName = oldName.replace(/[\r\n\v\f\u2028\u2029]+/g, ' ').trim(); cleanName = cleanName.replace(/^【[^】]+】 *[-_.]? */, ''); cleanName = cleanName.replace(/^[a-z0-9-]+[.](?:com|net|org|cc|xyz|vip|top|la) +/i, ''); const adKw = "(?:[.]com|[.]net|[.]org|[.]cc|[.]xyz|[.]vip|[.]top|[.]la|2048|www[.])"; const atRegex = new RegExp('^.*?' + adKw + '.*?(?:@|--+|_\\s)', 'i'); cleanName = cleanName.replace(atRegex, ''); const hyphenRegex = new RegExp('^[a-z0-9.-]+' + adKw + '-', 'i'); cleanName = cleanName.replace(hyphenRegex, ''); cleanName = cleanName.replace(/^(?:精品加群|福利合集)[0-9]+[-_]+ */, ''); cleanName = cleanName.replace(/^[-_. ,,::;;\p{Extended_Pictographic}]+/u, ''); const pairs = [['【','】'], ['[',']'], ['《','》'],['<','>'], ['(',')'],['(',')'], ['{','}']]; pairs.forEach(([L_char, R_char]) => { const idxR_Fix = cleanName.indexOf(R_char); const idxL_Check = cleanName.indexOf(L_char); if (idxR_Fix > 0 && idxR_Fix <= 10 && (idxL_Check === -1 || idxL_Check > idxR_Fix)) { cleanName = L_char + cleanName; } const chars = cleanName.split(''); const stack = []; const toRemove = new Set(); for (let i = 0; i < chars.length; i++) { const c = chars[i]; if (c === L_char) stack.push(i); else if (c === R_char) { if (stack.length > 0) stack.pop(); else toRemove.add(i); } } stack.forEach(i => toRemove.add(i)); if (toRemove.size > 0) cleanName = chars.filter((_, i) => !toRemove.has(i)).join(''); }); const quoteCount = (cleanName.match(/'/g) || []).length; if (quoteCount % 2 !== 0) cleanName = cleanName.replace(/'/, ''); cleanName = cleanName .replace(/\s*[-_ ..。]*\s*(?:\(\s*\d+\s*\)|(\s*\d+\s*)|\[\s*\d+\s*\]|【\s*\d+\s*】)\s*$/u, '') .replace(/\s*(?:[-_ ..。]*\s*)?(?:副本|复制|拷贝|拷貝|コピー|複製|복사본|사본|복사)\s*(?:\d+|\(\s*\d+\s*\)|(\s*\d+\s*)|\[\s*\d+\s*\]|【\s*\d+\s*】)?\s*$/u, '') .replace(/\s*[-_ ..。]+\s*(?:copy|duplicate|dup|salinan|salin|duplikat)\s*(?:\d+|\(\s*\d+\s*\)|(\s*\d+\s*)|\[\s*\d+\s*\]|【\s*\d+\s*】)?\s*$/i, '') .trim(); let finalResult = cleanName.toLowerCase().trim(); if (simThreshold <= 0.5) { finalResult = finalResult.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, ''); } return finalResult || oldName.toLowerCase().trim(); }; folderArr.forEach(f => { const k = cleanFolderName(f.name); if (!nameGroups.has(k)) nameGroups.set(k,[]); nameGroups.get(k).push(f); }); const sizeRatioLimit = simThreshold >= 0.5 ? 0.10 : Infinity; for (const[k, items] of nameGroups) { if (items.length > 1) { const sorted = [...items].sort((a,b) => Number(a.size) - Number(b.size)); let currentGroup = [sorted[0]]; for (let i = 1; i < sorted.length; i++) { const target = sorted[i]; const root = currentGroup[0]; const rootSize = Number(root.size || 0); const targetSize = Number(target.size || 0); let isMatch = false; if (rootSize === 0 && targetSize === 0) isMatch = true; else { const sizeDiff = Math.abs(targetSize - rootSize); const maxBase = Math.max(targetSize, rootSize); if (maxBase > 0 && (sizeDiff / maxBase) <= sizeRatioLimit) isMatch = true; } if (isMatch) currentGroup.push(target); else { if (currentGroup.length > 1) { const gNodes = currentGroup; let minS = Number.MAX_SAFE_INTEGER, maxS = 0; gNodes.forEach(n => { const sz = Number(n.size||0); if(szmaxS) maxS=sz; }); if (minS === Number.MAX_SAFE_INTEGER) minS = 0; const range = (minS === maxS) ? fmtSize(minS) : `${fmtSize(minS)} ~ ${fmtSize(maxS)}`; groups.push({ ids: gNodes.map(f => f.id), type: `${gNodes.length} ${L.str_items} | ${range}`, _sim: 1 }); gNodes.forEach(f => assigned.add(f.id)); } currentGroup = [target]; } } if (currentGroup.length > 1) { const gNodes = currentGroup; let minS = Number.MAX_SAFE_INTEGER, maxS = 0; gNodes.forEach(n => { const sz = Number(n.size||0); if(szmaxS) maxS=sz; }); if (minS === Number.MAX_SAFE_INTEGER) minS = 0; const range = (minS === maxS) ? fmtSize(minS) : `${fmtSize(minS)} ~ ${fmtSize(maxS)}`; groups.push({ ids: gNodes.map(f => f.id), type: `${gNodes.length} ${L.str_items} | ${range}`, _sim: 1 }); gNodes.forEach(f => assigned.add(f.id)); } } } } else { for (let i = 0; i < total; i++) { if (i % 50 === 0 || performance.now() - lastYieldTime > 16) { if (!isRunning) break; updateLoadTxt(`${L.str_analyzing}\n${Math.round((i / total) * 100)}%`); await sleep(0); lastYieldTime = performance.now(); } if (assigned.has(folderArr[i].id)) continue; const f1 = folderArr[i]; const group = [f1]; let groupMinSim = 1.0; const candidateIndices = []; const marker = i + 1; f1.fileCounts.forEach((_, hash) => { const foldersWithHash = invertedIndex.get(hash); if (foldersWithHash) { for (let k = 0, len = foldersWithHash.length; k < len; k++) { const idx = foldersWithHash[k]; if (idx > i && candidateSeen[idx] !== marker && !assigned.has(folderArr[idx].id)) { candidateSeen[idx] = marker; candidateIndices.push(idx); } } } }); for (let m = 0, cLen = candidateIndices.length; m < cLen; m++) { const j = candidateIndices[m]; const f2 = folderArr[j]; if (simAlgo === 'sim' && (isDescendant(f2.id, f1.id) || isDescendant(f1.id, f2.id))) continue; let total1 = f1.weightedTotal; let total2 = f2.weightedTotal; let intersect = 0; if (isDescendant(f2.id, f1.id)) { total1 -= total2; if (total1 <= 0 || total2 <= 0) continue; const maxS = total1 > total2 ? total1 : total2; const minS = total1 < total2 ? total1 : total2; if (simAlgo === 'sim' && minS / maxS < simThreshold) continue; for (let k = 0, len = f2._keys.length; k < len; k++) { const hash = f2._keys[k]; const c2 = f2.fileCounts.get(hash); const c1 = f1.fileCounts.get(hash) || 0; const diff = c1 - c2; if (diff > 0) intersect += (diff < c2 ? diff : c2) * weightMap.get(hash); } } else if (isDescendant(f1.id, f2.id)) { total2 -= total1; if (total1 <= 0 || total2 <= 0) continue; const maxS = total1 > total2 ? total1 : total2; const minS = total1 < total2 ? total1 : total2; if (simAlgo === 'sim' && minS / maxS < simThreshold) continue; for (let k = 0, len = f1._keys.length; k < len; k++) { const hash = f1._keys[k]; const c1 = f1.fileCounts.get(hash); const c2 = f2.fileCounts.get(hash) || 0; const diff = c2 - c1; if (diff > 0) intersect += (diff < c1 ? diff : c1) * weightMap.get(hash); } } else { if (total1 <= 0 || total2 <= 0) continue; const maxS = total1 > total2 ? total1 : total2; const minS = total1 < total2 ? total1 : total2; if (simAlgo === 'sim' && minS / maxS < simThreshold) continue; const fSmall = f1._keys.length < f2._keys.length ? f1 : f2; const fLarge = f1._keys.length < f2._keys.length ? f2 : f1; for (let k = 0, len = fSmall._keys.length; k < len; k++) { const hash = fSmall._keys[k]; const cLarge = fLarge.fileCounts.get(hash); if (cLarge !== undefined) { const cSmall = fSmall.fileCounts.get(hash); intersect += (cSmall < cLarge ? cSmall : cLarge) * weightMap.get(hash); } } } const minTotal = total1 < total2 ? total1 : total2; const union = total1 + total2 - intersect; const sim = simAlgo === 'contain' ? (minTotal > 0 ? (intersect / minTotal) : 0) : (union > 0 ? (intersect / union) : 0); if (sim >= simThreshold) { let isGroupQualified = true; let currentMinSim = sim; for (let gIdx = 1; gIdx < group.length; gIdx++) { const gMember = group[gIdx]; if (simAlgo === 'sim' && (isDescendant(f2.id, gMember.id) || isDescendant(gMember.id, f2.id))) { isGroupQualified = false; break; } let tA = gMember.weightedTotal; let tB = f2.weightedTotal; let intS = 0; if (isDescendant(f2.id, gMember.id)) { tA -= tB; } else if (isDescendant(gMember.id, f2.id)) { tB -= tA; } if (tA <= 0 || tB <= 0) { isGroupQualified = false; break; } const maxS = tA > tB ? tA : tB; const minS = tA < tB ? tA : tB; if (simAlgo === 'sim' && minS / maxS < simThreshold) { isGroupQualified = false; break; } if (isDescendant(f2.id, gMember.id)) { for (let k = 0, len = f2._keys.length; k < len; k++) { const h = f2._keys[k]; const cB = f2.fileCounts.get(h); const cA = gMember.fileCounts.get(h) || 0; const diff = cA - cB; if (diff > 0) intS += (diff < cB ? diff : cB) * weightMap.get(h); } } else if (isDescendant(gMember.id, f2.id)) { for (let k = 0, len = gMember._keys.length; k < len; k++) { const h = gMember._keys[k]; const cA = gMember.fileCounts.get(h); const cB = f2.fileCounts.get(h) || 0; const diff = cB - cA; if (diff > 0) intS += (diff < cA ? diff : cA) * weightMap.get(h); } } else { const fSmall = gMember._keys.length < f2._keys.length ? gMember : f2; const fLarge = gMember._keys.length < f2._keys.length ? f2 : gMember; for (let k = 0, len = fSmall._keys.length; k < len; k++) { const h = fSmall._keys[k]; const cLarge = fLarge.fileCounts.get(h); if (cLarge !== undefined) { const cSmall = fSmall.fileCounts.get(h); intS += (cSmall < cLarge ? cSmall : cLarge) * weightMap.get(h); } } } const minT = tA < tB ? tA : tB; const un = tA + tB - intS; const pairwiseSim = simAlgo === 'contain' ? (minT > 0 ? (intS / minT) : 0) : (un > 0 ? (intS / un) : 0); if (pairwiseSim < simThreshold) { isGroupQualified = false; break; } if (pairwiseSim < currentMinSim) { currentMinSim = pairwiseSim; } } if (isGroupQualified) { group.push(f2); if (currentMinSim < groupMinSim) groupMinSim = currentMinSim; } } } if (group.length > 1) { group.forEach(f => assigned.add(f.id)); groups.push({ ids: group.map(f => f.id), type: `${group.length} ${L.str_items} | ${simAlgo === 'contain' ? L.lbl_containment : L.lbl_sim_score}: ${Math.round(groupMinSim * 100)}%`, _sim: groupMinSim }); } } } if (simAlgo !== 'name') { const folderObjMap = new Map(); folderArr.forEach(f => folderObjMap.set(f.id, f)); const finalGroups =[]; groups.forEach(g => { const nodes = g.ids.map(id => folderObjMap.get(id)).filter(Boolean); const toRemove = new Set(); nodes.forEach(node => { const descendants = nodes.filter(n => n.id !== node.id && isDescendant(n.id, node.id)); if (descendants.length === 0) return; const hasExternal = nodes.some(n => n.id !== node.id && !isDescendant(n.id, node.id) && !isDescendant(node.id, n.id)); if (hasExternal) return; const topDescendants = descendants.filter(d1 => !descendants.some(d2 => d1.id !== d2.id && isDescendant(d1.id, d2.id)) ); let isCovered = true; let sumTotal = 0; const sumCounts = new Map(); topDescendants.forEach(td => { sumTotal += td.totalFiles; td.fileCounts.forEach((count, hash) => { sumCounts.set(hash, (sumCounts.get(hash) || 0) + count); }); }); if (sumTotal !== node.totalFiles) { isCovered = false; } else { for (const [hash, count] of node.fileCounts) { if (sumCounts.get(hash) !== count) { isCovered = false; break; } } } if (isCovered) { toRemove.add(node.id); } }); const finalIds = g.ids.filter(id => !toRemove.has(id)); if (finalIds.length >= 2) { finalGroups.push({ ids: finalIds, type: g.type, _sim: g._sim }); } }); groups = finalGroups; const partitionedGroups = []; const varianceTolerance = 0.15; groups.forEach(g => { const nodes = g.ids.map(id => folderObjMap.get(id)).filter(Boolean); if (nodes.length <= 2) { const pct = Math.round(g._sim * 100); g.type = `${nodes.length} ${L.str_items} | ${simAlgo === 'contain' ? L.lbl_containment : L.lbl_sim_score}: ${pct}%`; partitionedGroups.push(g); return; } const simMatrix = []; let maxSim = 0, minSim = 1; for (let i = 0; i < nodes.length; i++) { simMatrix[i] = []; for (let j = 0; j < nodes.length; j++) { if (i === j) { simMatrix[i][j] = 1; continue; } if (j < i) { simMatrix[i][j] = simMatrix[j][i]; continue; } const n1 = nodes[i], n2 = nodes[j]; if (simAlgo === 'sim' && (isDescendant(n2.id, n1.id) || isDescendant(n1.id, n2.id))) { simMatrix[i][j] = 0; minSim = 0; continue; } let tA = n1.weightedTotal, tB = n2.weightedTotal, intS = 0; if (isDescendant(n2.id, n1.id)) tA -= tB; else if (isDescendant(n1.id, n2.id)) tB -= tA; if (tA > 0 && tB > 0) { if (isDescendant(n2.id, n1.id)) { for (let k = 0; k < n2._keys.length; k++) { const h = n2._keys[k], cB = n2.fileCounts.get(h), cA = n1.fileCounts.get(h) || 0, diff = cA - cB; if (diff > 0) intS += (diff < cB ? diff : cB) * weightMap.get(h); } } else if (isDescendant(n1.id, n2.id)) { for (let k = 0; k < n1._keys.length; k++) { const h = n1._keys[k], cA = n1.fileCounts.get(h), cB = n2.fileCounts.get(h) || 0, diff = cB - cA; if (diff > 0) intS += (diff < cA ? diff : cA) * weightMap.get(h); } } else { const fSmall = n1._keys.length < n2._keys.length ? n1 : n2, fLarge = n1._keys.length < n2._keys.length ? n2 : n1; for (let k = 0; k < fSmall._keys.length; k++) { const h = fSmall._keys[k], cLarge = fLarge.fileCounts.get(h); if (cLarge !== undefined) intS += (fSmall.fileCounts.get(h) < cLarge ? fSmall.fileCounts.get(h) : cLarge) * weightMap.get(h); } } const minT = tA < tB ? tA : tB; const un = tA + tB - intS; const s = simAlgo === 'contain' ? (minT > 0 ? (intS / minT) : 0) : (un > 0 ? (intS / un) : 0); simMatrix[i][j] = s; if (s > maxSim) maxSim = s; if (s < minSim) minSim = s; } else { simMatrix[i][j] = 0; minSim = 0; } } } if (maxSim - minSim <= varianceTolerance) { const minPct = Math.round(minSim * 100); const maxPct = Math.round(maxSim * 100); const range = (minPct === maxPct) ? `${minPct}%` : `${minPct}% ~ ${maxPct}%`; g.type = `${nodes.length} ${L.str_items} | ${simAlgo === 'contain' ? L.lbl_containment : L.lbl_sim_score}: ${range}`; partitionedGroups.push(g); } else { const unassigned = new Set(nodes.map((_, idx) => idx)); const pairs =[]; for (let i = 0; i < nodes.length; i++) { for (let j = i + 1; j < nodes.length; j++) pairs.push({i, j, s: simMatrix[i][j]}); } pairs.sort((a, b) => b.s - a.s); pairs.forEach(pair => { if (unassigned.has(pair.i) && unassigned.has(pair.j) && pair.s >= simThreshold) { const sg = [pair.i, pair.j]; let sgMinSim = pair.s; unassigned.delete(pair.i); unassigned.delete(pair.j); for (const u of Array.from(unassigned)) { let canAdd = true, localMin = 1; for (const mem of sg) { const s = simMatrix[u < mem ? u : mem][u > mem ? u : mem]; if (pair.s - s > varianceTolerance || s < simThreshold) { canAdd = false; break; } if (s < localMin) localMin = s; } if (canAdd) { sg.push(u); unassigned.delete(u); if (localMin < sgMinSim) sgMinSim = localMin; } } if (sg.length >= 2) { let subMin = 1, subMax = 0; for(let x=0; x sg[y] ? sg[x] : sg[y]; const sVal = simMatrix[idx1][idx2]; if (sVal < subMin) subMin = sVal; if (sVal > subMax) subMax = sVal; } } const minPct = Math.round(subMin * 100); const maxPct = Math.round(subMax * 100); const range = (minPct === maxPct) ? `${minPct}%` : `${minPct}% ~ ${maxPct}%`; partitionedGroups.push({ ids: sg.map(idx => nodes[idx].id), type: `${sg.length} ${L.str_items} | ${simAlgo === 'contain' ? L.lbl_containment : L.lbl_sim_score}: ${range}`, _sim: subMin }); } } }); } }); groups = partitionedGroups; } } catch (e) { console.error("[SimMode] Error:", e); } if (groups.length === 0) { setLoad(false); showToast(L.msg_dup_none); return; } groups.sort((a, b) => (b._sim - a._sim) || (b.ids.length - a.ids.length)); S.analyzeSimGroups = groups; clearGroupedGridMetaCache(); clearGridStableRows(UI.in); viewItems = Array.from(assigned).map(id => { const node = nodeMap.get(id); return { id: node.id, kind: 'drive#folder', name: node.name, icon_link: node.icon_link, starred: node.starred, tags: node.tags, size: node.size.toString(), _pathStr: (node._pathStr && node._pathStr.includes('/')) ? node._pathStr.substring(0, node._pathStr.lastIndexOf('/')) : L.btn_nav_home, _lineage: node.lineage, modified_time: new Date(getServerNow()).toISOString(), parent_id: node.parentId }; }); } else { updateLoadTxt(L.str_rendering); const uniqueResults = Array.from(new Set(largeFolders)); const kw = gmGet('pk_analyze_last_keyword', ''); const kwList = kw ? kw.toLowerCase().split(/[,,]/).map(k => k.trim()).filter(k => k) : []; let filteredResults = uniqueResults.filter(n => n.size >= minBytes && (maxBytes === 0 || n.size <= maxBytes)); if (kwList.length > 0) { filteredResults = filteredResults.filter(node => !kwList.some(k => (node.name || "").toLowerCase().includes(k))); } filteredResults.sort((a, b) => b.size - a.size); if (filteredResults.length === 0) { setLoad(false); let rangeStr = maxBytes > 0 ? `${fmtSize(minBytes)} - ${fmtSize(maxBytes)}` : `≥ ${fmtSize(minBytes)}`; showToast(L.msg_analyze_no_large_folders.replace('{s}', rangeStr)); return; } viewItems = filteredResults.map(node => ({ id: node.id, kind: 'drive#folder', name: node.name, icon_link: node.icon_link, starred: node.starred, tags: node.tags, size: node.size.toString(), _pathStr: (node._pathStr && node._pathStr.includes('/')) ? node._pathStr.substring(0, node._pathStr.lastIndexOf('/')) : L.btn_nav_home, _lineage: node.lineage, modified_time: new Date(getServerNow()).toISOString(), parent_id: node.parentId })); } rememberFolderFirstBeforeStrictMode(); S.analyzeMode = true; S.hasShownAnaWarn = false; S.isFlattened = false; S.dupMode = false; S.analyzeResultItems = [...viewItems]; S.analyzeMap = nodeMap; S.path = [{ id: 'analyze_root', name: L.str_analyze_results }]; renderCrumb(); S.items = viewItems; S.itemMap.clear(); S.items.forEach(i => S.itemMap.set(i.id, i)); S.sel.clear(); UI.scan.style.display = 'none'; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'none'; UI.btnExit.style.display = 'flex'; if (UI.dupTools) UI.dupTools.style.display = 'none'; if (UI.dupFilters) UI.dupFilters.style.display = 'none'; if (UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if (UI.chkGlobal) UI.chkGlobal.checked = false; if (typeof applyResolvedViewMode === 'function') applyResolvedViewMode(S.analyzeSimGroups ? 'folder_dup' : 'folder_insight'); if (typeof applyResolvedSortState === 'function') applyResolvedSortState(); refresh(); if (S._enterTopRaf) cancelAnimationFrame(S._enterTopRaf); S._enterTopRaf = requestAnimationFrame(() => { S._enterTopRaf = 0; if (UI.vp && UI.vp.scrollTop !== 0) UI.vp.scrollTop = 0; }); updateStat(); setTimeout(() => { setLoad(false); isGUISensitive = false; }, 200); } }; } if (UI.btnExport) { UI.btnExport.onclick = async () => { if (S.trashMode) return; const curFolderId = S.path[S.path.length - 1].id || ''; if (isPathBusy(curFolderId)) { showAlert(L.msg_op_blocked_moving); return; } const format = await new Promise((resolve) => { const m = showModal(`

${L.title_export_format}

${L.lbl_export_current}
${L.opt_tree_view}
Root\n├─ Folder 1\n│ ├─ Folder 1-1\n│ └─ Folder 1-2\n└─ Folder 2\n └─ Folder 2-1
${L.opt_list_view}
Root/Folder 1\nRoot/Folder 1/Folder 1-1\nRoot/Folder 1/Folder 1-2\nRoot/Folder 2\nRoot/Folder 2/Folder 2-1
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { modalBox.style.width = '520px'; modalBox.style.height = 'auto'; modalBox.style.minHeight = 'auto'; const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '22px', right: '22px' }); } let selectedFormat = 'tree'; m.querySelectorAll('.pk-exp-card').forEach(card => { card.onclick = () => { m.querySelectorAll('.pk-exp-card').forEach(c => { c.style.borderColor = 'var(--pk-bd)'; c.querySelector('.pk-exp-title').style.color = 'var(--pk-fg)'; c.querySelector('.pk-exp-check').style.display = 'none'; c.classList.remove('act'); }); card.style.borderColor = 'var(--pk-pri)'; card.querySelector('.pk-exp-title').style.color = 'var(--pk-pri)'; card.querySelector('.pk-exp-check').style.display = 'flex'; card.classList.add('act'); selectedFormat = card.dataset.fmt; }; }); m.querySelector('#exp_cancel').onclick = () => { m.remove(); resolve(null); }; m.querySelector('.pk-modal-close').onclick = () => { m.remove(); resolve(null); }; m.querySelector('#exp_confirm').onclick = () => { m.remove(); resolve(selectedFormat); }; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#exp_confirm').click(); } }); }); if (!format) return; setLoad(true); S.scanning = true; S.scanId = (S.scanId || 0) + 1; const myScanId = S.scanId; if (S.scanAbortController) S.scanAbortController.abort(); S.scanAbortController = new AbortController(); const signal = S.scanAbortController.signal; let isRunning = true; UI.stopBtn.onclick = () => { isRunning = false; S.scanning = false; if (S.scanAbortController) S.scanAbortController.abort(); updateLoadTxt(L.str_stopping); }; const rootNode = S.path[S.path.length - 1]; const startNodes =[{ id: rootNode.id || '', name: rootNode.name || L.btn_nav_home, lineage: [], retryCount: 0 }]; const itemTree = new Map(); try { await coreRecursiveEngine(startNodes, { signal: signal, onFolder: (folder, filesInFolder) => { itemTree.set(folder.id, [...filesInFolder]); if (typeof globalCache !== 'undefined' && !globalCache.has(folder.id)) { globalCache.set(folder.id, [...filesInFolder]); } indexParents(folder.id, folder.name, filesInFolder); }, onProgress: (st) => { updateLoadTxt(`${L.msg_exporting}\n${L.str_folders}: ${st.folders} | ${L.str_files}: ${st.files}`); } }); if (!isRunning || signal.aborted || myScanId !== S.scanId) return; updateLoadTxt(L.str_processing); await sleep(50); const rootName = startNodes[0].name; let outputLines =[]; const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); const sortItems = (items) => { return items.sort((a, b) => { if (a.kind !== b.kind) return a.kind === 'drive#folder' ? -1 : 1; return collator.compare(a.name, b.name); }); }; if (format === 'tree') { outputLines.push(rootName); const buildTree = (parentId, prefix) => { const children = itemTree.get(parentId); if (!children || children.length === 0) return; const sorted = sortItems(children); for (let i = 0; i < sorted.length; i++) { const child = sorted[i]; const isLast = (i === sorted.length - 1); const connector = isLast ? '└─ ' : '├─ '; outputLines.push(prefix + connector + child.name); if (child.kind === 'drive#folder') { const childPrefix = prefix + (isLast ? ' ' : '│ '); buildTree(child.id, childPrefix); } } }; buildTree(startNodes[0].id, ''); } else { const buildList = (parentId, currentPath) => { const children = itemTree.get(parentId); if (!children || children.length === 0) return; const sorted = sortItems(children); for (let i = 0; i < sorted.length; i++) { const child = sorted[i]; const fullPath = currentPath ? currentPath + '/' + child.name : child.name; outputLines.push(fullPath); if (child.kind === 'drive#folder') { buildList(child.id, fullPath); } } }; buildList(startNodes[0].id, rootName); } const outputText = outputLines.join('\r\n'); const blob = new Blob([outputText], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const safeRootName = rootName.replace(/[\\/:*?"<>|]/g, '_'); const a = document.createElement('a'); a.href = url; a.download = `${safeRootName}_${format}.txt`; a.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); } catch (e) { if (e.name !== 'AbortError' && myScanId === S.scanId) { showAlert(`${L.str_error}: ${e.message}`); } } finally { if (myScanId === S.scanId) { setLoad(false); S.scanning = false; S.scanAbortController = null; isGUISensitive = false; } } }; } UI.btnNewFolder.onclick = async () => { const name = await showPrompt(L.msg_newfolder_prompt, ''); if (!name) return; isGUISensitive = true; const cur = S.path[S.path.length - 1]; const cacheKey = cur.id || 'root'; try { await fetch('https://api-drive.mypikpak.com/drive/v1/files', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ kind: 'drive#folder', parent_id: cur.id || '', name: name }) }); if (cur.id) gmSet('pk_fmod_' + cur.id, new Date(getServerNow()).toISOString()); await sleep(300); S.cache.delete(cacheKey); if (typeof globalCache !== 'undefined') globalCache.delete(cacheKey); if (typeof globalDirtyFolders !== 'undefined') { globalDirtyFolders.add(cacheKey === 'root' ? '' : cacheKey); } if (typeof globalNeedsSync !== 'undefined') globalNeedsSync = true; if (cacheKey !== 'root') { scannedFolderIds.delete(cacheKey); } if (typeof runBackgroundCrawler === 'function') runBackgroundCrawler(); load(false, true); } catch (e) { showAlert(`${L.str_error}: ${e.message}`); } finally { isGUISensitive = false; } }; UI.btnCopy.onclick = async () => { const ids = S.getSelectedIds(); const totalSelected = ids.length; if (totalSelected === 0) return; if (totalSelected > 10000) { showToast(L.str_copying); await sleep(10); } const itemList = []; let count = 0; for (const id of ids) { const item = S.itemMap.get(id); if (item) itemList.push(item); count++; if (count % 10000 === 0) await sleep(0); } S.clipItems = itemList; S.clipType = 'copy'; const curId = S.path[S.path.length - 1].id || ''; S.clipSourceParentId = S.isFlattened ? '__VIRTUAL__' : curId; setLoad(false); UI.btnPaste.disabled = false; showToast(L.msg_copy_done); }; UI.btnCut.onclick = async () => { const ids = S.getSelectedIds(); if (ids.length === 0) return; const itemList = []; let count = 0; for (const id of ids) { const item = S.itemMap.get(id); if (!S.trashMode && isSystemItem(item)) { continue; } itemList.push(item); count++; if (count % 10000 === 0) await sleep(0); } if (itemList.length === 0) return; if (itemList.length > 10000) { showToast(L.str_moving); await sleep(10); } S.clipItems = itemList; S.clipType = 'move'; const curId = S.path[S.path.length - 1].id || ''; S.clipSourceParentId = S.isFlattened ? '__VIRTUAL__' : curId; setLoad(false); UI.btnPaste.disabled = false; showToast(L.msg_cut_done); }; UI.btnPaste.onclick = async () => { if (!S.clipItems || S.clipItems.length === 0) { showToast(L.msg_paste_empty, 'error'); return; } if (S.movingIds && S.movingIds.size > 0) { showAlert(L.msg_op_blocked_moving); return; } const items = S.clipItems; const type = S.clipType; const srcId = S.clipSourceParentId; const destId = S.path[S.path.length - 1].id || ''; const normalize = (id) => (!id || id === 'root') ? 'root' : id; S.clipItems = []; S.clipType = ''; updateStat(); const targetFolderName = S.path[S.path.length - 1].name || L.str_target_folder; await executeFileTransfer(items, type, normalize(srcId), normalize(destId), targetFolderName); }; UI.btnRename.onclick = async () => { if (S.getSelectedCount() !== 1) return; const id = S.getSelectedIds()[0]; const item = S.itemMap.get(id); if (!item || (!S.trashMode && isSystemItem(item))) return; const newName = await showPrompt(L.msg_rename_prompt, item.name, L.modal_rename_title); if (!newName || newName === item.name) return; let progressTask = null; try { progressTask = FloatBarManager.create(L.str_renaming); await apiAction(`/${id}`, { name: newName }); item.name = newName; const nowIso = new Date(getServerNow()).toISOString(); item.modified_time = nowIso; if (item.kind === 'drive#folder') gmSet('pk_fmod_' + item.id, nowIso); const parentIdForFmod = item.parent_id || 'root'; gmSet('pk_fmod_' + parentIdForFmod, nowIso); const row = UI.in.querySelector(`.pk-row[data-id="${id}"]`); if (row && row.lastElementChild) { row.lastElementChild.textContent = fmtDate(nowIso); row.lastElementChild.style.transition = 'color 0.3s'; row.lastElementChild.style.color = 'var(--pk-pri)'; setTimeout(() => { if(row.lastElementChild) row.lastElementChild.style.color = ''; }, 2000); } const pid = item.parent_id || 'root'; if (typeof globalCache !== 'undefined') { if (globalCache.has(pid)) { const list = globalCache.get(pid); const target = list.find(f => f.id === id); if (target) target.name = newName; } globalDirtyFolders.add(pid === 'root' ? '' : pid); if (typeof runBackgroundCrawler === 'function') runBackgroundCrawler(); } if (S.lastGlobalResults && S.lastGlobalResults.length > 0) { const globalTarget = S.lastGlobalResults.find(f => f.id === id); if (globalTarget) globalTarget.name = newName; } if (S.analyzeResultItems) { const anaItem = S.analyzeResultItems.find(x => x.id === id); if (anaItem) anaItem.name = newName; } if (S.analyzeMap && S.analyzeMap.has(id)) { S.analyzeMap.get(id).name = newName; } if (S.dupMode) renderDupView(); else refresh(); } catch (e) { showAlert(`${L.str_error}: ${e.message}`); } finally { if (progressTask) progressTask.destroy(); } }; UI.btnBulkRename.onclick = () => { const selectedIds = S.getSelectedIds(); const selectedCount = selectedIds.length; if (selectedCount < 2) return; if (S.movingIds && S.movingIds.size > 0) { const hasConflict = selectedIds.some(id => S.movingIds.has(id)); if (hasConflict) { showAlert(L.msg_op_blocked_moving); return; } } const getSmartDisplayHTML = (fullStr, hlStr) => { if (!hlStr || hlStr.indexOf('§§§MATCH_START§§§') === -1) { return esc(fullStr); } const parts = hlStr.split(/(§§§MATCH_START§§§.*?§§§MATCH_END§§§)/g); let result = ""; const PRE_CTX = 12; const SUF_CTX = 80; parts.forEach((part, index) => { if (part.startsWith('§§§MATCH_START§§§')) { const content = part.replace(/§§§MATCH_START§§§|§§§MATCH_END§§§/g, ''); result += `${esc(content)}`; } else { let text = part; if (index === 0) { if (text.length > PRE_CTX + 3) { text = "..." + text.slice(-PRE_CTX); } } else if (index === parts.length - 1) { if (text.length > SUF_CTX) { text = text.slice(0, SUF_CTX) + "..."; } } else { if (text.length > 20) { text = text.slice(0, 8) + ".." + text.slice(-8); } } result += esc(text); } }); return result; }; const getSmartTipHTML = (fullStr, hlStr) => { if (!hlStr || hlStr.indexOf('§§§MATCH_START§§§') === -1) return esc(fullStr); return hlStr.split(/(§§§MATCH_START§§§.*?§§§MATCH_END§§§)/g).map(part => { if (part.startsWith('§§§MATCH_START§§§')) { const content = part.replace(/§§§MATCH_START§§§|§§§MATCH_END§§§/g, ''); return `${esc(content)}`; } return esc(part); }).join(''); }; const extractKeyword = (fileName) => { const fc2Regex = /(?:FC2|FC)(?:[-_. ]*PPV)?[-_. @]*(\d{5,8})(?:[-_. ]*(?:part|pt|cd)?[-_. ]?(\d{1,2}|[a-e]))?(?![a-z\d])/i; const fc2Match = fileName.match(fc2Regex); if (fc2Match) { const id = fc2Match[1]; const part = fc2Match[2]; return (part && part.trim()) ? `FC2-PPV-${id}-${part.toUpperCase()}` : `FC2-PPV-${id}`; } return null; }; const inputStyle = ` width:100%; height:44px; padding:0 15px; border:2px solid var(--pk-bd); border-radius:8px; background:var(--pk-bg); color:var(--pk-fg); font-size:14px; font-weight:600; outline:none; transition:border-color 0.2s; box-sizing:border-box; `; const labelStyle = ` position:absolute; top:0; transform:translateY(-50%); left:10px; background:var(--pk-bg); padding:0 5px; line-height:1; font-size:11px; color:var(--pk-pri); font-weight:bold; pointer-events:none; z-index:1; `; const m = showModal(`

${L.modal_rename_multi_title}

${L.rn_stat.replace('{n}', selectedCount).replace('{m}', '0')}
${L.label_replace_find}
${L.label_replace_to}
${L.col_type}
${L.col_old}
${L.col_new}
${L.rn_tip_wait}
${L.lbl_rn_preview_title}
`); const modalBox = m.querySelector('.pk-modal'); Object.assign(modalBox.style, { width: '800px', maxWidth: '95vw', padding: '30px', borderRadius: '12px' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '26px', right: '26px' }); const radios = m.querySelectorAll('input[name="rn_mode"]'); const inpPattern = m.querySelector('#rn_pattern'); const inpFind = m.querySelector('#rn_find'); const inpRep = m.querySelector('#rn_rep'); const chkRegex = m.querySelector('#rn_regex'); const chkCase = m.querySelector('#rn_case_sense'); const chkIncludeExt = m.querySelector('#rn_include_ext'); const btnApply = m.querySelector('#rn_apply'); const rnVp = m.querySelector('#pk-rn-vp'); const rnIn = m.querySelector('#pk-rn-in'); const txtStatNum = m.querySelector('#rn_stat_num'); const bindFocus = (el) => { el.onfocus = () => el.style.borderColor = 'var(--pk-pri)'; el.onblur = () => el.style.borderColor = 'var(--pk-bd)'; }; [inpPattern, inpFind, inpRep].forEach(bindFocus); const updateInputs = () => { const mode = m.querySelector('input[name="rn_mode"]:checked').value; const groups = { 'replace': m.querySelector('#group_replace'), 'pattern': m.querySelector('#group_pattern'), 'jav': m.querySelector('#group_jav'), 'format': m.querySelector('#group_format'), 'ad_remove': m.querySelector('#group_ad_remove'), 'ext_fix': m.querySelector('#group_ext_fix') }; Object.keys(groups).forEach(k => { if (groups[k]) { let displayStyle = 'block'; if (k === 'replace' || k === 'format') displayStyle = 'flex'; groups[k].style.display = (k === mode) ? displayStyle : 'none'; } }); plannedChanges = []; rnDisplay = []; previewSelectedIds.clear(); const initialTip = (mode === 'jav') ? L.rn_tip_jav : L.rn_tip_wait; rnIn.innerHTML = `
${initialTip}
`; txtStatNum.innerText = "0"; btnApply.disabled = true; const cbWrapper = m.querySelector('#rn_cb_wrapper'); const cbAll = m.querySelector('#rn_cb_all'); if (cbWrapper) cbWrapper.style.visibility = 'hidden'; if (cbAll) { cbAll.checked = false; cbAll.indeterminate = false; } generatePreview(); }; radios.forEach(r => r.onchange = updateInputs); const setupInputHistory = (inputEl, storageKey) => { const pop = document.createElement('div'); pop.className = 'pk-hist-pop'; pop.style.cssText = "position:absolute; top:calc(100% + 5px); left:0; right:0; z-index:9999; display:none; flex-direction:column;"; inputEl.parentNode.appendChild(pop); const loadHist = () => { try { return JSON.parse(gmGet(storageKey, '[]')); } catch { return []; } }; const saveHist = (val) => { if (val === null || val === undefined) return; let list = loadHist(); list = list.filter(x => x !== val); list.unshift(val); if (list.length > 3) list = list.slice(0, 3); gmSet(storageKey, JSON.stringify(list)); }; const render = () => { const list = loadHist(); if (list.length === 0) { pop.style.display = 'none'; return; } document.querySelectorAll('.pk-hist-pop').forEach(p => p.style.display = 'none'); let html = `
${L.title_search_hist}${L.btn_clear_hist}
`; list.forEach(txt => { const displayTxt = txt === "" ? L.str_empty_str : (txt.replace(/\s/g, '') === "" ? `"${txt}"` : txt); html += `
${esc(displayTxt)}
`; }); pop.innerHTML = html; pop.style.display = 'flex'; pop.querySelector('#pk-hist-del').onclick = (e) => { e.stopPropagation(); gmSet(storageKey, '[]'); pop.style.display = 'none'; }; pop.querySelectorAll('.pk-select-item').forEach(el => { el.onclick = (e) => { e.stopPropagation(); const val = el.getAttribute('data-raw'); inputEl.value = val; pop.style.display = 'none'; generatePreview(); }; }); }; inputEl.addEventListener('focus', render); inputEl.addEventListener('click', (e) => { e.stopPropagation(); render(); }); const closeHandler = (e) => { if (!inputEl || !pop || !pop.parentNode) return; if (!inputEl.contains(e.target) && !pop.contains(e.target)) pop.style.display = 'none'; }; setTimeout(() => document.addEventListener('click', closeHandler), 0); return { save: saveHist }; }; const findHist = setupInputHistory(inpFind, 'pk_bn_find_hist'); const repHist = setupInputHistory(inpRep, 'pk_bn_rep_hist'); const RN_ROW_HEIGHT = 40; const RN_BUFFER = 15; let rnDisplay = []; let plannedChanges = []; let previewSelectedIds = new Set(); let isRnScrollScheduled = false; let currentPreviewId = 0; const VALID_EXTS_LIST = [ 'mp4', 'mkv', 'avi', 'mov', 'wmv', 'm4v', 'flv', '3gp', 'webm', 'ts', 'm2ts', 'mts', 'vob', 'mpg', 'mpeg', 'rm', 'rmvb', 'asf', 'divx', 'f4v', 'ogv', 'm2v', 'mpe', 'mp3', 'aac', 'flac', 'wav', 'ogg', 'm4a', 'wma', 'opus', 'ape', 'alac', 'aiff', 'mid', 'midi', 'amr', 'mka', 'dts', 'ac3', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'tif', 'tiff', 'heic', 'raw', 'ico', 'psd', 'ai', 'eps', 'cdr', 'srt', 'ass', 'ssa', 'vtt', 'smi', 'sub', 'idx', 'sup', 'lrc', 'zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'iso', 'img', 'dmg', 'pkg', 'apk', 'ipa', 'exe', 'msi', 'torrent', 'nzb', 'pdf', 'txt', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'epub', 'mobi', 'azw3', 'cbz', 'cbr', 'md', 'rtf', 'csv', 'log', 'ini', 'cfg', 'html', 'htm', 'css', 'js', 'json', 'xml', 'php', 'py', 'java', 'c', 'cpp' ]; const VALID_EXTS = new Set(VALID_EXTS_LIST); const recalcPatternNames = () => { const mode = m.querySelector('input[name="rn_mode"]:checked').value; if (mode !== 'pattern') return; const pattern = inpPattern.value; let counter = 1; rnDisplay.forEach(item => { const originalItem = S.itemMap.get(item.id); const isFolder = originalItem?.kind === 'drive#folder'; const isSelected = previewSelectedIds.has(item.id); if (isSelected && !isFolder) { let ext = ''; const oldName = item.old; const lastDotIndex = oldName.lastIndexOf('.'); if (lastDotIndex > 0) { const potentialExt = oldName.substring(lastDotIndex + 1).toLowerCase(); if (VALID_EXTS.has(potentialExt)) ext = '.' + potentialExt; } const newName = pattern.replace(/{n}/g, String(counter).padStart(2, '0')) + ext; item.new = newName; item.newNameHTML = `${esc(newName)}`; counter++; } else { item.new = item.old; item.newNameHTML = `${esc(item.old)}`; } }); }; const updateHeaderCheckbox = () => { const cbAll = m.querySelector('#rn_cb_all'); if (!cbAll) return; const total = plannedChanges.length; const count = previewSelectedIds.size; cbAll.checked = total > 0 && count === total; cbAll.indeterminate = count > 0 && count < total; const validCount = rnDisplay.filter(c => c.new !== c.old && previewSelectedIds.has(c.id)).length; btnApply.disabled = validCount === 0; txtStatNum.style.display = 'inline'; txtStatNum.innerHTML = `${validCount}/${total}`; }; const renderRNVisible = () => { const top = rnVp.scrollTop; const h = rnVp.clientHeight; const totalHeight = rnDisplay.length * RN_ROW_HEIGHT; rnIn.style.height = `${totalHeight}px`; const start = Math.max(0, Math.floor(top / RN_ROW_HEIGHT) - RN_BUFFER); const end = Math.min(rnDisplay.length, Math.ceil((top + h) / RN_ROW_HEIGHT) + RN_BUFFER); rnIn.innerHTML = ''; const fragment = document.createDocumentFragment(); const rowStyle = ` display: flex; align-items: center; height: ${RN_ROW_HEIGHT}px; padding-right: 20px; border-bottom: 1px dashed #f0f0f0; box-sizing: border-box; font-size: 13px; color: var(--pk-fg); `; for (let i = start; i < end; i++) { const c = rnDisplay[i]; if (!c) continue; const row = document.createElement('div'); row.style.cssText = `position:absolute; top:${i * RN_ROW_HEIGHT}px; width:100%;` + rowStyle; let finalDisplayHTML = esc(c.old); if (c.hl_old) { finalDisplayHTML = getSmartDisplayHTML(c.old, c.hl_old); } const isChecked = previewSelectedIds.has(c.id); const it = S.itemMap.get(c.id); let iconHtml = ''; let thumbAttr = ''; if (it) { const isFolder = it.kind === 'drive#folder'; const mime = (it.mime_type || '').toLowerCase(); const isMedia = mime.startsWith('video/') || mime.startsWith('image/'); const hasCover = it.thumbnail_link && it.thumbnail_link !== it.icon_link; const usableCover = hasCover && !isPreviewIconFailed(it.thumbnail_link); const fallbackSvg = getIcon(it).replace(/width="\d+"/, 'width="24"').replace(/height="\d+"/, 'height="24"'); const makePreviewImg = (src, fit, radius = '0') => { const safeSrc = String(src || '').replace(/"/g, '"'); return ``; }; if (usableCover) { thumbAttr = `data-pk-thumb="${it.thumbnail_link}"`; } if (!isFolder && isMedia && usableCover) { iconHtml = makePreviewImg(it.thumbnail_link, 'cover', '4px'); const secondFallback = it.icon_link && !isPreviewIconFailed(it.icon_link) ? `${makePreviewImg(it.icon_link, 'contain')}${fallbackSvg}` : fallbackSvg; iconHtml += `${secondFallback}`; } else { const iconSrc = it.icon_link; iconHtml = iconSrc && !isPreviewIconFailed(iconSrc) ? `${makePreviewImg(iconSrc, 'contain')}${fallbackSvg}` : fallbackSvg; } } const oldTipHTML = c.hl_old ? getSmartTipHTML(c.old, c.hl_old) : esc(c.old); row.innerHTML = `
${iconHtml}
${finalDisplayHTML}
${c.newNameHTML}
${c.conflict ? `
${L.str_name_conflict}
` : ''}
`; bindPreviewIconFallback(row); const toggleRow = () => { const targetId = c.id; if (previewSelectedIds.has(targetId)) previewSelectedIds.delete(targetId); else previewSelectedIds.add(targetId); recalcPatternNames(); renderRNVisible(); updateHeaderCheckbox(); }; row.onclick = () => { toggleRow(); }; fragment.appendChild(row); } rnIn.appendChild(fragment); }; rnVp.onscroll = () => { if (!isRnScrollScheduled) { requestAnimationFrame(() => { renderRNVisible(); isRnScrollScheduled = false; }); isRnScrollScheduled = true; } }; const handlePreviewResults = (changes, error) => { if (error) { rnIn.innerHTML = `
❌ ${L.err_worker}: ${esc(error)}
`; return; } plannedChanges = changes; previewSelectedIds = new Set(changes.map(c => c.id)); rnDisplay = changes.map(c => { let newH = esc(c.new); if (c.new !== c.old) { newH = `${newH}`; } return { id: c.id, old: c.old, new: c.new, hl_old: c.hl_old, newNameHTML: newH, conflict: c.conflict }; }); recalcPatternNames(); const cbWrapper = m.querySelector('#rn_cb_wrapper'); const cbAll = m.querySelector('#rn_cb_all'); if (rnDisplay.length === 0) { const mode = m.querySelector('input[name="rn_mode"]:checked').value; const selectedIds = S.getSelectedIds(); const selectedIdSet = new Set(selectedIds); const selectedItems = S.display.filter(i => !i.isHeader && selectedIdSet.has(i.id)); const isSeriesFolderOnly = mode === 'pattern' && selectedItems.length > 0 && selectedItems.every(i => i.kind === 'drive#folder'); rnIn.innerHTML = `
${isSeriesFolderOnly ? L.rn_tip_series_folder_only : L.rn_tip_none}
`; txtStatNum.innerText = "0"; btnApply.disabled = true; if (cbWrapper) cbWrapper.style.visibility = 'hidden'; if (cbAll) { cbAll.checked = false; cbAll.disabled = true; } } else { rnVp.scrollTop = 0; renderRNVisible(); updateHeaderCheckbox(); if (cbWrapper) cbWrapper.style.visibility = 'visible'; if (cbAll) cbAll.disabled = false; btnApply.disabled = false; } }; const javState = { isRunning: false, runId: 0, signature: '', cache: new Map(), completed: 0, total: 0, ui: null, activePlannedMap: new Map(), activeNames: new Set() }; const generatePreview = async () => { const myPreviewId = ++currentPreviewId; const mode = m.querySelector('input[name="rn_mode"]:checked').value; rnDisplay = []; plannedChanges = []; rnIn.innerHTML = ''; const pattern = inpPattern.value; const findStr = inpFind.value; const repStr = inpRep.value || ''; const useRegex = chkRegex.checked; const useIncludeExt = chkIncludeExt.checked; const caseMode = selectedCase; const widthMode = selectedWidth; const useCaseSense = m.querySelector('#rn_case_sense').checked; let isRuleActive = true; if (mode === 'replace') isRuleActive = !!findStr; else if (mode === 'format') isRuleActive = !!(caseMode || widthMode); else if (mode === 'pattern') isRuleActive = !!pattern; if (!isRuleActive && mode !== 'jav' && mode !== 'ad_remove' && mode !== 'ext_fix') { plannedChanges = []; rnDisplay = []; previewSelectedIds.clear(); rnIn.innerHTML = `
${L.rn_tip_wait}
`; txtStatNum.innerText = "0"; btnApply.disabled = true; const cbWrapper = m.querySelector('#rn_cb_wrapper'); if (cbWrapper) cbWrapper.style.visibility = 'hidden'; return; } if (mode === 'replace') { if (findStr) findHist.save(findStr); if (repStr) repHist.save(repStr); } const cbWrapper = m.querySelector('#rn_cb_wrapper'); if (cbWrapper) cbWrapper.style.visibility = 'hidden'; rnIn.innerHTML = `
${L.str_calc_changes}
`; btnApply.disabled = true; txtStatNum.innerText = "..."; plannedChanges = []; rnDisplay = []; previewSelectedIds = new Set(); const cbAll = m.querySelector('#rn_cb_all'); if(cbAll) { cbAll.checked = false; cbAll.indeterminate = false; } const selectedIds = S.getSelectedIds(); const selectedIdSet = new Set(selectedIds); const items = S.display.filter(i => !i.isHeader); if (false) { const targetIds =[]; for (let i = 0; i < items.length; i++) { const item = items[i]; const id = item.id; if (!selectedIdSet.has(id)) continue; if (isSystemItem(item)) continue; const isFolder = item.kind === 'drive#folder'; const mime = (item.mime_type || '').toLowerCase(); const duration = (item.params && item.params.duration) || 0; const isVideo = !isFolder && (mime.startsWith('video/') || duration > 0); if ((isFolder || isVideo) && extractKeyword(item.name)) { targetIds.push(id); } } if (targetIds.length === 0) { rnIn.innerHTML = `
${L.rn_tip_none}
`; txtStatNum.innerText = "0"; btnApply.disabled = true; javState.isRunning = false; return; } const sigIds = targetIds.length > 200 ? targetIds.slice(0, 100).concat(targetIds.slice(-100)) : targetIds; const currentSignature = [...sigIds].sort().join('|') + `_len_${targetIds.length}`; const isSameSelection = (currentSignature === javState.signature); if (!isSameSelection) { javState.runId++; javState.signature = currentSignature; javState.cache.clear(); javState.completed = 0; javState.total = targetIds.length; javState.isRunning = false; } const changes = targetIds.map(id => { const item = S.itemMap.get(id); const cached = javState.cache.get(id); if (cached) { return { id: item.id, old: item.name, new: cached.new, hl_old: cached.hlOld, conflict: cached.conflict, parent_id: item.parent_id }; } else { return { id: item.id, old: item.name, new: item.name, hl_old: null, conflict: false, parent_id: item.parent_id }; } }); handlePreviewResults(changes, null); const plannedMap = new Map(); plannedChanges.forEach(c => plannedMap.set(c.id, c)); const displayMap = new Map(); javState.activePlannedMap.clear(); plannedChanges.forEach(c => javState.activePlannedMap.set(c.id, c)); javState.activeNames = new Set(items.map(i => i.name)); rnDisplay.forEach(d => { displayMap.set(d.id, d); const cached = javState.cache.get(d.id); if (cached) { if (cached.new === d.old) { d.newNameHTML = `${L.str_jav_no_match}`; } else { d.newNameHTML = `${esc(cached.new)}`; } if (cached.hlOld) { d.hl_old = cached.hlOld; } } else { d.newNameHTML = ` ${L.str_jav_querying}`; } }); if (!document.getElementById('pk-spin-style')) { const style = document.createElement('style'); style.id = 'pk-spin-style'; style.innerHTML = `@keyframes pk-spin { 100% { transform: rotate(360deg); } }`; document.head.appendChild(style); } const updateProgress = () => { if(!txtStatNum) return; txtStatNum.style.display = 'inline-flex'; txtStatNum.style.alignItems = 'baseline'; txtStatNum.style.gap = '8px'; txtStatNum.innerHTML = `
${javState.completed} / ${javState.total} `; }; javState.ui = { displayMap: displayMap, render: renderRNVisible, updateProgress: updateProgress }; renderRNVisible(); if (isSameSelection && (javState.isRunning || javState.completed === javState.total)) { updateProgress(); if (javState.completed === javState.total) { if (rnVp) rnVp.scrollTop = 0; renderRNVisible(); btnApply.disabled = false; updateHeaderCheckbox(); const validCount = plannedChanges.filter(c => c.new !== c.old && !c.conflict).length; txtStatNum.innerHTML = `${validCount} / ${javState.total}`; } return; } javState.isRunning = true; const currentRunId = javState.runId; btnApply.disabled = true; updateProgress(); const allNames = new Set(items.map(i => i.name)); const queue = targetIds.filter(id => !javState.cache.has(id)); if (queue.length === 0) { javState.isRunning = false; btnApply.disabled = false; updateHeaderCheckbox(); return; } let lastRenderTime = 0; const processItem = async (id) => { if (currentRunId !== javState.runId || !document.body.contains(m)) return; const item = S.itemMap.get(id); if (!item) { javState.completed++; updateProgress(); return; } const oldName = item.name; let ext = ''; if (item.kind !== 'drive#folder') { const extIndex = oldName.lastIndexOf('.'); if (extIndex > 0) { const potentialExt = oldName.substring(extIndex + 1).toLowerCase(); if (VALID_EXTS.has(potentialExt)) { ext = oldName.substring(extIndex); } } } const code = extractKeyword(oldName); let newName = oldName; let hlOld = null; let displayHTML = `${L.str_jav_no_match}`; if (code) { try { const parts = code.split(/[^a-zA-Z0-9]+/).filter(p => p.length > 0); if (parts.length > 0) { const escapedParts = parts.map(p => p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); const pattern = `(${escapedParts.join('|')})`; const re = new RegExp(pattern, 'gi'); hlOld = oldName.replace(re, (m) => '§§§MATCH_START§§§' + m + '§§§MATCH_END§§§'); } } catch(e) {} newName = code + ext; displayHTML = `${esc(newName)}`; } let isConflict = false; if (newName !== oldName) { if (javState.activeNames.has(newName)) isConflict = true; else javState.activeNames.add(newName); } javState.cache.set(id, { new: newName, hlOld, conflict: isConflict }); javState.completed++; const currentMode = m.querySelector('input[name="rn_mode"]:checked').value; if (currentMode === 'jav' && currentRunId === javState.runId && javState.ui) { const displayItem = javState.ui.displayMap.get(id); const planItem = javState.activePlannedMap.get(id); if (displayItem) { displayItem.new = newName; displayItem.hl_old = hlOld; displayItem.newNameHTML = displayHTML; if (isConflict) displayItem.conflict = true; if (planItem) { planItem.new = newName; planItem.hl_old = hlOld; planItem.conflict = isConflict; } javState.ui.updateProgress(); const now = Date.now(); if (now - lastRenderTime > 500) { javState.ui.render(); updateHeaderCheckbox(); lastRenderTime = now; } } } }; const CONCURRENCY = 6; await Promise.all(Array.from({ length: CONCURRENCY }).map(async () => { while (queue.length > 0) { if (currentRunId !== javState.runId) break; const id = queue.shift(); if (id) await processItem(id); } })); if (currentRunId === javState.runId) { javState.isRunning = false; const currentMode = m.querySelector('input[name="rn_mode"]:checked').value; if (currentMode === 'jav') { if (rnVp) rnVp.scrollTop = 0; renderRNVisible(); btnApply.disabled = false; updateHeaderCheckbox(); const validCount = plannedChanges.filter(c => c.new !== c.old && !c.conflict).length; txtStatNum.style.display = 'inline'; txtStatNum.innerHTML = `${validCount}/${javState.total}`; } } return; } const workerFunction = function(e) { try { const { selectedIds, items, mode, pattern, findStr, repStr, useRegex, validExtsList, caseMode, widthMode, useCaseSense, useIncludeExt } = e.data; const changes =[]; const idSet = new Set(selectedIds); const allNames = new Set(); items.forEach(i => allNames.add(i.name)); const VALID_EXTS = new Set(validExtsList); const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const extractKeyword = (fileName) => { const fc2Regex = /(?:FC2|FC)(?:[-_. ]*PPV)?[-_. @]*(\d{5,8})(?:[-_. ]*(?:part|pt|cd)?[-_. ]?(\d{1,2}|[a-e]))?(?![a-z\d])/i; const fc2Match = fileName.match(fc2Regex); if (fc2Match) { const id = fc2Match[1]; const part = fc2Match[2]; return (part && part.trim()) ? `FC2-PPV-${id}-${part.toUpperCase()}` : `FC2-PPV-${id}`; } return null; }; const toHalf = (str) => str.replace(/[!-~]/g, c => String.fromCharCode(c.charCodeAt(0) - 0xFEE0)).replace(/ /g, ' '); const toFull = (str) => str.replace(/[!-~]/g, c => String.fromCharCode(c.charCodeAt(0) + 0xFEE0)).replace(/ /g, ' '); const toTitle = (str) => str.replace(/\b\w/g, c => c.toUpperCase()); let regex = null; if (mode === 'replace' && findStr) { try { const flags = useCaseSense ? 'g' : 'gi'; const p = useRegex ? findStr : escapeRegExp(findStr); regex = new RegExp(p, flags); } catch (err) {} } let counter = 1; for (let i = 0; i < items.length; i++) { const item = items[i]; if (!idSet.has(item.id)) continue; const oldName = item.name; let newName = oldName, hlOld = null; if (mode === 'pattern') { if (item.kind === 'drive#folder') continue; let ext = ''; const lastDotIndex = oldName.lastIndexOf('.'); if (lastDotIndex > 0) { const potentialExt = oldName.substring(lastDotIndex + 1).toLowerCase(); if (VALID_EXTS.has(potentialExt)) ext = '.' + potentialExt; } newName = pattern.replace(/{n}/g, String(counter).padStart(2, '0')) + ext; counter++; } else if (mode === 'replace') { if (findStr && regex) { let targetStr = oldName; let extStr = ""; if (!useIncludeExt && item.kind !== 'drive#folder') { const lastDot = oldName.lastIndexOf('.'); if (lastDot > 0) { targetStr = oldName.substring(0, lastDot); extStr = oldName.substring(lastDot); } } if (regex.test(targetStr)) { regex.lastIndex = 0; const newBase = targetStr.replace(regex, repStr); regex.lastIndex = 0; const hlBase = targetStr.replace(regex, (m) => '§§§MATCH_START§§§' + m + '§§§MATCH_END§§§'); newName = newBase + extStr; hlOld = hlBase + extStr; } } } else if (mode === 'format') { let base = newName; let ext = ''; if (item.kind !== 'drive#folder') { const lastDot = newName.lastIndexOf('.'); if (lastDot > 0) { base = newName.substring(0, lastDot); ext = newName.substring(lastDot); } } if (widthMode === 'half') base = toHalf(base); else if (widthMode === 'full') base = toFull(base); if (caseMode === 'upper') base = base.toUpperCase(); else if (caseMode === 'lower') base = base.toLowerCase(); else if (caseMode === 'title') base = toTitle(base); newName = base + ext; } else if (mode === 'jav') { const code = extractKeyword(oldName); if (code) { let ext = '', ext_old = '', base_old = oldName; if (item.kind !== 'drive#folder') { const lastDotIndex = oldName.lastIndexOf('.'); if (lastDotIndex > 0) { const potentialExt = oldName.substring(lastDotIndex + 1).toLowerCase(); if (VALID_EXTS.has(potentialExt)) { ext = oldName.substring(lastDotIndex); ext_old = ext; base_old = oldName.substring(0, lastDotIndex); } } } newName = code + ext; try { const parts = code.split(/[^a-zA-Z0-9]+/).filter(p => p.length > 0); if (parts.length > 0) { const escapedParts = parts.map(p => escapeRegExp(p)); const pat = `(${escapedParts.join('|')})`; const re = new RegExp(pat, 'gi'); hlOld = base_old.replace(re, (m) => '§§§MATCH_START§§§' + m + '§§§MATCH_END§§§') + ext_old; } } catch(e) {} } } else if (mode === 'ad_remove') { let cleanName = oldName; cleanName = cleanName.replace(/^【[^】]+】 *[-_.]? */, ''); cleanName = cleanName.replace(/^[a-z0-9-]+[.](?:com|net|org|cc|xyz|vip|top|la) +/i, ''); const adKw = "(?:[.]com|[.]net|[.]org|[.]cc|[.]xyz|[.]vip|[.]top|[.]la|2048|www[.])"; const atRegex = new RegExp('^.*?' + adKw + '.*?(?:@|--+|_\\s)', 'i'); cleanName = cleanName.replace(atRegex, ''); const hyphenRegex = new RegExp('^[a-z0-9.-]+' + adKw + '-', 'i'); cleanName = cleanName.replace(hyphenRegex, ''); cleanName = cleanName.replace(/^(?:精品加群|福利合集)[0-9]+[-_]+ */, ''); cleanName = cleanName.replace(/^[-_. ,,::;;\p{Extended_Pictographic}]+/u, ''); const idxChnR_Fix = cleanName.indexOf('】'); const idxChnL_Check = cleanName.indexOf('【'); if (idxChnR_Fix > 0 && idxChnR_Fix <= 10 && (idxChnL_Check === -1 || idxChnL_Check > idxChnR_Fix)) { cleanName = '【' + cleanName; } const idxEngR_Fix = cleanName.indexOf(']'); const idxEngL_Check = cleanName.indexOf('['); if (idxEngR_Fix > 0 && idxEngR_Fix <= 10 && (idxEngL_Check === -1 || idxEngL_Check > idxEngR_Fix)) { cleanName = '[' + cleanName; } const idxBkR_Fix = cleanName.indexOf('》'); const idxBkL_Check = cleanName.indexOf('《'); if (idxBkR_Fix > 0 && idxBkR_Fix <= 10 && (idxBkL_Check === -1 || idxBkL_Check > idxBkR_Fix)) { cleanName = '《' + cleanName; } const idxAngR_Fix = cleanName.indexOf('>'); const idxAngL_Check = cleanName.indexOf('<'); if (idxAngR_Fix > 0 && idxAngR_Fix <= 10 && (idxAngL_Check === -1 || idxAngL_Check > idxAngR_Fix)) { cleanName = '<' + cleanName; } const idxChnParR_Fix = cleanName.indexOf(')'); const idxChnParL_Check = cleanName.indexOf('('); if (idxChnParR_Fix > 0 && idxChnParR_Fix <= 10 && (idxChnParL_Check === -1 || idxChnParL_Check > idxChnParR_Fix)) { cleanName = '(' + cleanName; } const idxEngParR_Fix = cleanName.indexOf(')'); const idxEngParL_Check = cleanName.indexOf('('); if (idxEngParR_Fix > 0 && idxEngParR_Fix <= 10 && (idxEngParL_Check === -1 || idxEngParL_Check > idxEngParR_Fix)) { cleanName = '(' + cleanName; } const idxCurR_Fix = cleanName.indexOf('}'); const idxCurL_Check = cleanName.indexOf('{'); if (idxCurR_Fix > 0 && idxCurR_Fix <= 10 && (idxCurL_Check === -1 || idxCurL_Check > idxCurR_Fix)) { cleanName = '{' + cleanName; } const cleanStack = (L, R) => { const chars = cleanName.split(''); const stack = []; const toRemove = new Set(); for (let i = 0; i < chars.length; i++) { const c = chars[i]; if (c === L) { stack.push(i); } else if (c === R) { if (stack.length > 0) stack.pop(); else toRemove.add(i); } } stack.forEach(i => toRemove.add(i)); if (toRemove.size > 0) { cleanName = chars.filter((_, i) => !toRemove.has(i)).join(''); } }; cleanStack('【', '】'); cleanStack('[', ']'); cleanStack('{', '}'); cleanStack('(', ')'); cleanStack('(', ')'); cleanStack('《', '》'); cleanStack('<', '>'); const quoteCount = (cleanName.match(/'/g) || []).length; if (quoteCount % 2 !== 0) cleanName = cleanName.replace(/'/, ''); const result = cleanName.trim(); const lastDot = oldName.lastIndexOf('.'); if (lastDot !== -1) { const ext = oldName.substring(lastDot); const extNoDot = ext.substring(1); if (!result || result === ext || result === extNoDot) { newName = oldName; } else { newName = result; } } else { if (!result) newName = oldName; else newName = result; } } else if (mode === 'ext_fix') { const mimeMap = { 'video/mp4': ['.mp4', '.m4v', '.f4v', '.mp4v', '.mov', '.avi', '.m4a', '.m4b'], 'video/x-matroska': ['.mkv', '.mk3d', '.mka', '.mks'], 'video/x-msvideo': ['.avi'], 'video/quicktime': ['.mov', '.qt', '.mp4', '.m4v'], 'video/x-flv': ['.flv'], 'video/webm': ['.webm'], 'video/mpeg': ['.mpg', '.mpeg', '.mpe', '.vob'], 'video/3gpp': ['.3gp', '.3g2', '.mp4'], 'video/mp2t': ['.ts', '.m2ts', '.mts'], 'video/x-m4v': ['.m4v', '.mp4'], 'video/x-ms-wmv': ['.wmv'], 'video/x-ms-asf': ['.asf', '.wmv', '.wma'], 'audio/mpeg': ['.mp3', '.mp2'], 'audio/mp4': ['.m4a', '.m4b', '.mp4'], 'audio/x-wav': ['.wav'], 'audio/flac': ['.flac'], 'audio/aac': ['.aac'], 'audio/ogg': ['.ogg', '.opus'], 'audio/x-ms-wma': ['.wma'], 'audio/webm': ['.weba'], 'image/jpeg': ['.jpg', '.jpeg', '.jpe', '.jif', '.jfif'], 'image/png': ['.png'], 'image/gif': ['.gif'], 'image/webp': ['.webp'], 'image/bmp': ['.bmp'], 'image/svg+xml': ['.svg'], 'image/heif': ['.heic', '.heif'], 'image/vnd.adobe.photoshop': ['.psd', '.abr'], 'image/x-icon': ['.ico'], 'image/vnd.microsoft.icon': ['.ico'], 'image/tiff': ['.tif', '.tiff', '.cr2', '.cr3', '.nef', '.dng', '.arw', '.orf', '.rw2', '.pef', '.sr2', '.raf'], 'application/postscript': ['.ps', '.eps', '.ai'], 'application/dicom': ['.dcm'], 'application/zip': [ '.zip', '.exe', '.pt', '.pth', '.apk', '.xapk', '.apks', '.obb', '.aar', '.aab', '.ipa', '.ipsw', '.wgt', '.docx', '.docm', '.dotx', '.dotm', '.xlsx', '.xlsm', '.xltx', '.xltm', '.pptx', '.pptm', '.potx', '.potm', '.vsdx', '.xmind', '.xlam', '.thmx', '.odt', '.ods', '.odp', '.oxps', '.xps', '.pages', '.numbers', '.key', '.xd', '.idml', '.zxp', '.fig', '.sketch', '.brush', '.brushset', '.3mf', '.usdz', '.dwfx', '.ora', '.ufo', '.h5p', '.apkg', '.colpkg', '.mcpack', '.mcworld', '.unitypackage', '.sb3', '.love', '.egg', '.nsp', '.xci', '.cia', '.alfredworkflow', '.sublime-package', '.otf', '.ttf', '.woff', '.woff2', '.epub', '.kmz', '.cbz', '.jar', '.war', '.ear', '.sar', '.whl', '.nupkg', '.wsz', '.crx', '.xpi', '.vsix', '.msix', '.appx', '.msixbundle', '.appxbundle', '.kra', '.appv', '.jks', '.keystore', '.truststore' ], 'application/x-rar-compressed': ['.rar', '.cbr', '.exe'], 'application/x-rar': ['.rar', '.cbr', '.exe'], 'application/vnd.rar': ['.rar', '.cbr', '.exe'], 'application/x-7z-compressed': ['.7z', '.exe', '.cb7', '.wim', '.esd'], 'application/x-tar': ['.tar', '.cbt', '.ova', '.unitypackage', '.gem'], 'application/gzip': ['.gz', '.tgz', '.svgz', '.als', '.schematic', '.litematic', '.tgs', '.unitypackage', '.box'], 'application/x-lzh-compressed': ['.lzh', '.lha'], 'application/x-lha': ['.lzh', '.lha'], 'application/x-iso9660-image': ['.iso', '.img'], 'application/vnd.android.package-archive': ['.apk'], 'application/x-apple-diskimage': ['.dmg'], 'application/x-debian-package': ['.deb'], 'application/x-redhat-package-manager': ['.rpm'], 'application/pdf': ['.pdf', '.ai'], 'text/plain': [ '.txt', '.log', '.md', '.markdown', '.nfo', '.rtf', '.rst', '.adoc', '.org','.mhtml', '.mht', '.dts', '.dtsi', '.ofx', '.qif', '.gnucash', '.tscn', '.tres', '.gd', '.godot', '.out', '.err', '.pid', '.asc', '.md5', '.sha1', '.sha256', '.sha512', '.dockerfile', '.makefile', '.jenkinsfile', '.tf', '.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte', '.astro', '.mdx', '.css', '.scss', '.less', '.html', '.htm', '.pug', '.jade', '.coffee', '.wat', '.pac', '.graphql', '.gql', '.prisma', '.py', '.java', '.c', '.cpp', '.h', '.hh', '.hpp', '.cs', '.php', '.go', '.rs', '.rb', '.lua', '.kt', '.swift', '.dart', '.pl', '.pm', '.scala', '.groovy', '.hs', '.asp', '.aspx', '.jsp', '.m', '.mm', '.r', '.rmd', '.jl', '.nb', '.ex', '.exs', '.erl', '.hrl', '.clj', '.lisp', '.ml', '.v', '.sv', '.vhd', '.vhdl', '.sas', '.do', '.sh', '.bat', '.cmd', '.ps1', '.psd1', '.psm1', '.vbs', '.reg', '.vmx', '.lock', '.toml', '.hex', '.gradle', '.cmake', '.editorconfig', '.ini', '.cfg', '.conf', '.rc', '.list', '.yaml', '.yml', '.json', '.xml', '.properties', '.env', '.gitignore', '.sql', '.drawio', '.dio', '.htaccess', '.npmrc', '.eps', '.ps', '.meta', '.asset', '.fbx', '.step', '.stp', '.iges', '.igs', '.gcode', '.stl', '.ply', '.fasta', '.fa', '.eml', '.mbox', '.ics', '.ifb', '.vcf', '.ovpn', '.glsl', '.hlsl', '.shader', '.cginc', '.unity', '.pem', '.key', '.crt', '.csr', '.p7b', '.p7c', '.tex', '.sty', '.cls', '.bib', '.srt', '.ass', '.ssa', '.sub', '.vtt', '.smi', '.lrc', '.sup', '.idx', '.sbv', '.m3u', '.m3u8', '.cue', '.torrent' ], 'text/html': ['.html', '.htm', '.mhtml', '.mht', '.vue', '.svelte', '.astro', '.txt'], 'text/xml': [ '.xml', '.ui', '.opml', '.kml', '.gpx', '.rss', '.nfo', '.txt', '.svg', '.plist', '.mobileconfig', '.webloc', '.ttml', '.musicxml', '.drawio', '.dio', '.csproj', '.vbproj', '.xaml', '.kdenlive', '.fb2', '.xmp', '.dae', '.fods', '.fodt', '.fodp', '.mobileprovision', '.nuspec', '.resx', '.vbox', '.osm', '.application', '.manifest' ], 'application/json': [ '.json', '.txt', '.ipynb', '.gltf', '.geojson', '.map', '.har', '.topojson', '.webmanifest', '.postman_collection', '.tfstate', '.webapp', '.uproject', '.uplugin', '.glyphs' ], 'application/x-hdf': ['.h5', '.hdf5', '.keras'], 'application/x-hdf5': ['.h5', '.hdf5', '.keras'], 'text/calendar': ['.ics', '.ifb'], 'application/x-bittorrent': ['.torrent'], 'message/rfc822': ['.mhtml', '.mht', '.eml'], 'multipart/related': ['.mhtml', '.mht'], 'application/x-mobipocket-ebook': ['.mobi', '.azw3'], 'application/vnd.amazon.ebook': ['.azw3', '.mobi'], 'text/vcard': ['.vcf'], 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx', '.docm', '.dotx', '.dotm'], 'application/msword': ['.doc'], 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx', '.xlsm', '.xltx', '.xltm', '.csv'], 'application/vnd.ms-excel': ['.xls', '.csv'], 'application/vnd.openxmlformats-officedocument.presentationml.presentation': ['.pptx', '.pptm', '.potx', '.potm'], 'application/vnd.ms-powerpoint': ['.ppt'], 'application/epub+zip': ['.epub', '.zip'] }; const exactNamesToKeep = new Set([ 'thumbs.db', 'desktop.ini', '.ds_store', 'dockerfile', 'makefile', 'jenkinsfile', 'rakefile', 'gemfile', 'vagrantfile', 'procfile', 'license', 'readme', 'changelog', 'copying', 'authors', 'cmakelists.txt', 'contributors', 'patents', 'security', 'notice', 'version', 'cname', 'owners', 'robots.txt', 'go.mod', 'go.sum', 'podfile', 'podfile.lock', 'yarn.lock', 'package-lock.json' ]); const binaryMimes = ['application/octet-stream', 'binary/octet-stream']; const safeImgExts = ['.jpg', '.jpeg', '.png', '.bmp', '.heic']; const safeVidExts = ['.mp4', '.mkv', '.avi', '.mov', '.wmv', '.flv', '.webm', '.m4v', '.3gp', '.ts', '.mpg', '.mpeg', '.vob', '.rmvb', '.asf']; const highRiskExts = [ '.rar', '.zip', '.7z', '.iso', '.img', '.dmg', '.apk', '.ipa', '.mhtml', '.mht', '.html', '.htm', '.xml', '.json', '.db', '.dat', '.tmp', '.dts', '.dtsi', '.ts', '.3gp', '.mkv', '.avi', '.mp4', '.flv', '.mov', '.wmv' ]; if (item.mimeType === 'application/vnd.google-apps.folder') continue; const pureMimeType = (item.mimeType || '').split(';')[0].trim().toLowerCase(); const lowerName = oldName.toLowerCase(); const lastDotIndex = oldName.lastIndexOf('.'); const currentExt = lastDotIndex !== -1 ? oldName.substring(lastDotIndex).toLowerCase() : ''; const isPartFile = /^\.\d+$/.test(currentExt) || /\.part\d+$/i.test(currentExt); let newName = oldName; let shouldFix = false; if (binaryMimes.includes(pureMimeType) || exactNamesToKeep.has(lowerName) || oldName.startsWith('.') || isPartFile) { shouldFix = false; } else { const validExtensions = mimeMap[pureMimeType]; if (validExtensions && validExtensions.length > 0) { const primaryExt = validExtensions[0]; if (validExtensions.includes(currentExt)) { newName = oldName.substring(0, lastDotIndex) + currentExt; shouldFix = true; } else if ( (safeImgExts.includes(currentExt) && safeImgExts.includes(primaryExt)) || (safeVidExts.includes(currentExt) && safeVidExts.includes(primaryExt)) ) { newName = oldName.substring(0, lastDotIndex) + currentExt; shouldFix = true; } else if ( (pureMimeType === 'application/pdf' && ['.rar', '.zip', '.7z'].includes(currentExt)) || highRiskExts.includes(currentExt) ) { shouldFix = false; } else { shouldFix = true; if (lastDotIndex === -1) { const ambiguousTextExts = ['.svg', '.html', '.htm', '.xml', '.json']; if (ambiguousTextExts.includes(primaryExt) && !oldName.includes(' ')) { shouldFix = false; } else { if (ambiguousTextExts.includes(primaryExt)) newName = oldName + '.txt'; else newName = oldName + primaryExt; } } else { const isSourceText = ['.txt', '.log', '.md', '.ini', '.nfo'].includes(currentExt); const ambiguousTextExts = ['.svg', '.html', '.htm', '.xml', '.json']; if (isSourceText && ambiguousTextExts.includes(primaryExt)) { newName = oldName.substring(0, lastDotIndex) + currentExt; } else { newName = oldName.substring(0, lastDotIndex) + primaryExt; } } } } } if (shouldFix && newName !== oldName) { changes.push({ id: item.id, old: oldName, new: newName, hl_old: null, conflict: false, parent_id: item.parent_id }); } } if (item._isSystem) continue; if (newName !== oldName || (mode === 'replace' && hlOld) || mode === 'pattern') { let isConflict = false; const cleanNewName = newName.trim(); if (!cleanNewName) { isConflict = true; newName = e.data.STR_EMPTY_FILENAME; } else if (allNames.has(cleanNewName)) { isConflict = true; } else { allNames.add(cleanNewName); } changes.push({ id: item.id, old: oldName, new: newName, hl_old: hlOld, conflict: isConflict, parent_id: item.parent_id }); } } self.postMessage({ changes: changes, error: null }); } catch (e) { self.postMessage({ changes: [], error: e.toString() }); } }; const workerCode = 'self.onmessage = ' + workerFunction.toString(); const itemsCopy = items.map(i => ({ id: i.id, name: i.name, kind: i.kind, mimeType: i.mime_type, parent_id: i.parent_id, _isSystem: isSystemItem(i) })); const blob = new Blob([workerCode], { type: 'application/javascript' }); const workerUrl = URL.createObjectURL(blob); const previewWorker = new Worker(workerUrl); previewWorker.onmessage = (e) => { const { changes, error } = e.data; previewWorker.terminate(); URL.revokeObjectURL(workerUrl); if (myPreviewId !== currentPreviewId) return; handlePreviewResults(changes, error); }; previewWorker.onerror = (e) => { console.error("Worker Error:", e); rnIn.innerHTML = `
❌ ${L.err_worker_failed}
`; previewWorker.terminate(); URL.revokeObjectURL(workerUrl); }; previewWorker.postMessage({ selectedIds, items: itemsCopy, mode, pattern, findStr, repStr, useRegex, STR_INVALID_REGEX: L.err_invalid_regex, STR_EMPTY_FILENAME: L.str_empty_filename, validExtsList: VALID_EXTS_LIST, caseMode, widthMode, useCaseSense, useIncludeExt }); }; m.querySelector('#rn_cancel').onclick = () => m.remove(); [inpPattern, inpFind, inpRep, chkRegex, chkCase, chkIncludeExt].forEach(el => el.onchange = generatePreview); [inpPattern, inpFind, inpRep].forEach(el => el.onkeydown = (e) => { if(e.key==='Enter') generatePreview(); }); let selectedCase = ""; let selectedWidth = ""; const bindRNSelect = (id, onSelect) => { const container = m.querySelector(`#${id}`); if (!container) return; const trigger = container.querySelector('.pk-select-trigger'); const menu = container.querySelector('.pk-select-menu'); const txt = container.querySelector('span'); const items = container.querySelectorAll('.pk-select-item'); trigger.onclick = (e) => { e.stopPropagation(); const allMenus = m.querySelectorAll('.pk-select-menu'); const isOpen = menu.style.display === 'block'; allMenus.forEach(om => om.style.display = 'none'); menu.style.display = isOpen ? 'none' : 'block'; }; items.forEach(item => { item.onclick = (e) => { e.stopPropagation(); items.forEach(i => i.classList.remove('act')); item.classList.add('act'); txt.textContent = item.textContent; menu.style.display = 'none'; onSelect(item.dataset.val); generatePreview(); }; }); }; bindRNSelect('cs_rn_case', (val) => { selectedCase = val; }); bindRNSelect('cs_rn_width', (val) => { selectedWidth = val; }); const closeDropdowns = () => m.querySelectorAll('.pk-select-menu').forEach(menu => menu.style.display = 'none'); setTimeout(() => document.addEventListener('click', closeDropdowns), 0); const _orgRemove = m.remove.bind(m); m.remove = () => { document.removeEventListener('click', closeDropdowns); _orgRemove(); }; const bindHeaderCheckbox = () => { const cbAll = m.querySelector('#rn_cb_all'); if(cbAll) { cbAll.onclick = (e) => { const isChecked = e.target.checked; if(isChecked) { plannedChanges.forEach(c => previewSelectedIds.add(c.id)); } else { previewSelectedIds.clear(); } recalcPatternNames(); renderRNVisible(); updateHeaderCheckbox(); }; } }; setTimeout(bindHeaderCheckbox, 0); btnApply.onclick = async () => { const validChanges = rnDisplay.filter(c => previewSelectedIds.has(c.id) && c.new !== c.old); const skippedCount = plannedChanges.length - validChanges.length; if (validChanges.length === 0) { showAlert(L.msg_rn_all_skipped); return; } let confirmMsg = L.rn_warn_confirm.replace('{n}', validChanges.length); if (!await showConfirm(confirmMsg)) return; const progressTask = FloatBarManager.create(L.str_renaming); m.remove(); let isRunning = true; UI.stopBtn.onclick = () => { isRunning = false; updateLoadTxt(L.str_stopping); }; const USER_LIMIT = parseInt(localStorage.getItem('pk_user_limit') || "200"); let currentLimit = 2; const MIN_LIMIT = 2; const queue = [...validChanges]; const activeTasks = new Set(); const stats = { success: 0, fail: 0, lastUiTime: 0 }; const total = validChanges.length; const runRenameTask = async (task) => { try { await apiAction(`/${task.id}`, { name: task.new }); stats.success++; const item = S.itemMap.get(task.id); if (item) { item.name = task.new; const nowIso = new Date(getServerNow()).toISOString(); item.modified_time = nowIso; if (item.kind === 'drive#folder') gmSet('pk_fmod_' + item.id, nowIso); const parentIdForFmod = item.parent_id || 'root'; gmSet('pk_fmod_' + parentIdForFmod, nowIso); const row = UI.in.querySelector(`.pk-row[data-id="${task.id}"]`); if (row && row.lastElementChild) row.lastElementChild.textContent = fmtDate(nowIso); if (S.analyzeResultItems) { const anaItem = S.analyzeResultItems.find(x => x.id === task.id); if (anaItem) anaItem.name = task.new; } if (S.analyzeMap && S.analyzeMap.has(task.id)) { S.analyzeMap.get(task.id).name = task.new; } if (S.lastGlobalResults && S.lastGlobalResults.length > 0) { const gItem = S.lastGlobalResults.find(x => x.id === task.id); if (gItem) gItem.name = task.new; } if (typeof globalDirtyFolders !== 'undefined') { const pid = item.parent_id || ''; globalDirtyFolders.add(pid); } } if (currentLimit < USER_LIMIT) currentLimit++; } catch (e) { if (!isRunning) return; if ((e.message && e.message.includes('429')) || (e.message && e.message.includes('Network'))) { currentLimit = Math.max(MIN_LIMIT, Math.floor(currentLimit / 2)); task.retryCount = (task.retryCount || 0) + 1; await sleep(Math.min(task.retryCount * 1000, 10000)); queue.push(task); } else { stats.fail++; } } }; try { updateLoadTxt(L.str_init_rename); while ((queue.length > 0 || activeTasks.size > 0) && isRunning) { while (queue.length > 0 && activeTasks.size < currentLimit && isRunning) { const task = queue.shift(); const p = runRenameTask(task).finally(() => activeTasks.delete(p)); activeTasks.add(p); } if (activeTasks.size > 0) await Promise.race(activeTasks); const now = Date.now(); if (now - stats.lastUiTime > 150) { progressTask.update(`${L.str_renaming} ${stats.success + stats.fail}/${total} | ${L.str_speed}: ${activeTasks.size} | ${L.str_success}: ${stats.success}`); stats.lastUiTime = now; } } if (!isRunning) throw new Error('StoppedByUser'); updateLoadTxt(L.str_refreshing_cache); if (typeof runBackgroundCrawler === 'function') runBackgroundCrawler(); if (S.dupMode) renderDupView(); else refresh(); let msgParts = []; if (stats.success > 0) msgParts.push(L.msg_bulkrename_done.replace('{n}', stats.success)); if (stats.fail > 0) msgParts.push(L.msg_rn_fail_count.replace('{n}', stats.fail)); const finalMsg = msgParts.join('\n'); await sleep(300); showAlert(finalMsg); } catch (e) { if (e.message !== 'StoppedByUser') showAlert(`${L.str_error_crit}: ${e.message}`); } finally { if (progressTask) progressTask.destroy(); setLoad(false); } }; }; UI.btnPrune.onclick = async () => { isGUISensitive = true; ensureItemMap(); const selectedFolders = S.getSelectedIds() .map(id => S.itemMap.get(id)) .filter(i => i && i.kind === 'drive#folder'); if (selectedFolders.length === 0) return; const hasConflict = selectedFolders.some(f => isPathBusy(f.id)); if (hasConflict) { showAlert(L.msg_op_blocked_moving); return; } if (!await showConfirm(L.msg_prune_confirm)) return; setLoad(true); S.scanning = true; S.scanId = (S.scanId || 0) + 1; const myScanId = S.scanId; if (S.scanAbortController) S.scanAbortController.abort(); S.scanAbortController = new AbortController(); const signal = S.scanAbortController.signal; UI.stopBtn.onclick = () => { S.scanning = false; if (S.scanAbortController) S.scanAbortController.abort(); updateLoadTxt(L.str_stopping); setLoad(false); isGUISensitive = false; }; const folderMap = new Map(); updateLoadTxt(L.str_scanning_dir); try { await coreRecursiveEngine(selectedFolders, { signal: signal, onFolder: (folder, filesInFolder, subFolders) => { const hasFiles = filesInFolder.some(f => f.kind !== 'drive#folder'); folderMap.set(folder.id, { id: folder.id, name: folder.name, parent_id: folder.parent_id, depth: folder.depth || 0, hasFiles: hasFiles, subFolderIds: subFolders.map(s => s.id) }); }, onProgress: (st) => { const folderText = `${L.str_scanning_dir} ${st.folders} ${L.unit_folders}`; const statusInfo = ` | ${L.str_files}: ${st.files} | ${L.str_speed}: ${st.currentConcurrency} | ${L.str_cached} ${st.cacheHits} ${L.unit_folders}`; updateLoadTxt(folderText + statusInfo); } }); if (!S.scanning || signal.aborted || myScanId !== S.scanId) return; updateLoadTxt(L.str_analyzing); await sleep(50); const allScanned = Array.from(folderMap.values()).sort((a, b) => b.depth - a.depth); const toDeleteList = []; const toDeleteIds = new Set(); for (let i = 0; i < allScanned.length; i++) { if (!S.scanning) return; const folder = allScanned[i]; const isSystemProtected = isSystemItem({ ...folder, kind: 'drive#folder' }); if (isSystemProtected) continue; if (!folder.hasFiles) { const allSubsWillBeDeleted = folder.subFolderIds.every(subId => toDeleteIds.has(subId)); if (allSubsWillBeDeleted) { toDeleteIds.add(folder.id); toDeleteList.push(folder); } } } if (toDeleteList.length === 0) { setLoad(false); showToast(L.msg_prune_none); } else { setLoad(false); let confirmMsg = L.msg_prune_found.replace('{n}', toDeleteList.length); const delRes = await showDeleteConfirm(confirmMsg); if (delRes.confirm) { const allIds = toDeleteList.map(f => f.id); await executeBatchDelete(allIds, { silent: true, forceRefresh: false, hardDelete: delRes.hardDelete }); if (myScanId === S.scanId) { const deletedSet = new Set(allIds); if (S.lastGlobalResults && S.lastGlobalResults.length > 0) { S.lastGlobalResults = S.lastGlobalResults.filter(x => !deletedSet.has(x.id)); } if (S.analyzeMode && S.analyzeResultItems) { S.analyzeResultItems = S.analyzeResultItems.filter(x => !deletedSet.has(x.id)); } updateLoadTxt(L.str_refreshing); const affectedParentIds = new Set(); affectedParentIds.add(S.path[S.path.length - 1].id || 'root'); toDeleteList.forEach(folder => { if (folder.parent_id) affectedParentIds.add(folder.parent_id); else affectedParentIds.add('root'); }); affectedParentIds.forEach(pid => { if (typeof globalCache !== 'undefined') globalCache.delete(pid); S.cache.delete(pid); }); await load(false, true); showToast(L.str_cleanup_done); } } } } catch (e) { if (e.name !== 'AbortError' && myScanId === S.scanId) { setLoad(false); showAlert(`${L.str_error}: ${e.message}`); } } finally { if (myScanId === S.scanId) { setLoad(false); S.scanning = false; S.scanAbortController = null; isGUISensitive = false; if (typeof DurationProber !== 'undefined') DurationProber.checkAndRun(); } } }; if (UI.btnUpPause) UI.btnUpPause.onclick = () => { const ids = S.getSelectedIds(); ids.forEach(id => { const task = S.uploadTasks.find(t => t.id === id); if (task && S.upMng) S.upMng.pause(task, true); }); if (S.uploadMode) { refresh(); } }; if (UI.btnUpStart) UI.btnUpStart.onclick = () => { const ids = S.getSelectedIds(); ids.forEach(id => { const task = S.uploadTasks.find(t => t.id === id); if (task && S.upMng) S.upMng.resume(task, true); }); if (S.uploadMode) { refresh(); } }; if (UI.btnUpDel) UI.btnUpDel.onclick = () => { const count = S.getSelectedCount(); if (count === 0) return; const html = `

${L.title_del_task_confirm_fmt.replace('{n}', count)}

`; const m = showModal(html); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { modalBox.style.width = "420px"; modalBox.style.padding = "24px"; modalBox.style.borderRadius = "12px"; } const close = () => m.remove(); m.querySelector('#del_up_cancel').onclick = close; const closeBtn = m.querySelector('.pk-modal-close'); if(closeBtn) closeBtn.onclick = close; m.querySelector('#del_up_confirm').onclick = async () => { const isDeleteFile = m.querySelector('#del_up_files').checked; m.remove(); const ids = S.getSelectedIds(); const filesToTrash = []; const ghostsToHardDelete = []; ids.forEach(id => { const task = S.uploadTasks.find(t => t.id === id); if (task) { task._deleted = true; const forceDelete = task.status !== 'DONE'; task._deleteFileIntent = forceDelete || isDeleteFile; if (task.file_id) { if (forceDelete) ghostsToHardDelete.push(task.file_id); else if (isDeleteFile) filesToTrash.push(task.file_id); } if (S.upMng) S.upMng.pause(task, true); } }); const idSet = S.sel; S.uploadTasks = S.uploadTasks.filter(t => { if(idSet.has(t.id)) { if (S.upMng) S.upMng.removeTask(t.id); return false; } return true; }); S.clearSelection(); load(false, true); if (filesToTrash.length > 0) { try { await executeBatchDelete(filesToTrash, { silent: false, hardDelete: false, forceRefresh: false }); } catch(e) { console.error("Failed to trash uploaded files:", e); } } if (ghostsToHardDelete.length > 0) { try { await fetch('https://api-drive.mypikpak.com/drive/v1/files:batchDelete', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: ghostsToHardDelete }) }); ghostsToHardDelete.forEach(id => { if (typeof window.pkRemoveGhostFile === 'function') window.pkRemoveGhostFile(id); }); } catch (e) { console.error("Failed to hard delete ghost files:", e); } } showToast(L.msg_task_del_success_fmt.replace('{n}', ids.length)); }; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#del_up_confirm').click(); } }); }; if (UI.btnUpClearAll) UI.btnUpClearAll.onclick = () => { if (S.uploadTasks.length === 0) return; const count = S.uploadTasks.length; const html = `

${L.title_clear_task_confirm}

`; const m = showModal(html); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { modalBox.style.width = "420px"; modalBox.style.padding = "24px"; modalBox.style.borderRadius = "12px"; } const close = () => m.remove(); m.querySelector('#clear_up_cancel').onclick = close; const closeBtn = m.querySelector('.pk-modal-close'); if(closeBtn) closeBtn.onclick = close; m.querySelector('#clear_up_confirm').onclick = async () => { const isDeleteFile = m.querySelector('#clear_all_up_files').checked; m.remove(); const filesToTrash = []; const ghostsToHardDelete = []; const count = S.uploadTasks.length; S.uploadTasks.forEach(task => { task._deleted = true; const forceDelete = task.status !== 'DONE'; task._deleteFileIntent = forceDelete || isDeleteFile; if (S.upMng) { S.upMng.pause(task, true); S.upMng.removeTask(task.id); } if (task.file_id) { if (forceDelete) ghostsToHardDelete.push(task.file_id); else if (isDeleteFile) filesToTrash.push(task.file_id); } }); S.uploadTasks = []; S.clearSelection(); load(false, true); if (filesToTrash.length > 0) { try { await executeBatchDelete(filesToTrash, { silent: false, hardDelete: false, forceRefresh: false }); } catch(e) {} } if (ghostsToHardDelete.length > 0) { try { await fetch('https://api-drive.mypikpak.com/drive/v1/files:batchDelete', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: ghostsToHardDelete }) }); ghostsToHardDelete.forEach(id => { if (typeof window.pkRemoveGhostFile === 'function') window.pkRemoveGhostFile(id); }); } catch (e) {} } showToast(L.msg_task_clear_success_fmt.replace('{n}', count)); }; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#clear_up_confirm').click(); } }); }; const resolvePlayableDetailForExternal = async (item) => { const targetApiId = ((S.offlineMode && item.kind === 'drive#task') || (S.uploadMode && item.file_id)) ? item.file_id : item.id; if (item._isShareItem) return await resolveSharePlayableFile(item); if (!targetApiId) throw new Error("File ID not ready"); return await apiGet(targetApiId); }; const cleanExternalPlaybackUrl = (url) => { let cleanUrl = String(url || '').replace('&ext=.m3u8', ''); if (cleanUrl.includes('ts_downloader') && cleanUrl.includes('url=')) { try { const urlParam = new URL(cleanUrl).searchParams.get('url'); if (urlParam) cleanUrl = decodeURIComponent(urlParam); } catch (e) {} } return cleanUrl; }; function getM3UExportItemById(id) { if (!id) return null; return (S.shareParseInsightItemMap && S.shareParseInsightItemMap.get(id)) || (S.shareParseItemMap && S.shareParseItemMap.get(id)) || (S.itemMap && S.itemMap.get(id)) || (S.items || []).find(it => it && it.id === id) || null; } function isM3UExportVideoItem(item) { if (!item || item.isHeader || item.kind === 'drive#folder') return false; if (S.offlineMode && item.kind === 'drive#task' && item.phase !== 'PHASE_TYPE_COMPLETE') return false; if (S.uploadMode && item.status !== 'DONE') return false; return isVideoLikeItem(item); } function getM3UExportSelectedVideos() { const selected = new Set(S.getSelectedIds()); if (!selected.size) return []; const out = []; const seen = new Set(); const list = Array.isArray(S.display) ? S.display : []; for (let i = 0; i < list.length; i++) { const displayItem = list[i]; const id = displayItem && displayItem.id; if (!id || seen.has(id) || !selected.has(id)) continue; const item = getM3UExportItemById(id) || displayItem; if (!isM3UExportVideoItem(item)) continue; seen.add(id); out.push(item); } return out; } function escapeM3UInfoText(s) { return String(s || '').replace(/[\r\n]+/g, ' ').trim() || 'video'; } function downloadM3UText(text, fileName) { const blob = new Blob([text], { type: 'audio/x-mpegurl;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = fileName || 'PikPak_Selected_Videos.m3u'; document.body.appendChild(a); a.click(); setTimeout(() => { URL.revokeObjectURL(url); a.remove(); }, 0); } function getM3UExportFileName() { const pad = n => String(n).padStart(2, '0'); const d = new Date(); const ts = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`; const pathName = (S.path && S.path.length ? S.path[S.path.length - 1].name : '') || 'PikPak'; const safe = String(pathName).replace(/[\\/:*?"<>|\r\n]+/g, '_').slice(0, 60) || 'PikPak'; return `${safe}_M3U_${ts}.m3u`; } function formatM3UExportProgress(done, total) { return (getStrings().msg_m3u_export_progress).replace('{done}', done).replace('{total}', total); } const openExternalPlaybackDialog = (detail, opts = {}) => { const L = getStrings(); if (!detail) return null; const qualities = generateQualityList(detail); const bestSource = getBestSource(detail); let selectedUrl = opts.preferredUrl || bestSource.src; let selectedResName = opts.preferredName || bestSource.name; const savedPlayer = opts.initialPlayer || gmGet('pk_ext_player', 'potplayer'); let selectedPlayer = (savedPlayer === 'other') ? 'other' : 'potplayer'; const initialBtnTxt = (selectedPlayer === 'potplayer') ? L.btn_start_play : L.btn_copy_link; const shareExtPreviewTip = (detail && detail._isShareItem && detail.params && (detail.params.anonymous_play_seconds || detail.params.anonymous_play_range)) ? (L.msg_share_ext_preview_limited_save || L.msg_share_preview_limited_save || '') : ''; const m = showModal(`

${L.btn_ext}

${shareExtPreviewTip ? `
${shareExtPreviewTip}
` : ''}
${L.lbl_resolution}
${selectedResName}
${CONF.crumbIcons.down}
${qualities.map(q => `
${q.name}
`).join('')}
${L.lbl_player}
${selectedPlayer === 'potplayer' ? 'PotPlayer' : L.opt_player_other}
${CONF.crumbIcons.down}
PotPlayer
${L.opt_player_other}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { Object.assign(modalBox.style, { width: '480px', padding: '30px', height: 'auto', minHeight: 'auto', overflow: 'visible' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '26px', right: '26px' }); } const bindSelect = (id, onSelect) => { const container = m.querySelector(`#${id}`); const trigger = container.querySelector('.pk-select-trigger'); const menu = container.querySelector('.pk-select-menu'); const txt = container.querySelector('span'); trigger.onclick = (e) => { e.stopPropagation(); const allMenus = m.querySelectorAll('.pk-select-menu'); const isCurrentlyOpen = menu.style.display === 'block'; allMenus.forEach(om => om.style.display = 'none'); menu.style.display = isCurrentlyOpen ? 'none' : 'block'; }; container.querySelectorAll('.pk-select-item').forEach(item => { item.onclick = (e) => { e.stopPropagation(); container.querySelectorAll('.pk-select-item').forEach(i => i.classList.remove('act')); item.classList.add('act'); txt.textContent = item.textContent; menu.style.display = 'none'; onSelect(item.dataset.val, item.textContent); }; }); }; const runBtn = m.querySelector('#ext_run'); const fixBtn = m.querySelector('#ext_potplayer_fix'); const getCleanSelectedUrl = () => cleanExternalPlaybackUrl(selectedUrl); const updatePotPlayerFixEntry = () => { if (fixBtn) fixBtn.style.display = selectedPlayer === 'potplayer' ? 'inline-flex' : 'none'; }; bindSelect('cs_res', (val, text) => { selectedUrl = val; selectedResName = text; }); bindSelect('cs_player', (val) => { selectedPlayer = val; runBtn.textContent = (val === 'potplayer') ? L.btn_start_play : L.btn_copy_link; updatePotPlayerFixEntry(); }); const closeAllMenus = () => m.querySelectorAll('.pk-select-menu').forEach(om => om.style.display = 'none'); setTimeout(() => document.addEventListener('click', closeAllMenus), 0); const _orgRemove = m.remove.bind(m); m.remove = () => { document.removeEventListener('click', closeAllMenus); _orgRemove(); }; m.querySelector('#ext_cancel').onclick = () => m.remove(); m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { if (e.target && e.target.id === 'ext_potplayer_fix') return; e.preventDefault(); e.stopPropagation(); runBtn.click(); } }); if (fixBtn) { fixBtn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); openPotPlayerProtocolRepairHelper(getCleanSelectedUrl(), { button: runBtn, source: opts.source || 'normal', onRetryLikelyOpen: () => m.remove(), onRetryLikelyFail: () => m.remove() }); }; } runBtn.onclick = () => { gmSet('pk_ext_player', selectedPlayer); const cleanUrl = getCleanSelectedUrl(); if (selectedPlayer === 'potplayer') { launchPotPlayerWithFallback(cleanUrl, { button: runBtn, source: opts.source || 'normal', restoreText: L.btn_start_play, restoreBg: runBtn.style.background, restoreColor: runBtn.style.color, failToastDuration: 6500, failCloseDelay: 3200, onLikelyOpen: () => m.remove(), onLikelyFail: (ctx) => { if (!opts.keepOpenOnFail) m.remove(); schedulePotPlayerAutoRepairPrompt(cleanUrl, { source: opts.source || 'normal', autoRepairPrompt: ctx && ctx.autoRepairPrompt, autoRepairPromptDelay: ctx && ctx.autoRepairPromptDelay }); } }); } else { GM_setClipboard(cleanUrl); runBtn.textContent = L.msg_copy_success; runBtn.style.background = "#52c41a"; runBtn.disabled = true; setTimeout(() => m.remove(), 1000); } }; return m; }; const openDefaultPotPlayerForItem = async (item) => { const L = getStrings(); if (!item || S.trashMode) return false; setLoad(true); updateLoadTxt(L.loading_detail); let detail = item; try { detail = await resolvePlayableDetailForExternal(item); } catch (e) { setLoad(false); return false; } setLoad(false); const bestSource = getBestSource(detail); const cleanUrl = cleanExternalPlaybackUrl(bestSource && bestSource.src); if (!cleanUrl) return false; launchPotPlayerWithFallback(cleanUrl, { source: 'default_open', failToastDuration: 6500, failCloseDelay: 3200, silentLikelyFailToast: true, onLikelyFail: (ctx) => { schedulePotPlayerAutoRepairPrompt(cleanUrl, { source: 'default_open', autoRepairPrompt: ctx && ctx.autoRepairPrompt, autoRepairPromptDelay: ctx && ctx.autoRepairPromptDelay }); } }); return true; }; UI.btnExt.onclick = async () => { const id = S.getSelectedIds()[0]; const item = (S.shareParseMode ? ((S.shareParseInsightItemMap && S.shareParseInsightItemMap.get(id)) || (S.shareParseItemMap && S.shareParseItemMap.get(id))) : null) || S.itemMap.get(id); if (!item) return; setLoad(true); updateLoadTxt(L.loading_detail); let detail = item; try { detail = await resolvePlayableDetailForExternal(item); } catch (e) { if (!detail.web_content_link && !detail.medias) { setLoad(false); showToast(L.msg_video_fail, 'error'); return; } } setLoad(false); openExternalPlaybackDialog(detail, { source: 'normal' }); }; if (UI.btnExportM3U) UI.btnExportM3U.onclick = async () => { const L = getStrings(); const videos = getM3UExportSelectedVideos(); if (!videos.length) { showToast(L.msg_m3u_no_video, 'warning'); updateStat(); return; } if (videos.length > 50) { showToast((L.msg_m3u_export_limit || '').replace('{n}', '50'), 'warning'); return; } const btn = UI.btnExportM3U; const oldDisabled = btn.disabled; btn.disabled = true; btn.style.cursor = 'wait'; let progressTask = null; try { if (videos.some(it => it && it._isShareItem)) { showToast(L.msg_m3u_share_limited_save, 'warning', 6500); } progressTask = FloatBarManager.create(formatM3UExportProgress(0, videos.length)); const lines = ['#EXTM3U']; let exported = 0; let skipped = 0; let processed = 0; for (const item of videos) { try { const detail = await resolvePlayableDetailForExternal(item); const best = getBestSource(detail); const url = cleanExternalPlaybackUrl(best && best.src); if (!url) { skipped++; continue; } const duration = Number((item.params && item.params.duration) || (detail && detail.params && detail.params.duration) || -1); const extinfDuration = Number.isFinite(duration) && duration > 0 ? Math.round(duration) : -1; lines.push(`#EXTINF:${extinfDuration},${escapeM3UInfoText(item.name || (detail && detail.name) || 'video')}`); lines.push(url); exported++; } catch (e) { skipped++; } finally { processed++; if (progressTask) progressTask.update(formatM3UExportProgress(processed, videos.length)); } } if (progressTask) { progressTask.destroy(); progressTask = null; } if (!exported) { showToast(L.msg_m3u_no_valid_link, 'warning'); return; } downloadM3UText('\uFEFF' + lines.join('\n') + '\n', getM3UExportFileName()); const doneMsg = skipped ? (L.msg_m3u_export_partial || '').replace('{n}', exported).replace('{s}', skipped) : (L.msg_m3u_export_done || '').replace('{n}', exported); showToast(doneMsg, skipped ? 'warning' : 'success'); } finally { if (progressTask) progressTask.destroy(); btn.disabled = oldDisabled; btn.style.cursor = btn.disabled ? 'not-allowed' : 'pointer'; updateStat(); } }; if (UI.btnImgSearch) { UI.btnImgSearch.onclick = () => { if (UI.btnImgSearch.disabled) return; const id = S.getSelectedIds()[0]; const item = (S.shareParseMode ? ((S.shareParseInsightItemMap && S.shareParseInsightItemMap.get(id)) || (S.shareParseItemMap && S.shareParseItemMap.get(id))) : null) || S.itemMap.get(id); if (!item || !item.thumbnail_link) return; const thumbUrl = item.thumbnail_link.replace('SIZE_MEDIUM', 'SIZE_LARGE'); const thumbImg = new Image(); thumbImg.crossOrigin = 'anonymous'; thumbImg.src = thumbUrl; startImageSearch(thumbImg, item.name, document.body, thumbUrl); }; } UI.win.querySelector('#pk-down').onclick = async () => { const selectedIds = S.getSelectedIds(); const hasConflict = selectedIds.some(id => { const item = S.itemMap.get(id); if (item && item.kind === 'drive#folder') return isPathBusy(item.id); return S.movingIds.has(id); }); if (hasConflict) { showAlert(L.msg_op_blocked_moving); return; } setLoad(true); isGUISensitive = true; const abortCtrl = new AbortController(); const { signal } = abortCtrl; let isRunning = true; UI.stopBtn.onclick = () => { isRunning = false; abortCtrl.abort(); updateLoadTxt(L.str_stopping); }; const allFiles = []; const rootNodes = []; const HYDRATE_LIMIT = 20; const dlFilterRules = getDownloadFilterRules(); const hasDownloadFilterRules = dlFilterRules.hasRules; const filterStats = { scanned: 0, blocked: 0 }; const applyDownloadFilterToFile = (file) => { if (!hasDownloadFilterRules) return false; filterStats.scanned++; if (isDownloadFilterBlocked(file, dlFilterRules)) { filterStats.blocked++; return true; } return false; }; selectedIds.forEach(id => { const item = S.itemMap.get(id); if (item) { if (item.kind === 'drive#folder') { rootNodes.push({...item, lineage:[], retryCount: 0}); } else if (!applyDownloadFilterToFile(item)) { allFiles.push(item); } } }); let progressTask = null; try { await coreRecursiveEngine(rootNodes, { signal, onFile: (f) => { if (applyDownloadFilterToFile(f)) return; allFiles.push(f); }, onProgress: (st) => { updateLoadTxt(`${L.msg_batch_scanning}\n${L.str_files}: ${allFiles.length} | ${L.str_speed}: ${st.currentConcurrency}`); } }); if (!isRunning) throw new Error('StoppedByUser'); if (allFiles.length === 0) { setLoad(false); if (hasDownloadFilterRules && filterStats.scanned > 0 && filterStats.blocked === filterStats.scanned) { showToast(L.msg_batch_all_filtered.replace('{n}', filterStats.blocked)); } else { showToast(L.msg_batch_no_files); } return; } if (hasDownloadFilterRules && filterStats.blocked > 0) { showToast(L.msg_batch_filtered.replace('{n}', filterStats.blocked)); } let totalBytes = 0; for (let i = 0; i < allFiles.length; i++) { totalBytes += parseInt((allFiles[i] && allFiles[i].size) || 0, 10) || 0; } const needsSoftConfirm = allFiles.length > CONF.browserDownloadConfirmFileCount || totalBytes > CONF.browserDownloadConfirmTotalBytes; setLoad(false); if (needsSoftConfirm) { const confirmText = L.msg_down_confirm_total .replace('{n}', allFiles.length) .replace('{s}', fmtSize(totalBytes)) .replace('{fc}', CONF.browserDownloadConfirmFileCount) .replace('{fs}', fmtSize(CONF.browserDownloadConfirmTotalBytes)); if (!await showConfirm(confirmText)) return; } progressTask = FloatBarManager.create(L.msg_batch_hydrating); const readyFiles = []; const hydrateQueue = [...allFiles]; const activeTasks = new Set(); const hydrateWithRetry = async (file, maxRetries = 3) => { if (file.web_content_link) return file; if (file.phase === "PHASE_TYPE_PENDING" || file.phase === "PHASE_TYPE_RUNNING" || file.trashed) return null; let lastErr = null; for (let i = 0; i < maxRetries; i++) { if (!isRunning) return null; try { const detail = await apiGet(file.id); if (detail && detail.web_content_link) return detail; if (detail && (detail.phase === "PHASE_TYPE_PENDING" || detail.phase === "PHASE_TYPE_RUNNING")) return null; throw new Error("Link Empty"); } catch (e) { lastErr = e; if (i < maxRetries - 1) await sleep(1000 * (i + 1)); } } throw lastErr; }; while ((hydrateQueue.length > 0 || activeTasks.size > 0) && isRunning) { while (hydrateQueue.length > 0 && activeTasks.size < HYDRATE_LIMIT && isRunning) { const file = hydrateQueue.pop(); const p = (async () => { try { const detail = await hydrateWithRetry(file); if (detail) { readyFiles.push(detail); } } catch (e) { console.error(`[Hydrate Failed] ${file.name}:`, e); } })().finally(() => activeTasks.delete(p)); activeTasks.add(p); } if (activeTasks.size > 0) await Promise.race(activeTasks); if (progressTask) progressTask.update(`${L.msg_batch_hydrating} ${readyFiles.length} / ${allFiles.length}`); } for (let i = 0; i < readyFiles.length; i++) { if (!isRunning) break; if (progressTask) progressTask.update(`${L.msg_down_progress} ${i + 1} / ${readyFiles.length}`); if (isZeroByteFile(readyFiles[i])) { downloadEmptyFile(readyFiles[i]); } else { const link = document.createElement('a'); link.href = rewriteDownloadLinkForAccel(readyFiles[i].web_content_link, 'browser'); link.setAttribute('download', readyFiles[i].name); link.style.display = 'none'; document.body.appendChild(link); link.click(); setTimeout(() => { if (link.parentNode) link.remove(); }, 10000); } if (i < readyFiles.length - 1) { await sleep(2000); } else { await sleep(1500); } } if (isRunning && readyFiles.length > 0) { showToast(L.msg_down_success.replace('{n}', readyFiles.length)); } } catch (e) { if (e.message !== 'StoppedByUser' && e.name !== 'AbortError') showAlert(`${L.str_error}: ${e.message}`); } finally { setLoad(false); isGUISensitive = false; if (progressTask) progressTask.destroy(); } }; UI.win.querySelector('#pk-aria2').onclick = async () => { const selectedIds = S.getSelectedIds(); const hasConflict = selectedIds.some(id => { const item = S.itemMap.get(id); if (item && item.kind === 'drive#folder') return isPathBusy(item.id); return S.movingIds.has(id); }); if (hasConflict) { showAlert(L.msg_op_blocked_moving); return; } let ariaUrl = gmGet('pk_aria2_url', ''); let ariaToken = gmGet('pk_aria2_token', ''); if (!ariaUrl) { const result = await new Promise((resolve) => { const m = showModal(`

${CONF.icons.aria2.replace('width="16"', 'width="22"').replace('height="16"', 'width="22"')} ${L.btn_aria2} - ${L.btn_settings}

${L.msg_aria2_not_set}
${L.label_aria2_url}
${L.btn_default}
${L.lbl_aria2_status}
${L.label_aria2_token}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) Object.assign(modalBox.style, { width: '480px', padding: '30px', height: 'auto', minHeight: 'auto', overflow: 'visible' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '26px', right: '26px' }); const inpU = m.querySelector('#pop_aria_url'); const inpT = m.querySelector('#pop_aria_token'); const inpTEye = m.querySelector('#btn_pop_aria_token_eye'); const dot = m.querySelector('#pop_aria_test_dot'); const txt = m.querySelector('#pop_aria_test_txt'); const boxRes = m.querySelector('#pop_aria_test_res'); let testTimer = null; let popAriaTokenVisible = false; const runLiveTest = async () => { const url = inpU.value.trim(); const token = inpT.value.trim(); const showTip = () => showAlert(L.tip_mixed_content, L.lbl_aria2_status); if (!url) { dot.className = 'pk-aria-dot'; txt.textContent = L.lbl_aria2_status; boxRes.onclick = showTip; boxRes.style.cursor = 'pointer'; return; } dot.className = 'pk-aria-dot wait'; txt.textContent = L.str_connecting; boxRes.onclick = showTip; boxRes.style.cursor = 'pointer'; const testUrl = normalizeAriaRpcUrl(url); const payload = { jsonrpc: '2.0', method: 'aria2.getVersion', id: 'pk_quick_test', params: buildAriaRpcParams(token) }; try { await aria2RpcRequest(testUrl, payload, 3000); dot.className = 'pk-aria-dot ok'; txt.textContent = L.str_connected; boxRes.onclick = null; boxRes.style.cursor = 'default'; } catch (e) { dot.className = 'pk-aria-dot err'; txt.textContent = L.str_conn_fail; boxRes.onclick = showTip; boxRes.style.cursor = 'pointer'; } }; const triggerTest = () => { clearTimeout(testTimer); testTimer = setTimeout(runLiveTest, 600); }; inpU.oninput = (e) => { e.target.style.borderColor = e.target.value.trim() ? 'var(--pk-pri)' : 'var(--pk-bd)'; triggerTest(); }; inpT.oninput = (e) => { e.target.style.borderColor = e.target.value.trim() ? 'var(--pk-pri)' : 'var(--pk-bd)'; triggerTest(); }; if (inpTEye) { inpTEye.onclick = (e) => { e.preventDefault(); e.stopPropagation(); popAriaTokenVisible = !popAriaTokenVisible; inpT.style.webkitTextSecurity = popAriaTokenVisible ? 'none' : 'disc'; inpTEye.innerHTML = popAriaTokenVisible ? CONF.icons.eyeOff : CONF.icons.eye; }; } m.querySelector('#btn_pop_aria_default').onclick = () => { inpU.value = 'http://localhost:6800/jsonrpc'; inpU.style.borderColor = 'var(--pk-pri)'; triggerTest(); }; setTimeout(runLiveTest, 200); m.querySelector('#pop_aria_cancel').onclick = () => { m.remove(); resolve(null); }; m.querySelector('.pk-modal-close').onclick = () => { m.remove(); resolve(null); }; m.querySelector('#pop_aria_save').onclick = async () => { let u = inpU.value.trim(); let t = inpT.value.trim(); if (!u) { inpU.style.borderColor = '#d93025'; return; } if (!/^https?:\/\/|^wss?:\/\//i.test(u)) u = 'http://' + u; const saveBtn = m.querySelector('#pop_aria_save'); const originalTxt = saveBtn.textContent; saveBtn.disabled = true; saveBtn.textContent = L.str_saving; try { u = normalizeAriaRpcUrl(u); const payload = { jsonrpc: '2.0', method: 'aria2.getVersion', id: 'pk_quick_test', params: buildAriaRpcParams(t) }; await aria2RpcRequest(u, payload, 5000); gmSet('pk_aria2_url', u); gmSet('pk_aria2_token', t); m.remove(); resolve({ url: u, token: t }); } catch (err) { const confirmed = await showConfirm( L.msg_aria2_test_fail, L.title_aria2_fail ); if (confirmed) { gmSet('pk_aria2_url', u); gmSet('pk_aria2_token', t); m.remove(); resolve({ url: u, token: t }); } else { saveBtn.disabled = false; saveBtn.textContent = originalTxt; } } }; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#pop_aria_save').click(); } }); }); if (!result) return; ariaUrl = result.url; ariaToken = result.token; } setLoad(true); isGUISensitive = true; const abortCtrl = new AbortController(); const { signal } = abortCtrl; let isRunning = true; UI.stopBtn.onclick = () => { isRunning = false; abortCtrl.abort(); updateLoadTxt(L.str_stopping); }; ariaUrl = normalizeAriaRpcUrl(ariaUrl); const keepAria2Structure = getBoolPref('pk_aria2_keep_structure', CONF.aria2KeepFolderStructure); const allFiles = []; const rootNodes = []; const HYDRATE_LIMIT = 40; const dlFilterRules = getDownloadFilterRules(); const hasDownloadFilterRules = dlFilterRules.hasRules; const stats = { hydratedCount: 0, lastUiTime: 0 }; const filterStats = { scanned: 0, blocked: 0 }; const applyDownloadFilterToFile = (file) => { if (!hasDownloadFilterRules) return false; filterStats.scanned++; if (isDownloadFilterBlocked(file, dlFilterRules)) { filterStats.blocked++; return true; } return false; }; const selectedIdsForAria2 = S.getSelectedIds(); selectedIdsForAria2.forEach(id => { const item = S.itemMap.get(id); if (item) { if (item.kind === 'drive#folder') { rootNodes.push({...item, lineage: [{ id: item.id, name: item.name }], retryCount: 0}); } else if (!applyDownloadFilterToFile(item)) { allFiles.push({ ...item, _lineage: [] }); } } }); let progressTask = null; try { await coreRecursiveEngine(rootNodes, { signal, onFile: (f, parent) => { if (applyDownloadFilterToFile(f)) return; f._lineage = parent.lineage || []; allFiles.push(f); }, onProgress: (st) => { const now = Date.now(); if (now - stats.lastUiTime > 150) { updateLoadTxt(`${L.msg_batch_scanning}\n${L.str_files}: ${allFiles.length} | ${L.str_speed}: ${st.currentConcurrency}`); stats.lastUiTime = now; } } }); if (!isRunning) throw new Error('StoppedByUser'); if (allFiles.length === 0) { setLoad(false); if (hasDownloadFilterRules && filterStats.scanned > 0 && filterStats.blocked === filterStats.scanned) { showToast(L.msg_batch_all_filtered.replace('{n}', filterStats.blocked)); } else { showToast(L.msg_batch_no_files); } return; } if (hasDownloadFilterRules && filterStats.blocked > 0) { showToast(L.msg_batch_filtered.replace('{n}', filterStats.blocked)); } setLoad(false); progressTask = FloatBarManager.create(L.msg_batch_hydrating); const readyFiles =[]; const failedFiles = []; const hydrateQueue = [...allFiles]; const activeTasks = new Set(); const hydrateWithRetry = async (file, maxRetries = 3) => { if (file.web_content_link) return file; if (file.phase === "PHASE_TYPE_PENDING" || file.phase === "PHASE_TYPE_RUNNING" || file.trashed) return null; let lastErr = null; for (let i = 0; i < maxRetries; i++) { if (!isRunning) return null; try { const detail = await apiGet(file.id); if (detail && detail.web_content_link) return detail; if (detail && (detail.phase === "PHASE_TYPE_PENDING" || detail.phase === "PHASE_TYPE_RUNNING")) return null; throw new Error("Link Empty"); } catch (e) { lastErr = e; if (i < maxRetries - 1) await sleep(1000 * (i + 1)); } } throw lastErr; }; while ((hydrateQueue.length > 0 || activeTasks.size > 0) && isRunning) { while (hydrateQueue.length > 0 && activeTasks.size < HYDRATE_LIMIT && isRunning) { const file = hydrateQueue.pop(); const p = (async () => { try { const detail = await hydrateWithRetry(file); if (detail) { detail._lineage = file._lineage; readyFiles.push(detail); } } catch (e) { console.error(`[Hydrate Failed] ${file.name}:`, e); failedFiles.push(file.name + " " + L.str_aria2_fetch_err); } })().finally(() => { activeTasks.delete(p); stats.hydratedCount++; if (progressTask) progressTask.update(`${L.msg_batch_hydrating} ${stats.hydratedCount} / ${allFiles.length}`); }); activeTasks.add(p); } if (activeTasks.size > 0) await Promise.race(activeTasks); } if (!isRunning) throw new Error('StoppedByUser'); if (readyFiles.length > 0 && isRunning) { const zeroByteFiles = readyFiles.filter(isZeroByteFile); if (zeroByteFiles.length > 0) { showToast(L.str_aria2_zero_byte_skipped); for (let i = readyFiles.length - 1; i >= 0; i--) { if (isZeroByteFile(readyFiles[i])) readyFiles.splice(i, 1); } if (!readyFiles.length) return; } const BATCH_SIZE = 50; let successCount = 0; let rpcFatalError = false; for (let i = 0; i < readyFiles.length; i += BATCH_SIZE) { if (!isRunning || rpcFatalError) break; const chunk = readyFiles.slice(i, i + BATCH_SIZE); const sanitize = (s) => s.replace(/[\\/:*?"<>|]/g, '_').trim(); const payload = chunk.map(f => { let relativePrefix = ''; if (keepAria2Structure && f._lineage && f._lineage.length > 0) { relativePrefix = f._lineage.map(n => sanitize(n.name)).join('/') + '/'; } const outPath = relativePrefix + sanitize(f.name); return { jsonrpc: '2.0', method: 'aria2.addUri', id: `pk_${Date.now()}_${Math.random().toString(16).slice(2)}`, params: buildAriaRpcParams(ariaToken, [[rewriteDownloadLinkForAccel(f.web_content_link, 'aria2')], { out: outPath, header: [`User-Agent: ${navigator.userAgent}`, `Referer: https://mypikpak.com/`] }]) }; }); try { await aria2RpcRequest(ariaUrl, payload, Math.max(10000, chunk.length * 800)); successCount += chunk.length; } catch (rpcErr) { console.error("[RPC Batch Error]", rpcErr); chunk.forEach(f => failedFiles.push(f.name + " " + L.str_aria2_rpc_err)); if (rpcErr.message === "Network Error" || rpcErr.message === "Timeout") { console.warn("[RPC Circuit Breaker] Aria2 disconnected. Aborting remaining batches."); rpcFatalError = true; const remainingFiles = readyFiles.slice(i + BATCH_SIZE); remainingFiles.forEach(f => failedFiles.push(f.name + " " + L.str_aria2_aborted)); } } if (progressTask) progressTask.update(`${L.msg_aria2_sending_batch} ${successCount} / ${readyFiles.length}`); } if (failedFiles.length > 0) { let failListText = failedFiles.slice(0, 10).join('\n'); if (failedFiles.length > 10) { failListText += '\n...'; failListText += L.msg_aria2_batch_fail_log; } await showAlert(`${L.str_failed} ${failedFiles.length} ${L.str_items}\n\n${failListText}`, L.title_alert); if (failedFiles.length > 10) { try { const blob = new Blob([failedFiles.join('\r\n')], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); const now = new Date(); const dateStr = now.toISOString().replace(/[:.]/g, '-').slice(0, 19); a.href = url; a.download = `${L.str_aria2_fail_file_name}_${dateStr}.txt`; document.body.appendChild(a); a.click(); setTimeout(() => { if (a.parentNode) document.body.removeChild(a); URL.revokeObjectURL(url); }, 1000); } catch (exportErr) { console.error("[Export Error] Failed to generate failure log:", exportErr); } } } else { showToast(L.msg_aria2_sent.replace('{n}', successCount)); } } } catch (e) { if (e.message !== 'StoppedByUser' && e.name !== 'AbortError') { showAlert(`${L.msg_aria2_check_fail}\n(${e.message})`); } } finally { setLoad(false); isGUISensitive = false; if (progressTask) progressTask.destroy(); } }; const ensureItemMap = () => { S.itemMap.clear(); const len = S.items.length; for (let i = 0; i < len; i++) { const item = S.items[i]; if (item && item.id) { S.itemMap.set(item.id, item); } } }; const executeBatchDelete = async (ids, options = {}) => { const { silent = false, deleteFiles = false, isTask = false, forceRefresh = false, hardDelete = false, explicitItems = [] } = options; if (!ids || ids.length === 0) return; const tempItemLookup = new Map(); if (S.itemMap) S.itemMap.forEach((v, k) => tempItemLookup.set(k, v)); if (explicitItems && explicitItems.length > 0) { explicitItems.forEach(item => { if (item && item.id) tempItemLookup.set(item.id, item); }); } const BATCH_SIZE = 200; const progressTask = FloatBarManager.create(L.str_deleting); const updateFloat = progressTask.update; S.movingSourceId = S.path[S.path.length - 1].id || 'root'; S.movingDestId = 'trash'; const allLockedIdsArray =[]; ids.forEach(id => { S.movingIds.add(id); allLockedIdsArray.push(id); if (isTask && deleteFiles) { const taskItem = tempItemLookup.get(id); if (taskItem && taskItem.file_id) { S.movingIds.add(taskItem.file_id); allLockedIdsArray.push(taskItem.file_id); } } }); if (S.broadcast) S.broadcast.postMessage({ type: 'LOCK_ADD', ids: allLockedIdsArray, src: S.movingSourceId, dst: S.movingDestId }); if (typeof updateGlobalLockCSS === 'function') updateGlobalLockCSS(); isGUISensitive = true; S.sel.clear(); S.lastSelIdx = -1; S.activeId = null; try { const totalToDelete = ids.length; let deletedCount = 0; const affectedParentIds = new Set(); const deletedSet = new Set(); affectedParentIds.add(S.path[S.path.length - 1].id || 'root'); for (let i = 0; i < totalToDelete; i += BATCH_SIZE) { const chunk = ids.slice(i, i + BATCH_SIZE); let retry = 0; const maxRetries = 3; let success = false; while (retry < maxRetries && !success) { try { if (isTask) { await apiCancelTask(chunk, deleteFiles); } else { const action = hardDelete ? 'files:batchDelete' : 'files:batchTrash'; const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/${action}`, { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: chunk }) }); if (!res.ok) throw new Error(`API ${res.status}`); } success = true; } catch (err) { retry++; console.warn(`[Delete] Retry ${retry}/${maxRetries}`); updateFloat(`${L.str_deleting} (Retry ${retry})...`); await sleep(1000 * retry); if (retry >= maxRetries) throw err; } } if (!isTask && chunk.length > 0) { const lastId = chunk[chunk.length - 1]; let verifyRetries = 0; while (verifyRetries < 20) { try { const meta = await apiGet(lastId); if (!hardDelete && meta.trashed) break; } catch (e) { break; } updateFloat(`${L.str_deleting} ${deletedCount}/${totalToDelete}`); verifyRetries++; await sleep(500); } } const chunkSet = new Set(chunk); const chunkLockedIds =[]; chunk.forEach(id => { S.movingIds.delete(id); chunkLockedIds.push(id); const it = tempItemLookup.get(id); let physicalFolderId = null; if (it) { if (it.kind === 'drive#folder') { physicalFolderId = it.id; } else if (isTask && deleteFiles && it.file_id) { const isFolderTask = (it.mime_type && (it.mime_type.includes('folder') || it.mime_type.includes('directory'))) || (it.icon_link && it.icon_link.includes('folder')) || (typeof globalCache !== 'undefined' && globalCache.has(it.file_id)); if (isFolderTask) { physicalFolderId = it.file_id; } } } if (physicalFolderId && typeof globalCache !== 'undefined') { const purgeDescendants = (fid) => { const data = globalCache.get(fid) || (S.cache ? S.cache.get(fid) : null); if (data) { globalTombstoneCache.set(fid, Array.isArray(data) ?[...data] : {...data}); const list = Array.isArray(data) ? data : (data.items ||[]); list.forEach(child => { deletedSet.add(child.id); S.itemMap.delete(child.id); if (child.kind === 'drive#folder') { purgeDescendants(child.id); } }); globalCache.delete(fid); if (S.cache) S.cache.delete(fid); } }; purgeDescendants(physicalFolderId); } if (isTask && deleteFiles && it && it.file_id) { S.movingIds.delete(it.file_id); chunkLockedIds.push(it.file_id); deletedSet.add(it.file_id); if (typeof globalParentIndex !== 'undefined' && globalParentIndex.has(it.file_id)) { const parentInfo = globalParentIndex.get(it.file_id); if (parentInfo && parentInfo.id) { affectedParentIds.add(parentInfo.id === 'root' ? '' : parentInfo.id); } } affectedParentIds.add('root'); affectedParentIds.add(''); } if (it && S.analyzeMap) { const analyzeTargetId = physicalFolderId || id; const analyzeIdsToRemove = new Set([analyzeTargetId, id]); const queue = [analyzeTargetId, id]; while (queue.length > 0) { const currId = queue.shift(); S.analyzeMap.forEach((node, nId) => { if (node.parentId === currId && !analyzeIdsToRemove.has(nId)) { analyzeIdsToRemove.add(nId); queue.push(nId); } }); } analyzeIdsToRemove.forEach(remId => { if (S.analyzeMap.has(remId)) S.analyzeMap.delete(remId); }); const lostSize = parseInt(it.size || 0); if (lostSize > 0) { let currPid = it.parent_id; if (!currPid && isTask && typeof globalParentIndex !== 'undefined') { const pInfo = globalParentIndex.get(it.file_id); if (pInfo) currPid = pInfo.id; } let safety = 50; while (currPid && S.analyzeMap.has(currPid) && safety > 0) { const pNode = S.analyzeMap.get(currPid); pNode.size = Math.max(0, pNode.size - lostSize); currPid = pNode.parentId; safety--; } } if (S.analyzeSimGroups) { S.analyzeSimGroups.forEach(group => { group.ids = group.ids.filter(gid => !analyzeIdsToRemove.has(gid)); }); S.analyzeSimGroups = S.analyzeSimGroups.filter(group => group.ids.length >= 2); clearGroupedGridMetaCache(); clearGridStableRows(UI.in); if (S.analyzeSimGroups.length === 0) { setTimeout(() => { if (UI.btnExit) UI.btnExit.click(); }, 500); } } if (S.analyzeResultItems) { S.analyzeResultItems = S.analyzeResultItems.filter(x => !analyzeIdsToRemove.has(x.id)); S.analyzeResultItems.forEach(resItem => { if (S.analyzeMap.has(resItem.id)) { resItem.size = S.analyzeMap.get(resItem.id).size.toString(); } }); } } if (it && it.parent_id) affectedParentIds.add(it.parent_id); S.itemMap.delete(id); deletedSet.add(id); }); if (S.broadcast) S.broadcast.postMessage({ type: 'LOCK_REM', ids: chunkLockedIds }); if (typeof updateGlobalLockCSS === 'function') updateGlobalLockCSS(); S.items = S.items.filter(x => !deletedSet.has(x.id)); if (typeof pkState !== 'undefined' && pkState && pkState.lastGlobalResults) { pkState.lastGlobalResults = pkState.lastGlobalResults.filter(x => !deletedSet.has(x.id)); } if (typeof globalCache !== 'undefined') { const cleanListChunk = (raw) => { if (Array.isArray(raw)) return raw.filter(f => !deletedSet.has(f.id)); if (raw && Array.isArray(raw.items)) { raw.items = raw.items.filter(f => !deletedSet.has(f.id)); return raw; } return raw; }; for (const key of globalCache.keys()) { globalCache.set(key, cleanListChunk(globalCache.get(key))); } for (const key of S.cache.keys()) { S.cache.set(key, cleanListChunk(S.cache.get(key))); } } if (!forceRefresh) { if (S.dupMode) renderDupView(); else refresh(); } await new Promise(r => requestAnimationFrame(r)); deletedCount += chunk.length; updateFloat(`${L.str_deleting} ${Math.min(deletedCount, totalToDelete)} / ${totalToDelete}`); if (deletedCount < totalToDelete) await sleep(50); } const cur = S.path[S.path.length - 1]; if (cur && cur.id) gmSet('pk_fmod_' + cur.id, new Date(getServerNow()).toISOString()); if (typeof globalCache !== 'undefined') { const cleanList = (raw) => { if (Array.isArray(raw)) return raw.filter(f => !deletedSet.has(f.id)); if (raw && Array.isArray(raw.items)) { raw.items = raw.items.filter(f => !deletedSet.has(f.id)); return raw; } return raw; }; for (const key of globalCache.keys()) { globalCache.set(key, cleanList(globalCache.get(key))); } for (const key of S.cache.keys()) { S.cache.set(key, cleanList(S.cache.get(key))); } affectedParentIds.forEach(pid => { const keysToCheck = (pid === 'root' || pid === '') ? ['root', ''] : [pid]; keysToCheck.forEach(key => { if (typeof globalDirtyFolders !== 'undefined') globalDirtyFolders.add(key); }); }); globalCache.delete('root_trashed'); S.cache.delete('root_trashed'); if (typeof runBackgroundCrawler === 'function') runBackgroundCrawler(); } if (window.pkSmartRefreshTrigger) window.pkSmartRefreshTrigger(); if (forceRefresh) { await load(false, true); } else { updateStat(); setTimeout(() => updateQuotaUI(), 2000); } if (!silent && !S.dupMode) { showToast(isTask ? L.msg_task_deleted : L.msg_del_items_done.replace('{n}', deletedCount)); } } catch (e) { console.error(e); showAlert(`${L.str_error}: ${e.message}`); } finally { if (typeof allLockedIdsArray !== 'undefined' && allLockedIdsArray.length > 0) { allLockedIdsArray.forEach(id => S.movingIds.delete(id)); if (S.broadcast) S.broadcast.postMessage({ type: 'LOCK_REM', ids: allLockedIdsArray }); } else if (ids && ids.length > 0) { ids.forEach(id => S.movingIds.delete(id)); if (S.broadcast) S.broadcast.postMessage({ type: 'LOCK_REM', ids: ids }); } if (typeof updateGlobalLockCSS === 'function') updateGlobalLockCSS(); if (S.movingIds.size === 0) { isGUISensitive = false; } if (progressTask) progressTask.destroy(); } }; if (UI.btnClearHistoryAll) { UI.btnClearHistoryAll.onclick = () => { clearAllPlayHistory(); }; } UI.btnDel.onclick = async () => { const selectedIds = S.getSelectedIds(); const count = selectedIds.length; if (!count) return; if (S.historyMode) { if (!await showConfirm(L.warn_clear_history.replace('{n}', count))) return; const eventIds = []; selectedIds.forEach(id => { const item = S.itemMap.get(id); const eventId = item ? (item._history_event_id || item.officialEventId || item.eventId || '') : ''; if (eventId) eventIds.push(eventId); }); if (eventIds.length === 0) { showToast(L.err_official_history_delete, 'error'); return; } const ok = await deleteOfficialPlayHistoryEvents(eventIds); if (!ok) { showToast(L.err_official_history_delete, 'error'); return; } if (!S._historyDeletedEventIds) S._historyDeletedEventIds = new Set(); eventIds.forEach(id => S._historyDeletedEventIds.add(String(id))); const selIds = new Set(selectedIds); selIds.forEach(id => { S.itemMap.delete(id); }); S.items = S.items.filter(it => !selIds.has(it.id)); if (typeof globalCache !== 'undefined') { globalCache.delete('history_root'); globalCache.delete('history_session'); } S.cache.delete('history_root'); S.clearSelection(); refresh(); updateStat(); showToast(L.msg_clear_history_done); return; } if (S.offlineMode) { const html = `

${L.title_del_task_confirm_fmt.replace('{n}', count)}

`; if (typeof m !== 'undefined' && m.remove) m.remove(); const taskModal = showModal(html); const modalBox = taskModal.querySelector('.pk-modal'); if (modalBox) { modalBox.style.width = "420px"; modalBox.style.padding = "24px"; modalBox.style.borderRadius = "12px"; } const close = () => taskModal.remove(); taskModal.querySelector('#del_task_cancel').onclick = close; const closeBtn = taskModal.querySelector('.pk-modal-close'); if(closeBtn) closeBtn.onclick = close; taskModal.querySelector('#del_task_confirm').onclick = async () => { const isDeleteFile = taskModal.querySelector('#del_task_files').checked; taskModal.remove(); await executeBatchDelete(selectedIds, { isTask: true, deleteFiles: isDeleteFile, forceRefresh: true }); }; taskModal.tabIndex = 0; setTimeout(() => taskModal.focus(), 10); taskModal.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); taskModal.querySelector('#del_task_confirm').click(); } }); return; } ensureItemMap(); const totalCount = selectedIds.length; let deleteCheckLoadTimer = null; let deleteCheckLoadShown = false; const clearDeleteCheckLoad = () => { if (deleteCheckLoadTimer) { clearTimeout(deleteCheckLoadTimer); deleteCheckLoadTimer = null; } if (deleteCheckLoadShown) { setLoad(false); deleteCheckLoadShown = false; } }; deleteCheckLoadTimer = setTimeout(() => { deleteCheckLoadTimer = null; deleteCheckLoadShown = true; setLoad(true); updateLoadTxt(`${L.str_checking_bl}\n${processed} / ${totalCount}`); }, 120); const blSet = S.blSet; const blFolderSet = S.blFolderSet; const toDeleteIds = []; let blacklistedCount = 0; let processed = 0; let lastYieldTime = performance.now(); for (const id of selectedIds) { const item = S.itemMap.get(id); if (!item) { processed++; continue; } if (!S.trashMode && isSystemItem(item)) { processed++; continue; } const lowerName = item.name.toLowerCase().trim(); const isFolder = item.kind === 'drive#folder'; const isProtectedMode = gmGet('pk_skip_bl_on_del', true); if (isProtectedMode && (isFolder ? blFolderSet.has(lowerName) : blSet.has(lowerName))) { blacklistedCount++; } else { toDeleteIds.push(id); } processed++; if ((processed % 500) === 0) { const now = performance.now(); if (now - lastYieldTime > 12) { if (deleteCheckLoadShown) updateLoadTxt(`${L.str_checking_bl}\n${processed} / ${totalCount}`); await sleep(0); lastYieldTime = performance.now(); } } } clearDeleteCheckLoad(); if (blacklistedCount > 0) { showToast(L.msg_del_protected.replace('{n}', blacklistedCount)); } if (blacklistedCount > 0 && toDeleteIds.length === 0) { S.clearSelection(); refresh(); updateStat(); return; } if (toDeleteIds.length === 0) { showAlert(L.msg_del_none); return; } const delRes = await showDeleteConfirm(L.warn_del.replace('{n}', toDeleteIds.length)); if (!delRes.confirm) return; await executeBatchDelete(toDeleteIds, { hardDelete: delRes.hardDelete }); }; UI.btnDeselect.onclick = () => { S.clearSelection(); refresh(); }; const processBlacklistAction = async (action) => { if (S.updateBlCache) S.updateBlCache(); const selectedItems = getSelectedBlacklistItems(); const totalSelected = selectedItems.length; if (totalSelected === 0) return; const allSelectedAlreadyAdded = selectedItems.every(isItemInBlacklist); const isRemove = allSelectedAlreadyAdded && action !== 'add'; const progressTask = FloatBarManager.create(L.str_init_op); const updateFloat = progressTask.update; let isRunning = true; UI.stopBtn.onclick = () => { isRunning = false; updateFloat(L.str_stopping); }; await sleep(16); const parseList = (str) => str ? str.split(/[\r\n]+/).map(s => s.trim()).filter(s => s) : []; const fileListStr = gmGet('pk_blacklist', ''); const folderListStr = gmGet('pk_blacklist_folders', ''); let currentFiles = parseList(fileListStr); let currentFolders = parseList(folderListStr); const targetFileKeys = new Set(); const targetFolderKeys = new Set(); const toAddFiles = []; const toAddFolders = []; let processedCount = 0; let lastYieldTime = performance.now(); const len = selectedItems.length; for (let i = 0; i < len; i++) { if (!isRunning) break; const item = selectedItems[i]; const name = getBlacklistItemName(item); const key = getBlacklistCleanKey(name); if (isBlacklistFolderItem(item)) { targetFolderKeys.add(key); if (!isRemove) toAddFolders.push(name); } else { targetFileKeys.add(key); if (!isRemove) toAddFiles.push(name); } processedCount++; if ((processedCount & 63) === 0) { const now = performance.now(); if (now - lastYieldTime > 12) { updateFloat(`${L.str_analyzing} ${processedCount} / ${totalSelected}`); await sleep(0); lastYieldTime = performance.now(); } } } if (!isRunning) { progressTask.destroy(); showAlert(L.msg_bl_stop); return; } updateFloat(L.str_processing); await sleep(10); let finalCount = 0; let dataChanged = false; if (isRemove) { const oldFileCount = currentFiles.length; const oldFolderCount = currentFolders.length; currentFiles = currentFiles.filter(name => !targetFileKeys.has(getBlacklistCleanKey(name))); currentFolders = currentFolders.filter(name => !targetFolderKeys.has(getBlacklistCleanKey(name))); if (oldFileCount !== currentFiles.length || oldFolderCount !== currentFolders.length) { dataChanged = true; finalCount = (oldFileCount - currentFiles.length) + (oldFolderCount - currentFolders.length); } } else { const existingFileKeys = new Set(currentFiles.map(s => getBlacklistCleanKey(s))); const existingFolderKeys = new Set(currentFolders.map(s => getBlacklistCleanKey(s))); let addedCount = 0; for (const name of toAddFiles) { const key = getBlacklistCleanKey(name); if (!existingFileKeys.has(key)) { currentFiles.push(name); existingFileKeys.add(key); addedCount++; dataChanged = true; } } for (const name of toAddFolders) { const key = getBlacklistCleanKey(name); if (!existingFolderKeys.has(key)) { currentFolders.push(name); existingFolderKeys.add(key); addedCount++; dataChanged = true; } } finalCount = addedCount; } if (dataChanged) { updateFloat(L.str_saving); await sleep(10); gmSet('pk_blacklist', currentFiles.join('\n')); gmSet('pk_blacklist_folders', currentFolders.join('\n')); S.updateBlCache(); renderVisible(); } progressTask.destroy(); const msgTemplate = isRemove ? L.msg_bl_remove_done : L.msg_bl_add_done; showToast(msgTemplate.replace('{n}', finalCount)); }; UI.btnBlacklistManager.onclick = showBlacklistModal; if (UI.btnTrashBlacklistManager) UI.btnTrashBlacklistManager.onclick = showBlacklistModal; if (UI.uploadWrap && UI.btnUpload) { const uploadMenu = UI.uploadWrap.querySelector('.pk-dropdown-menu'); const restoreUploadMenu = () => { if (!uploadMenu) return; if (uploadMenu._pkOriginParent && uploadMenu.parentNode !== uploadMenu._pkOriginParent) { uploadMenu._pkOriginParent.appendChild(uploadMenu); } uploadMenu.style.position = ''; uploadMenu.style.top = ''; uploadMenu.style.left = ''; uploadMenu.style.right = ''; uploadMenu.style.bottom = ''; uploadMenu.style.marginTop = ''; uploadMenu.style.zIndex = ''; uploadMenu.style.minWidth = ''; uploadMenu.style.visibility = ''; uploadMenu.style.pointerEvents = ''; uploadMenu.style.zoom = ''; uploadMenu.style.transformOrigin = ''; delete uploadMenu.dataset.pkPortal; }; const closeUploadMenu = () => { if (!uploadMenu) return; if (uploadMenu._pkPlaceRaf1) cancelAnimationFrame(uploadMenu._pkPlaceRaf1); if (uploadMenu._pkPlaceRaf2) cancelAnimationFrame(uploadMenu._pkPlaceRaf2); uploadMenu._pkPlaceRaf1 = 0; uploadMenu._pkPlaceRaf2 = 0; uploadMenu.style.display = 'none'; restoreUploadMenu(); UI.uploadWrap.classList.remove('active'); }; window.__pkCloseUploadMenu = closeUploadMenu; const placeUploadMenu = () => { if (!uploadMenu) return; if (!uploadMenu._pkOriginParent) uploadMenu._pkOriginParent = UI.uploadWrap; const isGridView = !!(UI.win && UI.win.classList.contains('pk-grid-view')); if (!isGridView) { if (uploadMenu.parentNode !== uploadMenu._pkOriginParent) { uploadMenu._pkOriginParent.appendChild(uploadMenu); } uploadMenu.classList.toggle('pk-dark', !!(el && el.classList.contains('pk-dark'))); uploadMenu.style.position = 'absolute'; uploadMenu.style.top = '100%'; uploadMenu.style.right = '0'; uploadMenu.style.left = 'auto'; uploadMenu.style.bottom = 'auto'; uploadMenu.style.marginTop = '6px'; uploadMenu.style.zIndex = ''; uploadMenu.style.minWidth = ''; uploadMenu.style.visibility = ''; uploadMenu.style.pointerEvents = ''; delete uploadMenu.dataset.pkPortal; return; } if (uploadMenu.parentNode !== document.body) { document.body.appendChild(uploadMenu); } uploadMenu.classList.toggle('pk-dark', !!(el && el.classList.contains('pk-dark'))); const btnRect = typeof getLogicalRect === 'function' ? getLogicalRect(UI.btnUpload) : UI.btnUpload.getBoundingClientRect(); const pad = 8; uploadMenu.dataset.pkPortal = '1'; uploadMenu.style.position = 'fixed'; uploadMenu.style.left = '0'; uploadMenu.style.top = '0'; uploadMenu.style.right = 'auto'; uploadMenu.style.bottom = 'auto'; uploadMenu.style.marginTop = '0'; uploadMenu.style.zIndex = '10060'; uploadMenu.style.transformOrigin = 'top right'; uploadMenu.style.minWidth = Math.max(Math.round(btnRect.width), 140) + 'px'; uploadMenu.style.display = 'flex'; uploadMenu.style.visibility = 'hidden'; uploadMenu.style.pointerEvents = 'none'; const menuRect = uploadMenu.getBoundingClientRect(); const menuW = menuRect.width; const menuH = menuRect.height; const winW = window.innerWidth; const winH = window.innerHeight; let left = btnRect.right - menuW; let top = btnRect.bottom + 6; if (left < pad) left = pad; if (left + menuW > winW - pad) { left = winW - pad - menuW; } if (top + menuH > winH - pad) { top = btnRect.top - menuH - 6; } if (top < pad) top = pad; uploadMenu.style.left = Math.round(left) + 'px'; uploadMenu.style.top = Math.round(top) + 'px'; uploadMenu.style.visibility = ''; uploadMenu.style.pointerEvents = ''; }; const requestPlaceUploadMenu = () => { if (!uploadMenu) return; if (uploadMenu.style.display !== 'flex') return; if (!UI.uploadWrap.classList.contains('active')) return; if (uploadMenu._pkPlaceRaf1) cancelAnimationFrame(uploadMenu._pkPlaceRaf1); if (uploadMenu._pkPlaceRaf2) cancelAnimationFrame(uploadMenu._pkPlaceRaf2); uploadMenu._pkPlaceRaf1 = requestAnimationFrame(() => { uploadMenu._pkPlaceRaf2 = requestAnimationFrame(() => { uploadMenu._pkPlaceRaf1 = 0; uploadMenu._pkPlaceRaf2 = 0; if (!uploadMenu || uploadMenu.style.display !== 'flex') return; if (!UI.uploadWrap.classList.contains('active')) return; placeUploadMenu(); }); }); }; window.__pkRequestPlaceUploadMenu = requestPlaceUploadMenu; if (uploadMenu && !uploadMenu.dataset.pkBindStop) { uploadMenu.addEventListener('click', (e) => e.stopPropagation()); uploadMenu.dataset.pkBindStop = '1'; } if (!window.__pkUploadMenuPortalBound) { window.addEventListener('resize', () => { requestPlaceUploadMenu(); }, { passive: true }); document.addEventListener('scroll', () => { closeUploadMenu(); }, true); if (window.visualViewport) { window.visualViewport.addEventListener('resize', () => { requestPlaceUploadMenu(); }, { passive: true }); window.visualViewport.addEventListener('scroll', () => { requestPlaceUploadMenu(); }, { passive: true }); } document.addEventListener('mousedown', (e) => { if (!uploadMenu || uploadMenu.style.display !== 'flex') return; const target = e.target; if (uploadMenu.contains(target)) return; if (UI.btnUpload.contains(target)) return; if (UI.uploadWrap.contains(target) && !uploadMenu.dataset.pkPortal) return; closeUploadMenu(); }, true); window.__pkUploadMenuPortalBound = true; } if (window.ResizeObserver && !UI.uploadWrap.__pkUploadMenuRO) { UI.uploadWrap.__pkUploadMenuRO = new ResizeObserver(() => { requestPlaceUploadMenu(); }); UI.uploadWrap.__pkUploadMenuRO.observe(UI.uploadWrap); } UI.btnUpload.onclick = (e) => { e.stopPropagation(); if (!isRealHomeUploadView()) { syncLocalUploadVisibility(); return; } const isActive = uploadMenu && uploadMenu.style.display === 'flex' && UI.uploadWrap.classList.contains('active'); document.querySelectorAll('.pk-dropdown-menu, .pk-select-menu').forEach(m => m.style.display = 'none'); document.querySelectorAll('.pk-dropdown-wrap').forEach(w => w.classList.remove('active')); if (isActive) { closeUploadMenu(); return; } if (!uploadMenu) return; uploadMenu.style.display = 'flex'; placeUploadMenu(); UI.uploadWrap.classList.add('active'); }; UI.actUpFile.onclick = (e) => { e.stopPropagation(); closeUploadMenu(); UI.inpFile.click(); }; UI.actUpFolder.onclick = (e) => { e.stopPropagation(); closeUploadMenu(); UI.inpFolder.click(); }; const updateRowUI = (task) => { if (document.hidden) return; const row = document.querySelector(`.pk-row[data-id="${task.id}"]`); if (row) { const progBar = row.querySelector('.pk-up-prog-bar'); const progTxt = row.querySelector('.pk-up-prog-txt'); const spdTxt = row.querySelector('.pk-up-spd'); const statusCol = row.children[4]; const msgSpan = statusCol ? statusCol.querySelector('span:first-child') : null; if (progBar) progBar.style.width = task.progress + '%'; if (progTxt) progTxt.textContent = Math.floor(task.progress) + '%'; if (msgSpan) { msgSpan.textContent = task.message; const activeStatus = ['UPLOADING', 'HASHING', 'WAITING', 'RUNNING']; if (activeStatus.includes(task.status)) { msgSpan.style.color = 'var(--pk-pri)'; if (progBar) progBar.style.backgroundColor = 'var(--pk-pri)'; } } if (spdTxt) spdTxt.innerHTML = task.status === 'DONE' ? `${L.lbl_done_check}` : S.upMng.fmtSpeed(task.speed); } }; S.upMng = { limit: 3, running: 0, store: null, initStore: () => {}, saveTask: (task) => {}, removeTask: (id) => {}, fmtSpeed: (bytesPerSec) => { if (bytesPerSec === 0) return '0 B/s'; const units = ['B/s', 'KB/s', 'MB/s', 'GB/s']; let i = 0; while (bytesPerSec >= 1024 && i < units.length - 1) { bytesPerSec /= 1024; i++; } return bytesPerSec.toFixed(2) + ' ' + units[i]; }, createTask: (file, parentId) => { const task = { id: 'up_' + Date.now() + '_' + Math.random().toString(36).substr(2), kind: 'pk#upload', file: file, name: file.name, size: file.size, parentId: parentId, status: 'WAITING', progress: 0, speed: 0, message: L.msg_task_waiting, _xhr: null, _lastCalcTime: 0, _lastCalcLoaded: 0, _lastUiTime: 0 }; S.upMng.saveTask(task); return task; }, scheduler: () => { if (S.upMng.running >= S.upMng.limit) return; const waiting = S.uploadTasks.find(t => t.status === 'WAITING'); if (waiting) S.upMng.start(waiting); }, start: async (task) => { if (S.quota && S.quota.limitRaw > 0 && !task.file_id) { const remaining = S.quota.limitRaw - S.quota.usedRaw; if (task.size > remaining) { task.status = 'ERROR'; task.message = L.err_quota_exceeded ; if (S.uploadMode) updateRowUI(task); S.upMng.scheduler(); return; } } S.upMng.running++; if (!task.hash) { task.status = 'HASHING'; task.message = L.msg_task_hashing; if (S.uploadMode) updateRowUI(task); task.hash = await calcSha1(task.file); } const hash = task.hash; S.upMng.saveTask(task); if (!task._totalUploadedBytes) task._totalUploadedBytes = 0; let _lastPollTime = Date.now(); let _lastPollBytes = task._totalUploadedBytes; const speedTimer = setInterval(() => { if (task.status === 'UPLOADING') { const now = Date.now(); const timeDiff = now - _lastPollTime; const bytesDiff = task._totalUploadedBytes - _lastPollBytes; if (timeDiff > 0) { task.speed = Math.max(0, (bytesDiff / timeDiff) * 1000); } _lastPollTime = now; _lastPollBytes = task._totalUploadedBytes; if (S.uploadMode) updateRowUI(task); } }, 1500); const cryptoSign = async (secret, stringToSign) => { const enc = new TextEncoder(); const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-1" }, false, ["sign"]); const signature = await crypto.subtle.sign("HMAC", key, enc.encode(stringToSign)); return btoa(String.fromCharCode(...new Uint8Array(signature))); }; try { let finalParentId = task.parentId; if (finalParentId === 'root' || finalParentId === 'upload_root' || finalParentId === '') { const UPLOAD_FOLDER_NAME = 'My Upload'; const cacheKey = `lock_root_${UPLOAD_FOLDER_NAME}`; if (!S.upMng._syncLocks) S.upMng._syncLocks = new Map(); if (!S.upMng._syncLocks.has(cacheKey)) { const createAction = (async () => { const checkExisting = async (targetName) => { try { const list = await apiList('', 1000, null, null, false, true); const found = list.find(f => f.kind === 'drive#folder' && f.name === targetName); return found ? found.id : null; } catch (e) { return null; } }; let existingId = await checkExisting(UPLOAD_FOLDER_NAME); if (existingId) return existingId; let retry = 0; while (retry < 3) { try { const res = await fetch('https://api-drive.mypikpak.com/drive/v1/files', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ kind: "drive#folder", parent_id: "", name: UPLOAD_FOLDER_NAME }) }); if (res.ok) { const data = await res.json(); if (typeof globalDirtyFolders !== 'undefined') globalDirtyFolders.add(''); if (typeof globalCache !== 'undefined') globalCache.delete(''); if (typeof runBackgroundCrawler === 'function') runBackgroundCrawler(); return data.file.id; } if (res.status === 400 || res.status === 429) { await new Promise(r => setTimeout(r, 1000)); existingId = await checkExisting(UPLOAD_FOLDER_NAME); if (existingId) return existingId; } } catch (e) {} retry++; } existingId = await checkExisting(UPLOAD_FOLDER_NAME); if (existingId) return existingId; throw new Error("My Upload folder creation failed"); })(); S.upMng._syncLocks.set(cacheKey, createAction); } try { finalParentId = await S.upMng._syncLocks.get(cacheKey); task.parentId = finalParentId; } catch (e) { S.upMng._syncLocks.delete(cacheKey); throw e; } } if (task.relativeFolder) { const folderNames = task.relativeFolder.split('/'); let currentPid = (task.parentId === 'root' || task.parentId === 'upload_root') ? '' : (task.parentId || ''); if (!S.upMng._syncLocks) S.upMng._syncLocks = new Map(); for (const name of folderNames) { const cacheKey = `lock_${currentPid}_${name}`; if (!S.upMng._syncLocks.has(cacheKey)) { const createAction = (async () => { const checkExisting = async (pid, targetName) => { try { const list = await apiList(pid, 1000, null, null, false, true); const found = list.find(f => f.kind === 'drive#folder' && f.name === targetName); return found ? found.id : null; } catch (e) { return null; } }; let existingId = await checkExisting(currentPid, name); if (existingId) return existingId; let retry = 0; while (retry < 3) { try { const res = await fetch('https://api-drive.mypikpak.com/drive/v1/files', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ kind: "drive#folder", parent_id: currentPid, name: name }) }); if (res.ok) { const data = await res.json(); return data.file.id; } if (res.status === 400 || res.status === 429) { await new Promise(r => setTimeout(r, 1000)); existingId = await checkExisting(currentPid, name); if (existingId) return existingId; } } catch (e) {} retry++; } existingId = await checkExisting(currentPid, name); if (existingId) return existingId; throw new Error("Folder creation failed"); })(); S.upMng._syncLocks.set(cacheKey, createAction); } try { currentPid = await S.upMng._syncLocks.get(cacheKey); } catch (e) { S.upMng._syncLocks.delete(cacheKey); throw e; } } finalParentId = currentPid; } if (task._deleted) throw new Error("Aborted"); task.status = 'UPLOADING'; task.message = L.msg_task_init_upload; _lastPollTime = Date.now(); _lastPollBytes = 0; const safePid = (finalParentId === 'root' || finalParentId === 'upload_root') ? '' : (finalParentId || ''); let data = null; if (task._initData && task.file_id) { data = task._initData; } else { let res = null; let createRetry = 0; const maxCreateRetries = 5; while (createRetry < maxCreateRetries) { try { res = await fetch('https://api-drive.mypikpak.com/drive/v1/files', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ hash: hash, name: task.name, size: String(task.size), kind: "drive#file", id: "", parent_id: safePid, upload_type: "UPLOAD_TYPE_RESUMABLE", folder_type: "NORMAL", resumable: { provider: "PROVIDER_ALIYUN" } }) }); if (res.status === 429) { const waitMs = 2000 + Math.random() * 2000 * (createRetry + 1); console.warn(`[Upload] Rate limited (429). Retrying in ${Math.round(waitMs)}ms...`); await new Promise(r => setTimeout(r, waitMs)); createRetry++; continue; } if (!res.ok) { const errData = await res.json().catch(() => ({})); const errMsg = errData.error_description || `HTTP ${res.status}`; const isQuotaExceeded = res.status === 400 && (errData.error_code === 12 || errMsg.toLowerCase().includes('quota')); if (isQuotaExceeded) { const quotaErr = new Error(L.err_quota_exceeded); quotaErr.isFatal = true; throw quotaErr; } if (res.status === 404 || errMsg.toLowerCase().includes('not found') || errMsg.toLowerCase().includes('invalid parent')) { if (S.upMng && S.upMng._syncLocks) S.upMng._syncLocks.clear(); throw new Error(L.err_parent_not_found); } throw new Error(errMsg); } break; } catch (e) { console.warn(`[Upload] Init request failed (${createRetry + 1}/${maxCreateRetries}):`, e.message); if (e.isFatal) throw e; createRetry++; if (createRetry >= maxCreateRetries) throw e; await new Promise(r => setTimeout(r, 1500)); } } data = await res.json(); task._initData = data; } let newlyCreatedFileId = null; if (data.file) { if (data.file.id) { task.file_id = data.file.id; newlyCreatedFileId = data.file.id; } if (data.file.name) task.name = data.file.name; if (data.file.thumbnail_link) task.thumbnail_link = data.file.thumbnail_link; if (data.file.icon_link) task.icon_link = data.file.icon_link; } else if (data.task && data.task.file_id) { task.file_id = data.task.file_id; newlyCreatedFileId = data.task.file_id; if (data.task.name) task.name = data.task.name; } else if (data.id) { task.file_id = data.id; newlyCreatedFileId = data.id; if (data.name) task.name = data.name; } const uploadTaskId = (data.task && data.task.id) || data.task_id || data.upload_task_id || (data.resumable && data.resumable.task_id) || ''; if (uploadTaskId) task._uploadTaskId = uploadTaskId; if (newlyCreatedFileId && !task._ghostAdded) { if (typeof window.pkAddGhostFile === 'function') window.pkAddGhostFile(newlyCreatedFileId); task._ghostAdded = true; } S.upMng.saveTask(task); const waitOfficialUploadTaskComplete = async () => { if (!task._uploadTaskId) return true; const maxPoll = 20; for (let i = 1; i <= maxPoll; i++) { if (task._deleted) throw new Error("Aborted"); task.status = 'RUNNING'; task.speed = 0; task.progress = Math.min(99.8, Math.max(99, Number(task.progress) || 99)); task.message = L.msg_wait_server.replace('{c}', i).replace('{t}', maxPoll); S.upMng.saveTask(task); if (S.uploadMode) updateRowUI(task); try { const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/tasks/${encodeURIComponent(task._uploadTaskId)}`, { headers: getHeaders() }); if (res.ok) { const taskData = await res.json().catch(() => null); const phase = (taskData && taskData.phase) || (taskData && taskData.task && taskData.task.phase) || ''; const ref = (taskData && taskData.reference_resource) || (taskData && taskData.task && taskData.task.reference_resource) || {}; if (ref.name) task.name = ref.name; if (ref.mime_type) task.mime_type = ref.mime_type; if (ref.icon_link) task.icon_link = ref.icon_link; if (ref.thumbnail_link) task.thumbnail_link = ref.thumbnail_link; if (phase === 'PHASE_TYPE_COMPLETE') return true; if (phase === 'PHASE_TYPE_ERROR') throw new Error((taskData && (taskData.message || taskData.error_description)) || L.err_unknown); } } catch (e) { if (e.message === "Aborted") throw e; console.warn('[Upload] Task completion poll warning:', e); } await sleep(i < 6 ? 1000 : 2000); } console.warn(`[Upload] Task completion poll timeout, fallback to local completion: ${task.name}`); return true; }; if (task._deleted) { console.warn(`[Upload] Task ${task.id} was deleted by user during initialization. Triggering self-destruct.`); if (newlyCreatedFileId && task._deleteFileIntent) { try { await fetch('https://api-drive.mypikpak.com/drive/v1/files:batchTrash', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: [newlyCreatedFileId] }) }); } catch (err) { console.error(`[Upload] Failed to cleanup ghost file ${newlyCreatedFileId}`, err); } } throw new Error("Aborted"); } if (data.upload_type === "UPLOAD_TYPE_URL" || data.phase === "PHASE_TYPE_COMPLETE" || (data.file && data.file.phase === "PHASE_TYPE_COMPLETE")) { task.status = 'DONE'; task.progress = 100; task.speed = 0; task.message = L.msg_task_fast_success; if (typeof window.pkRemoveGhostFile === 'function' && task.file_id) window.pkRemoveGhostFile(task.file_id); S.upMng.removeTask(task.id); if (S.uploadMode) { updateRowUI(task); if (!S._upRenderScheduled) { S._upRenderScheduled = true; requestAnimationFrame(() => { S._upRenderScheduled = false; if (typeof renderVisible === 'function' && !document.hidden) renderVisible(); }); } } setTimeout(() => updateQuotaUI(), 1000); if (typeof globalCache !== 'undefined') { const targetPid = (task.parentId === 'root' || task.parentId === 'upload_root') ? '' : (task.parentId || ''); if (globalCache.has(targetPid)) { const cacheEntry = globalCache.get(targetPid); const list = Array.isArray(cacheEntry) ? cacheEntry : (cacheEntry.items || []); const newFileStub = { id: task.file_id, kind: 'drive#file', name: task.name, size: task.size, parent_id: targetPid, mime_type: task.mime_type || '', thumbnail_link: task.thumbnail_link || task.icon_link, icon_link: task.icon_link, modified_time: new Date().toISOString(), hash: hash }; if (!list.some(f => f.id === newFileStub.id)) list.push(newFileStub); } } if (typeof globalDirtyFolders !== 'undefined') { const targetPid = task.parentId === 'root' ? '' : (task.parentId || ''); globalDirtyFolders.add(targetPid); if (typeof runBackgroundCrawler === 'function') runBackgroundCrawler(); } if (typeof globalNeedsSync !== 'undefined') globalNeedsSync = true; } else if (data.resumable && data.resumable.params) { const p = data.resumable.params; const objectName = p.key; const ossEncode = (v) => encodeURIComponent(String(v)).replace(/[!'()*]/g, c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`); const objectPath = objectName.split('/').map(ossEncode).join('/'); const parseOssQuery = (query = '') => String(query || '').split('&').filter(Boolean).map(s => { const i = s.indexOf('='); return i < 0 ? [s, null] : [s.slice(0, i), s.slice(i + 1)]; }).sort((a, b) => a[0] < b[0] ? -1 : (a[0] > b[0] ? 1 : 0)); const buildOssQuery = (query = '', encodeValue = false) => parseOssQuery(query).map(([k, v]) => v === null ? k : `${k}=${encodeValue ? ossEncode(v) : v}`).join('&'); const isOssCname = p.cname === true || String(p.cname || '').toLowerCase() === 'true'; const ossEndpoint = String(p.endpoint || '').replace(/^https?:\/\//i, '').replace(/\/+$/g, ''); const host = isOssCname ? `https://${ossEndpoint}` : `https://${p.bucket}.${ossEndpoint}`; const totalSize = task.file.size; const officialPartSize = Number(CONF.uploadPartSizeOfficial) || (1 * 1024 * 1024); const maxPartCount = Number(CONF.uploadPartMaxCount) || 10000; const PART_SIZE = Math.max(officialPartSize, Math.ceil(totalSize / maxPartCount)); const partCount = Math.ceil(totalSize / PART_SIZE); const getAuth = async (method, query = '', contentType = '') => { const dateStr = new Date().toUTCString(); const headersToSign = { 'x-oss-date': dateStr, 'x-oss-security-token': p.security_token }; const canonicalHeaders = Object.keys(headersToSign).sort().map(k => `${k}:${String(headersToSign[k]).trim()}`).join('\n'); const canonicalQuery = buildOssQuery(query, false); const canonicalResource = `/${p.bucket}/${objectPath}${canonicalQuery ? '?' + canonicalQuery : ''}`; const stringToSign = [method.toUpperCase(), "", contentType || "", dateStr, canonicalHeaders, canonicalResource].join("\n"); const signature = await cryptoSign(p.access_key_secret, stringToSign); return { date: dateStr, auth: `OSS ${p.access_key_id}:${signature}` }; }; const ossRequest = (method, query, body, contentType, onProgress) => { return new Promise(async (resolve, reject) => { try { const creds = await getAuth(method, query, contentType); const urlQuery = buildOssQuery(query, true); const url = `${host}/${objectPath}${urlQuery ? '?' + urlQuery : ''}`; if (!task._xhrs) task._xhrs = new Set(); const reqHeaders = { 'Authorization': creds.auth, 'x-oss-date': creds.date, 'x-oss-security-token': p.security_token }; if (contentType) reqHeaders['Content-Type'] = contentType; const req = GM_xmlhttpRequest({ method: method, url: url, data: body, headers: reqHeaders, upload: { onprogress: onProgress }, onload: (res) => { task._xhrs.delete(req); if (res.status >= 200 && res.status < 300) resolve(res); else { const err = new Error(`OSS ${method} Error: ${res.status} ${res.statusText}`); err.status = res.status; err.isOssError = true; err.responseText = res.responseText || ''; reject(err); } }, onerror: (err) => { task._xhrs.delete(req); reject(new Error("Network Error")); }, onabort: () => { task._xhrs.delete(req); reject(new Error("Aborted")); } }); task._xhrs.add(req); if (onProgress) task._xhr = { abort: () => req.abort() }; } catch (e) { reject(e); } }); }; task.message = L.msg_task_uploading; const fileContentType = task.file.type || 'application/octet-stream'; if (partCount <= 1) { await ossRequest('PUT', '', task.file, fileContentType, (pe) => { task._totalUploadedBytes = pe.loaded; task.progress = (pe.loaded / totalSize) * 100; }); task.progress = 100; } else { task.message = L.msg_task_init_part; if (S.uploadMode) updateRowUI(task); let uploadId = task._uploadId; let isResuming = !!uploadId; if (!uploadId) { const initRes = await ossRequest('POST', 'uploads', null, ''); const initXml = new DOMParser().parseFromString(initRes.responseText, "text/xml"); uploadId = initXml.querySelector('UploadId')?.textContent || initXml.getElementsByTagName('UploadId')[0]?.textContent; if (!uploadId) throw new Error("Failed to get UploadId"); task._uploadId = uploadId; } const parts = new Array(partCount); const CONCURRENCY = PART_SIZE <= 1 * 1024 * 1024 ? 5 : (PART_SIZE <= 3 * 1024 * 1024 ? 3 : 1); let completedBytes = 0; const activeParts = new Map(); const uploadedPartNumbers = new Set(); if (isResuming) { try { let nextMarker = ''; let isTruncated = true; while (isTruncated) { const query = `uploadId=${uploadId}` + (nextMarker ? `&part-number-marker=${nextMarker}` : ''); const listRes = await ossRequest('GET', query, null, ''); const listXml = new DOMParser().parseFromString(listRes.responseText, "text/xml"); const partNodes = listXml.querySelectorAll('Part') || listXml.getElementsByTagName('Part'); Array.from(partNodes).forEach(node => { const pNum = parseInt(node.querySelector('PartNumber')?.textContent || node.getElementsByTagName('PartNumber')[0]?.textContent); const etag = node.querySelector('ETag')?.textContent || node.getElementsByTagName('ETag')[0]?.textContent; if (pNum && etag && !uploadedPartNumbers.has(pNum)) { parts[pNum - 1] = { partNumber: pNum, etag: etag }; uploadedPartNumbers.add(pNum); const pSize = (pNum === partCount) ? (totalSize - (partCount - 1) * PART_SIZE) : PART_SIZE; completedBytes += pSize; } }); const truncNode = listXml.querySelector('IsTruncated') || listXml.getElementsByTagName('IsTruncated')[0]; isTruncated = truncNode ? (truncNode.textContent === 'true') : false; if (isTruncated) { const markerNode = listXml.querySelector('NextPartNumberMarker') || listXml.getElementsByTagName('NextPartNumberMarker')[0]; nextMarker = markerNode ? markerNode.textContent : ''; } } task._totalUploadedBytes = completedBytes; task.progress = (completedBytes / totalSize) * 100; } catch (e) { console.warn(`[Upload] Failed to fetch existing parts. Overwriting.`, e); } } const updateProgress = () => { let activeTotal = 0; for (const bytes of activeParts.values()) activeTotal += bytes; const currentTotal = Math.min(totalSize, completedBytes + activeTotal); task._totalUploadedBytes = currentTotal; task.progress = (currentTotal / totalSize) * 100; }; const pool = Array.from({length: partCount}, (_, k) => k + 1).filter(num => !uploadedPartNumbers.has(num)); const worker = async () => { while (pool.length > 0) { if (task.status === 'PAUSED' || !document.body.contains(el)) { activeParts.clear(); throw new Error("Aborted"); } const i = pool.shift(); const startByte = (i - 1) * PART_SIZE; const endByte = Math.min(i * PART_SIZE, totalSize); const chunk = task.file.slice(startByte, endByte); activeParts.set(i, 0); const query = `partNumber=${i}&uploadId=${uploadId}`; try { const partRes = await ossRequest('PUT', query, chunk, fileContentType, (pe) => { activeParts.set(i, pe.loaded); updateProgress(); }); const finalizedSize = chunk.size; completedBytes += finalizedSize; activeParts.delete(i); updateProgress(); const etagHeader = partRes.responseHeaders.match(/etag:\s*"?([^"\r\n]+)"?/i); const etag = etagHeader ? etagHeader[1] : null; if (!etag) throw new Error(`Part ${i} missing ETag`); parts[i - 1] = { partNumber: i, etag: etag }; if (!task._parts) task._parts = []; task._parts[i - 1] = parts[i - 1]; S.upMng.saveTask(task); } catch (err) { activeParts.delete(i); pool.unshift(i); throw err; } } }; task.message = L.msg_task_uploading; if (S.uploadMode) updateRowUI(task); if (pool.length > 0) { const workers = Array(Math.min(pool.length, CONCURRENCY)).fill(0).map(worker); await Promise.all(workers); } if (task._deleted) throw new Error("Aborted"); const xmlBody = `${parts.map(p => `${p.partNumber}${p.etag}`).join('')}`; await ossRequest('POST', `uploadId=${uploadId}`, xmlBody, 'application/xml'); if (task._deleted && task.file_id && task._deleteFileIntent) { fetch('https://api-drive.mypikpak.com/drive/v1/files:batchTrash', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: [task.file_id] }) }).catch(()=>{}); throw new Error("Aborted"); } task.progress = 100; task._totalUploadedBytes = totalSize; setTimeout(() => updateQuotaUI(), 1500); const tryFetchMeta = async (isRetry = false) => { if (task._deleted) return true; try { const meta = await apiGet(task.file_id); if (meta) { if (meta.icon_link) task.icon_link = meta.icon_link; if (meta.mime_type) task.mime_type = meta.mime_type; if (meta.medias) task.medias = meta.medias; const isValidThumb = meta.thumbnail_link && meta.thumbnail_link !== meta.icon_link; if (isValidThumb) { const testUrl = meta.thumbnail_link + (meta.thumbnail_link.includes('?') ? '&' : '?') + '_t=' + Date.now(); const isImageReady = await new Promise(resolve => { const img = new Image(); img.onload = () => resolve(true); img.onerror = () => resolve(false); img.src = testUrl; }); if (isImageReady) { task.thumbnail_link = testUrl; if (typeof globalCache !== 'undefined') { const pid = task.parentId === 'root' ? '' : (task.parentId || ''); if (globalCache.has(pid)) { const list = globalCache.get(pid); const target = Array.isArray(list) ? list.find(f => f.id === task.file_id) : (list.items ? list.items.find(f => f.id === task.file_id) : null); if (target) target.thumbnail_link = testUrl; } } if (isRetry && S.uploadMode) { if (!S._upRenderScheduled) { S._upRenderScheduled = true; requestAnimationFrame(() => { S._upRenderScheduled = false; if (typeof renderVisible === 'function' && !document.hidden) renderVisible(); }); } } return true; } else { return false; } } } } catch(e) { console.warn("[Upload] Meta fetch warning", e); } return false; }; (async () => { if (await tryFetchMeta(false)) return; for (let i = 0; i < 8; i++) { await sleep(3500); if (await tryFetchMeta(true)) return; } for (let i = 0; i < 20; i++) { await sleep(15000); if (await tryFetchMeta(true)) return; } while (true) { await sleep(60000); if (await tryFetchMeta(true)) return; } })(); } await waitOfficialUploadTaskComplete(); task.status = 'DONE'; task.progress = 100; task.speed = 0; task.message = L.msg_task_upload_done; if (typeof window.pkRemoveGhostFile === 'function' && task.file_id) window.pkRemoveGhostFile(task.file_id); S.upMng.removeTask(task.id); if (S.uploadMode) { updateRowUI(task); if (!S._upRenderScheduled) { S._upRenderScheduled = true; requestAnimationFrame(() => { S._upRenderScheduled = false; if (typeof renderVisible === 'function' && !document.hidden) renderVisible(); }); } } if (typeof globalCache !== 'undefined') { const targetPid = (task.parentId === 'root' || task.parentId === 'upload_root') ? '' : (task.parentId || ''); if (globalCache.has(targetPid)) { const cacheEntry = globalCache.get(targetPid); const list = Array.isArray(cacheEntry) ? cacheEntry : (cacheEntry.items || []); const newFileStub = { id: task.file_id, kind: 'drive#file', name: task.name, size: task.size, parent_id: targetPid, mime_type: task.mime_type || '', thumbnail_link: task.thumbnail_link || task.icon_link, icon_link: task.icon_link, modified_time: new Date().toISOString(), hash: hash }; if (!list.some(f => f.id === newFileStub.id)) list.push(newFileStub); } } if (typeof globalDirtyFolders !== 'undefined') { const targetPid = task.parentId === 'root' ? '' : (task.parentId || ''); globalDirtyFolders.add(targetPid); if (typeof runBackgroundCrawler === 'function') runBackgroundCrawler(); } if (typeof globalNeedsSync !== 'undefined') globalNeedsSync = true; } } catch (e) { const isManualAbort = e.message === 'Aborted'; task.status = isManualAbort ? 'PAUSED' : 'ERROR'; task.message = isManualAbort ? L.msg_task_paused : (e.message || L.err_unknown); if (e.status === 403 && task.file_id) { task.message = e.isOssError ? L.msg_oss_upload_forbidden : L.msg_token_expired_retry; task._initData = null; task._uploadId = null; task.progress = 0; task._totalUploadedBytes = 0; fetch('https://api-drive.mypikpak.com/drive/v1/files:batchDelete', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: [task.file_id] }) }).catch(()=>{}); if (typeof window.pkRemoveGhostFile === 'function') window.pkRemoveGhostFile(task.file_id); task.file_id = null; } if (S.uploadMode) updateRowUI(task); S.upMng.saveTask(task); } finally { clearInterval(speedTimer); task._xhr = null; S.upMng.running--; if (S.uploadMode) { refresh(); } S.upMng.scheduler(); } }, pause: (task, skipRender = false) => { if (task.status === 'UPLOADING') { task.status = 'PAUSED'; task.message = L.msg_task_paused; if (task._xhrs && task._xhrs.size > 0) { task._xhrs.forEach(req => req.abort()); task._xhrs.clear(); } if (task._xhr) { task._xhr.abort(); task._xhr = null; } S.upMng.saveTask(task); if (S.uploadMode && !skipRender) { refresh(); } } else if (task.status === 'WAITING') { task.status = 'PAUSED'; task.message = L.msg_task_paused; S.upMng.saveTask(task); if (S.uploadMode && !skipRender) { refresh(); } } }, resume: (task, skipRender = false) => { if (task.status === 'PAUSED' || task.status === 'ERROR') { task.status = 'WAITING'; task.message = L.msg_task_waiting; S.upMng.saveTask(task); S.upMng.scheduler(); if (S.uploadMode && !skipRender) { refresh(); } } } }; S.upMng.initStore(); const handleUploadInput = async (files) => { if (!files || files.length === 0) return; if (S.upMng && S.upMng._syncLocks) S.upMng._syncLocks.clear(); const curPath = S.path[S.path.length - 1]; const isVirtual = curPath.id.startsWith('virtual_') || curPath.id.includes('_root') || curPath.id === 'upload_root'; const safeParentId = (curPath.id && !isVirtual) ? curPath.id : ''; let fileList = Array.from(files); let existingFiles = []; if (typeof globalCache !== 'undefined' && globalCache.has(safeParentId)) { const raw = globalCache.get(safeParentId); existingFiles = Array.isArray(raw) ? raw : (raw.items || []); } else if (!isVirtual && S.items && S.items.length > 0) { existingFiles = S.items; } if (existingFiles.length > 0) { const existingMap = new Set(); existingFiles.forEach(f => { if (f.kind !== 'drive#folder') { existingMap.add(`${f.name}_${f.size}`); } }); let dupCount = 0; const duplicateIndices = new Set(); fileList.forEach((file, index) => { if (file.name.startsWith('.')) return; let relativeFolder = ""; if (file.webkitRelativePath) { const parts = file.webkitRelativePath.split('/'); if (parts.length > 1) { parts.pop(); relativeFolder = parts.join('/'); } } if (!relativeFolder) { const key = `${file.name}_${file.size}`; if (existingMap.has(key)) { dupCount++; duplicateIndices.add(index); } } }); if (dupCount > 0) { const skipDups = await showConfirm(L.msg_upload_dup_confirm.replace('{n}', dupCount)); if (skipDups) { fileList = fileList.filter((_, idx) => !duplicateIndices.has(idx)); } } } if (fileList.length === 0) { UI.inpFile.value = ''; UI.inpFolder.value = ''; return; } let addedCount = 0; const BATCH_SIZE = 50; if (fileList.length > BATCH_SIZE) { showToast(L.msg_parsing_files, 'info', 2000); } for (let i = 0; i < fileList.length; i += BATCH_SIZE) { const batch = fileList.slice(i, i + BATCH_SIZE); await new Promise(resolve => setTimeout(resolve, 0)); for (const file of batch) { if (file.name.startsWith('.')) continue; let relativeFolder = ""; if (file.webkitRelativePath) { const parts = file.webkitRelativePath.split('/'); if (parts.length > 1) { parts.pop(); relativeFolder = parts.join('/'); } } if (S.upMng) { const task = S.upMng.createTask(file, safeParentId); task.relativeFolder = relativeFolder; S.uploadTasks.unshift(task); addedCount++; } } } showToast(L.msg_task_added.replace('{n}', addedCount)); if (!S.uploadMode) { switchTab('upload'); } else { renderVisible(); updateStat(); } if (S.upMng) S.upMng.scheduler(); UI.inpFile.value = ''; UI.inpFolder.value = ''; }; UI.inpFile.onchange = (e) => handleUploadInput(e.target.files); UI.inpFolder.onchange = (e) => handleUploadInput(e.target.files); const dragMask = document.createElement('div'); dragMask.className = 'pk-drag-mask'; UI.win.appendChild(dragMask); const parseEntries = async (entries, relPath = "") => { const BATCH_SIZE = 10; for (let i = 0; i < entries.length; i += BATCH_SIZE) { const batch = entries.slice(i, i + BATCH_SIZE); await Promise.all(batch.map(async (entry) => { if (entry.isFile) { return new Promise((res) => { entry.file(file => { if (!file.name.startsWith('.')) { const curPath = S.path[S.path.length - 1]; const safeParentId = (curPath.id && !curPath.id.includes('_root')) ? curPath.id : ''; const task = S.upMng.createTask(file, safeParentId); task.relativeFolder = relPath; S.uploadTasks.unshift(task); } res(); }, () => res()); }); } else if (entry.isDirectory) { return new Promise((res) => { const reader = entry.createReader(); const readAllEntries = async () => { let allSubEntries = []; let readBatch = async () => { return new Promise((r) => { reader.readEntries((sub) => { if (sub.length > 0) { allSubEntries = allSubEntries.concat(sub); readBatch().then(r); } else { r(); } }, () => r()); }); }; await readBatch(); await parseEntries(allSubEntries, (relPath ? relPath + "/" : "") + entry.name); res(); }; readAllEntries(); }); } })); } }; const canDragUpload = () => { return !S.trashMode && !S.shareMode && !S.shareParseMode && !S.offlineMode && !S.starredMode && !S.recentMode && !S.historyMode && !S.isFlattened && !S.dupMode && !S.analyzeMode && !S.uploadMode; }; let dragCounter = 0; const handleDragGuard = (e) => { e.preventDefault(); e.stopPropagation(); if (canDragUpload()) { e.dataTransfer.dropEffect = 'copy'; } else { e.dataTransfer.dropEffect = 'none'; } }; el.addEventListener('dragenter', (e) => { handleDragGuard(e); if (!canDragUpload()) return; dragCounter++; const curPath = S.path[S.path.length - 1]; const isRoot = S.path.length === 1 && (curPath.id === '' || curPath.id === 'root'); let destHtml = ""; if (isRoot) { const homeIcon = CONF.icons.home.replace('width="24"', 'width="16"').replace('height="24"', 'height="16"').replace('
${L.msg_drag_drop_hint}
${L.lbl_upload_to}${destHtml}
`; dragMask.style.display = 'flex'; }); el.addEventListener('dragover', handleDragGuard); el.addEventListener('dragleave', (e) => { e.preventDefault(); e.stopPropagation(); if (!canDragUpload()) return; dragCounter--; if (dragCounter <= 0) { dragMask.style.display = 'none'; dragCounter = 0; } }); el.addEventListener('drop', async (e) => { handleDragGuard(e); if (!canDragUpload()) return; dragMask.style.display = 'none'; dragCounter = 0; const items = e.dataTransfer.items; if (!items) return; if (S.upMng && S.upMng._syncLocks) S.upMng._syncLocks.clear(); const entries =[]; for (let i = 0; i < items.length; i++) { const entry = items[i].webkitGetAsEntry(); if (entry) entries.push(entry); } if (entries.length > 0) { const curPath = S.path[S.path.length - 1]; const isVirtual = curPath.id.startsWith('virtual_') || curPath.id.includes('_root') || curPath.id === 'upload_root'; const safeParentId = (curPath.id && !isVirtual) ? curPath.id : ''; let existingFiles = []; if (typeof globalCache !== 'undefined' && globalCache.has(safeParentId)) { const raw = globalCache.get(safeParentId); existingFiles = Array.isArray(raw) ? raw : (raw.items || []); } else if (!isVirtual && S.items && S.items.length > 0) { existingFiles = S.items; } if (existingFiles.length > 0) { const existingMap = new Set(); existingFiles.forEach(f => { if (f.kind !== 'drive#folder') { existingMap.add(`${f.name}_${f.size}`); } }); let dupCount = 0; const duplicateIndices = new Set(); const topLevelFiles = []; for (let i = 0; i < entries.length; i++) { const entry = entries[i]; if (entry.isFile) { const file = await new Promise((res) => entry.file(res, () => res(null))); if (file && !file.name.startsWith('.')) { topLevelFiles.push({ file, index: i }); } } } topLevelFiles.forEach(tf => { const key = `${tf.file.name}_${tf.file.size}`; if (existingMap.has(key)) { dupCount++; duplicateIndices.add(tf.index); } }); if (dupCount > 0) { const skipDups = await showConfirm(L.msg_upload_dup_confirm.replace('{n}', dupCount)); if (skipDups) { for (let i = entries.length - 1; i >= 0; i--) { if (duplicateIndices.has(i)) { entries.splice(i, 1); } } } } } if (entries.length === 0) return; await parseEntries(entries); if (!S.uploadMode) switchTab('upload'); else { refresh(); updateStat(); } if (S.upMng) S.upMng.scheduler(); } }); document.addEventListener('click', (e) => { if (UI.uploadWrap && !UI.uploadWrap.contains(e.target)) { UI.uploadWrap.querySelector('.pk-dropdown-menu').style.display = 'none'; UI.uploadWrap.classList.remove('active'); } }); } const switchTab = (mode) => { if (S.historyMode && mode !== 'history') { if (typeof globalCache !== 'undefined') { const historySession = globalCache.get('history_session'); if (historySession) { const hasPending = !historySession.completed && (historySession.cursor || 0) < (((historySession.targetIds || []).length) || 0); const historySnapshot = { items: Array.isArray(S.items) ? [...S.items] : [], nextToken: hasPending ? `history:${historySession.cursor || 0}` : null }; S.cache.set('history_root', historySnapshot); globalCache.set('history_root', historySnapshot); } } } if (S.shareParseMode && mode !== 'shareParse') { S.shareParseReqId = (S.shareParseReqId || 0) + 1; S.shareParseLoading = false; resetShareParseListState(false); } if (S.abortController) S.abortController.abort(); activeLoadId++; S.sortId++; if (S.loading) { S.loading = false; } const isForcedListTab = mode === 'offline' || mode === 'upload' || mode === 'share'; const needListTabSync = isForcedListTab && (S.viewMode !== 'list' || isGridView() || (UI.win && UI.win.classList.contains('pk-grid-view')) || CONF.rowHeight !== getListRowHeight() || (UI.vp && UI.vp.scrollTop > 0)); if (needListTabSync) { beginFolderViewSync(); if (UI.win) { UI.win.classList.add('pk-view-switching'); UI.win.classList.remove('pk-grid-view', 'pk-grid-resizing', 'pk-grid-scrolling'); } S.viewMode = 'list'; CONF.rowHeight = getListRowHeight(); S._gridLayoutKey = ''; clearGroupedGridMetaCache(); if (UI.in) { UI.in.style.height = '0px'; UI.in.style.transform = 'none'; } if (UI.vp) { UI.vp.scrollTop = 0; UI.vp.scrollLeft = 0; } } S.items = []; S.display = []; S.recentResultItems = null; S.itemMap.clear(); S.sel.clear(); if (UI.in) { UI.in.innerHTML = ''; UI.in.style.height = '0px'; UI.in.style.transform = 'none'; } if (UI.vp) { UI.vp.scrollTop = 0; UI.vp.scrollLeft = 0; } if (mode === 'history') { S.sort = 'play_time'; S.dir = 1; } else if (mode === 'offline') { S.sort = 'created_time'; S.dir = 1; } else if (mode === 'recent') { S.sort = 'modified_time'; S.dir = 1; } else if (mode === 'home') { S._sortAppliedForId = null; } else { S.sort = 'modified_time'; } syncFolderFirstFromGlobal(); if (UI.chkGlobal && !S.trashMode && !S.shareMode && !S.shareParseMode && !S.starredMode && !S.offlineMode && !S.historyMode && !S.recentMode && !S.uploadMode) { S.wasGlobalChecked = UI.chkGlobal.checked; } S.trashMode = (mode === 'trash'); S.shareMode = (mode === 'share'); S.shareParseMode = (mode === 'shareParse'); S.starredMode = (mode === 'starred'); S.recentMode = (mode === 'recent'); S.historyMode = (mode === 'history'); S.offlineMode = (mode === 'offline'); S.uploadMode = (mode === 'upload'); const syncModeViewShell = (nextViewMode) => { const modeChanged = S.viewMode !== nextViewMode; if (modeChanged) { beginFolderViewSync(); if (UI.win) UI.win.classList.add('pk-view-switching', 'pk-mode-view-syncing'); } S.viewMode = nextViewMode; S._gridLayoutKey = ''; clearGroupedGridMetaCache(); if (UI.win) UI.win.classList.toggle('pk-grid-view', isGridView()); syncLayoutMetrics(); renderViewSwitch(); if (modeChanged && UI.win) requestAnimationFrame(() => UI.win.classList.remove('pk-mode-view-syncing')); }; if (UI.win) UI.win.classList.toggle('pk-share-parse-mode', !!S.shareParseMode); if (S.shareParseMode) { S.dupMode = false; S.isFlattened = false; S.analyzeMode = false; S.analyzeSimGroups = null; S.scanFilter = null; S.filterState = { active: false, cat: 'all', ext: 'all' }; S.shareParseInsightFilterState = { active: false, cat: 'all', ext: 'all' }; S.preSearchPath = null; S.lastGlobalResults = []; clearShareParseSearch(); if (UI.win) UI.win.classList.remove('pk-analyze-group-tools-on'); if (UI.btnAnaSelect) UI.btnAnaSelect.style.display = 'none'; if (UI.btnAnaSort) UI.btnAnaSort.style.display = 'none'; document.querySelectorAll('.pk-ana-pop, .pk-ana-sort-pop').forEach(p => { p.style.display = 'none'; }); if (UI.chkGlobal) UI.chkGlobal.checked = false; if (UI.btnExit) UI.btnExit.style.display = 'none'; if (UI.lblGlobal) UI.lblGlobal.style.display = 'none'; } if (mode === 'starred' || mode === 'recent' || mode === 'history' || mode === 'trash') { const prefKey = mode === 'trash' ? 'trash_root' : (mode === 'starred' ? 'starred_root' : (mode === 'recent' ? 'recent_root' : 'history_root')); const nextViewMode = (typeof resolvePreferredViewMode === 'function') ? resolvePreferredViewMode(prefKey) : (gmGet('pk_file_view_mode', 'grid') === 'list' ? 'list' : 'grid'); syncModeViewShell(nextViewMode); } S.scanFilter = null; if (UI.chkSearchPath) UI.chkSearchPath.checked = false; let rootName = L.btn_nav_home; if (S.trashMode) rootName = L.btn_nav_trash; if (S.shareMode) rootName = L.btn_nav_share; if (S.shareParseMode) rootName = L.title_share_parse; if (S.starredMode) rootName = L.btn_nav_starred; if (S.recentMode) rootName = L.btn_nav_recent; if (S.historyMode) rootName = L.btn_nav_history; if (S.offlineMode) rootName = L.title_offline; if (S.uploadMode) rootName = L.btn_nav_upload; if (S.shareParseMode) { S.path = [{ id: 'share_parse_root', name: rootName }]; } else if (S.offlineMode) { S.path = [{ id: 'offline_root', name: rootName }]; } else if (S.uploadMode) { S.path = [{ id: 'upload_root', name: rootName }]; } else if (S.trashMode) { S.path = [{ id: 'trash_root', name: rootName }]; } else if (S.recentMode) { S.path = [{ id: 'recent_root', name: rootName }]; } else if (S.historyMode) { S.path = [{ id: 'history_root', name: rootName }]; } else { S.path = [{ id: '', name: rootName }]; } if (mode === 'home') { const nextViewMode = (typeof resolvePreferredViewMode === 'function') ? resolvePreferredViewMode('') : (gmGet('pk_file_view_mode', 'grid') === 'list' ? 'list' : 'grid'); syncModeViewShell(nextViewMode); } if (typeof applyResolvedSortState === 'function') applyResolvedSortState(); UI.btnNavHome.classList.toggle('act', mode === 'home'); UI.btnNavTrash.classList.toggle('act', mode === 'trash'); if (UI.btnNavShare) UI.btnNavShare.classList.toggle('act', mode === 'share'); if (UI.btnNavShareParse) UI.btnNavShareParse.classList.toggle('act', mode === 'shareParse'); if (UI.btnNavStarred) UI.btnNavStarred.classList.toggle('act', mode === 'starred'); if (UI.btnNavRecent) UI.btnNavRecent.classList.toggle('act', mode === 'recent'); if (UI.btnNavHistory) UI.btnNavHistory.classList.toggle('act', mode === 'history'); if (UI.btnNavOffline) UI.btnNavOffline.classList.toggle('act', mode === 'offline'); if (UI.btnNavUpload) UI.btnNavUpload.classList.toggle('act', mode === 'upload'); if(UI.topBar) UI.topBar.style.display = 'flex'; if(UI.actionBar) UI.actionBar.style.display = 'flex'; if(UI.trashBar) UI.trashBar.style.display = 'none'; if(UI.bottomGrp) UI.bottomGrp.style.display = 'flex'; if(UI.crumb) { UI.crumb.style.opacity = '1'; UI.crumb.style.display = 'flex'; } if(UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'flex'; const mainHeader = UI.win ? UI.win.querySelector('.pk-grid-hd') : null; if (mainHeader) { mainHeader.style.display = ''; mainHeader.style.visibility = ''; } const stdBtns = [UI.btnNewFolder, UI.btnDel, UI.btnCopy, UI.btnCut, UI.btnPaste, UI.btnRename, UI.btnBulkRename, UI.btnPrune, UI.btnUnzip, UI.btnMigrate, UI.btnBlacklistManager]; const shareBtns = [document.getElementById('pk-cancel-share')]; const upBtns = [UI.btnUpPause, UI.btnUpStart, UI.btnUpDel, UI.btnUpClearAll]; const upSep = document.getElementById('pk-up-sep'); upBtns.forEach(b => { if(b) b.style.display = 'none'; }); if(upSep) upSep.style.display = 'none'; if (UI.upTip) UI.upTip.style.display = 'none'; if (UI.btnClearHistoryAll) UI.btnClearHistoryAll.style.display = 'none'; if (UI.btnShareParseSave) UI.btnShareParseSave.style.display = 'none'; if (UI.btnShareParseInsight) UI.btnShareParseInsight.style.display = 'none'; if (UI.btnShareParseStopScan) UI.btnShareParseStopScan.style.display = 'none'; if (UI.btnShareParseBackList) UI.btnShareParseBackList.style.display = 'none'; if (UI.actionBar) UI.actionBar.querySelectorAll('.pk-sep').forEach(sep => { sep.style.display = ''; }); if (UI.btnRefresh) { UI.btnRefresh.style.display = 'inline-flex'; UI.btnRefresh.disabled = false; } if (UI.btnBlacklistManager) UI.btnBlacklistManager.disabled = false; if (S.historyMode) { UI.win.classList.remove('pk-mode-trash'); stdBtns.forEach(b => { if(b && b !== UI.btnBlacklistManager && b !== UI.btnMigrate) b.style.display = 'none'; }); if (UI.btnClearHistoryAll) UI.btnClearHistoryAll.style.display = 'inline-flex'; if (UI.btnBlacklistManager) UI.btnBlacklistManager.style.display = 'inline-flex'; if (UI.btnMigrate) UI.btnMigrate.style.display = 'inline-flex'; if (UI.btnRefresh) UI.btnRefresh.style.display = 'inline-flex'; [UI.btnUpPause, UI.btnUpStart, UI.btnUpDel, UI.btnUpClearAll, UI.btnUpClearDone, UI.btnUpClearIng].forEach(b => { if(b) b.style.display = 'none'; }); const upSep = document.getElementById('pk-up-sep'); if(upSep) upSep.style.display = 'none'; if(UI.uploadWrap) UI.uploadWrap.style.display = 'none'; [UI.btnAria2, UI.btnDown, UI.btnExt, UI.btnExportM3U, UI.btnImgSearch].forEach(b => { if(b) b.style.display = 'inline-flex'; }); shareBtns.forEach(b => { if(b) b.style.display = 'none'; }); if (UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'none'; if (UI.scan) UI.scan.style.display = 'none'; if (UI.chkGlobal) UI.chkGlobal.checked = false; if (UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'none'; if (UI.searchInput && UI.searchInput.parentNode) { UI.searchInput.parentNode.style.display = 'flex'; } if (UI.bottomGrp) UI.bottomGrp.style.display = 'flex'; } else if (S.starredMode || S.recentMode) { UI.win.classList.remove('pk-mode-trash'); [UI.btnAria2, UI.btnDown, UI.btnExt, UI.btnExportM3U, UI.btnImgSearch].forEach(b => { if(b) b.style.display = 'inline-flex'; }); stdBtns.forEach(b => { if(b) b.style.display = 'inline-flex'; }); [UI.btnUpPause, UI.btnUpStart, UI.btnUpDel, UI.btnUpClearAll, UI.btnUpClearDone, UI.btnUpClearIng].forEach(b => { if(b) b.style.display = 'none'; }); const upSep = document.getElementById('pk-up-sep'); if(upSep) upSep.style.display = 'none'; if(UI.uploadWrap) UI.uploadWrap.style.display = 'none'; shareBtns.forEach(b => { if(b) b.style.display = 'none'; }); if(UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if(UI.btnAnalyze) UI.btnAnalyze.style.display = 'none'; if(UI.btnExport) UI.btnExport.style.display = 'none'; if(UI.scan) UI.scan.style.display = 'none'; if(UI.chkGlobal) UI.chkGlobal.checked = false; if(UI.bottomGrp) UI.bottomGrp.style.display = 'flex'; } else if (S.shareParseMode) { UI.win.classList.remove('pk-mode-trash'); stdBtns.forEach(b => { if(b) b.style.display = 'none'; }); shareBtns.forEach(b => { if(b) b.style.display = 'none'; }); [UI.btnRetryTask, UI.btnCopyLinkOffline, UI.btnAria2, UI.btnDown].forEach(b => { if(b) b.style.display = 'none'; }); if (UI.btnExt) UI.btnExt.style.display = S.shareParseListActive ? 'inline-flex' : 'none'; if (UI.btnExportM3U) UI.btnExportM3U.style.display = S.shareParseListActive ? 'inline-flex' : 'none'; if (UI.btnImgSearch) UI.btnImgSearch.style.display = S.shareParseListActive ? 'inline-flex' : 'none'; if (UI.btnRefresh) UI.btnRefresh.style.display = 'none'; if (UI.actionBar) { UI.actionBar.style.display = S.shareParseListActive ? 'flex' : 'none'; UI.actionBar.querySelectorAll('.pk-sep').forEach(sep => { sep.style.display = 'none'; }); } if (UI.trashBar) UI.trashBar.style.display = 'none'; if (UI.bottomGrp) UI.bottomGrp.style.display = S.shareParseListActive ? 'flex' : 'none'; if (UI.uploadWrap) UI.uploadWrap.style.display = 'none'; if (UI.btnExit) UI.btnExit.style.display = 'none'; if (UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if (UI.chkSearchPath) UI.chkSearchPath.checked = false; if (UI.chkGlobal) UI.chkGlobal.checked = false; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'none'; if (UI.btnExport) UI.btnExport.style.display = 'none'; if (UI.scan) UI.scan.style.display = 'none'; if (UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'none'; if (UI.dupTools) UI.dupTools.style.display = 'none'; if (UI.dupFilters) UI.dupFilters.style.display = 'none'; if (UI.offTools) UI.offTools.style.display = 'none'; if (UI.upTools) UI.upTools.style.display = 'none'; if (UI.filterBar) UI.filterBar.style.display = 'none'; if (UI.searchInput && UI.searchInput.parentNode) UI.searchInput.parentNode.style.display = 'none'; updateShareParseSaveButton(); } else if (S.shareMode) { UI.win.classList.remove('pk-mode-trash'); stdBtns.forEach(b => { if(b) b.style.display = 'none'; }); if (UI.btnRefresh) UI.btnRefresh.style.display = 'inline-flex'; shareBtns.forEach(b => { if(b) b.style.display = 'inline-flex'; }); if(UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if(UI.btnAnalyze) UI.btnAnalyze.style.display = 'none'; if(UI.scan) UI.scan.style.display = 'none'; if(UI.bottomGrp) UI.bottomGrp.style.display = 'none'; } else if (S.offlineMode) { UI.win.classList.remove('pk-mode-trash'); [UI.btnNewFolder, UI.btnCopy, UI.btnCut, UI.btnPaste, UI.btnRename, UI.btnBulkRename, UI.btnPrune, UI.btnUnzip, UI.btnMigrate].forEach(b => { if(b) b.style.display = 'none'; }); [UI.btnDel, UI.btnRefresh, UI.btnBlacklistManager].forEach(b => { if(b) b.style.display = 'inline-flex'; }); shareBtns.forEach(b => { if(b) b.style.display = 'none'; }); if(UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if(UI.btnAnalyze) UI.btnAnalyze.style.display = 'none'; if(UI.scan) UI.scan.style.display = 'none'; if(UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'none'; if(UI.bottomGrp) UI.bottomGrp.style.display = 'flex'; [UI.btnAria2, UI.btnDown].forEach(b => { if(b) b.style.display = 'none'; }); if(UI.btnExt) UI.btnExt.style.display = 'inline-flex'; if(UI.btnExportM3U) UI.btnExportM3U.style.display = 'inline-flex'; if(UI.btnImgSearch) UI.btnImgSearch.style.display = 'inline-flex'; } else if (S.uploadMode) { UI.win.classList.remove('pk-mode-trash'); stdBtns.forEach(b => { if(b) b.style.display = 'none'; }); if (UI.btnRefresh) UI.btnRefresh.style.display = 'none'; shareBtns.forEach(b => { if(b) b.style.display = 'none'; }); upBtns.forEach(b => { if(b) b.style.display = 'inline-flex'; }); if(upSep) upSep.style.display = 'block'; if(UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if(UI.btnAnalyze) UI.btnAnalyze.style.display = 'none'; if(UI.scan) UI.scan.style.display = 'none'; if(UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'none'; if(UI.uploadWrap) UI.uploadWrap.style.display = 'none'; if(UI.upTip) UI.upTip.style.display = 'flex'; if(UI.bottomGrp) UI.bottomGrp.style.display = 'flex'; [UI.btnAria2, UI.btnDown].forEach(b => { if(b) b.style.display = 'none'; }); if(UI.btnExt) UI.btnExt.style.display = 'inline-flex'; if(UI.btnExportM3U) UI.btnExportM3U.style.display = 'inline-flex'; if(UI.btnImgSearch) UI.btnImgSearch.style.display = 'inline-flex'; } else if (S.trashMode) { UI.win.classList.add('pk-mode-trash'); if(UI.topBar) UI.topBar.style.display = 'flex'; if(UI.actionBar) UI.actionBar.style.display = 'none'; if(UI.trashBar) UI.trashBar.style.display = 'flex'; if(UI.bottomGrp) UI.bottomGrp.style.display = 'none'; } else { UI.win.classList.remove('pk-mode-trash'); [UI.btnAria2, UI.btnDown, UI.btnExt, UI.btnExportM3U, UI.btnImgSearch].forEach(b => { if(b) b.style.display = 'inline-flex'; }); stdBtns.forEach(b => { if(b) b.style.display = 'inline-flex'; }); shareBtns.forEach(b => { if(b) b.style.display = 'none'; }); [UI.btnUpPause, UI.btnUpStart, UI.btnUpDel, UI.btnUpClearAll, UI.btnUpClearDone, UI.btnUpClearIng].forEach(b => { if(b) b.style.display = 'none'; }); const upSep = document.getElementById('pk-up-sep'); if(upSep) upSep.style.display = 'none'; if(UI.uploadWrap) UI.uploadWrap.style.display = 'inline-flex'; if(UI.lblGlobal) UI.lblGlobal.style.display = 'flex'; if(UI.btnAnalyze) UI.btnAnalyze.style.display = 'flex'; if(UI.scan) UI.scan.style.display = 'flex'; if(UI.bottomGrp) UI.bottomGrp.style.display = 'flex'; if (UI.chkGlobal && typeof S.wasGlobalChecked !== 'undefined') { UI.chkGlobal.checked = S.wasGlobalChecked; } } if (S.starredMode || S.offlineMode || S.uploadMode) { if (UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if (UI.chkGlobal) UI.chkGlobal.checked = false; } syncLocalUploadVisibility(); S.clearSelection(); if (S.shareParseMode) { setLoad(false); renderCrumb(); if (typeof applyResolvedSortState === 'function') applyResolvedSortState(); if (S.shareParseListActive) { syncShareParseMirror(); renderList(); } else { renderShareParsePanel(); } syncShareParseInsightFilterBar(); updateStat(); return; } const isOffline = mode === 'offline'; const isRecent = mode === 'recent'; const realKey = S.getRealCacheKey(isOffline ? 'offline_root' : (isRecent ? 'recent_root' : '')); const hasCache = typeof globalCache !== 'undefined' && globalCache.has(realKey); const session = typeof globalCache !== 'undefined' ? globalCache.get('offline_session') : null; const isResumingOffline = isOffline && session && !session.completed; const recentCache = isRecent && hasCache ? globalCache.get(realKey) : null; const isResumingRecent = isRecent && recentCache && !Array.isArray(recentCache) && recentCache.nextToken; if (!((isOffline && (hasCache || isResumingOffline)) || (isRecent && (hasCache || isResumingRecent)))) { setLoad(true, true); } load(false, !(isOffline || isRecent)); if (window.pkSmartRefreshTrigger) { setTimeout(() => window.pkSmartRefreshTrigger(isOffline || isRecent), 100); } }; const btnCloud = el.querySelector('#pk-btn-cloud'); const showFolderSelector = (initialId, onConfirm, initialPath = null, fileFilter = null, customTitle = null) => { let currentPath = initialPath ? JSON.parse(JSON.stringify(initialPath)) : [{ id: '', name: L.picker_all }]; if (currentPath.length > 0) currentPath[0].name = L.picker_all; let currentList = []; let selectedFile = null; let sortMode = 'new'; const titleStr = customTitle || (fileFilter ? L.title_select_file : L.picker_title); const picker = showModal(`

${titleStr}

${CONF.icons.close}
${L.picker_sort_new}
${L.loading}
${CONF.icons.newfolder} ${L.picker_new}
`); const mContent = picker.querySelector('.pk-modal'); mContent.style.padding = '20px'; mContent.style.width = 'fit-content'; picker.querySelector('.pk-modal-close').style.display = 'none'; const crumbEl = picker.querySelector('#pk_picker_crumb'); const listEl = picker.querySelector('#pk_picker_list'); listEl.onscroll = () => { const oldPop = document.querySelector('.pk-crumb-pop'); if (oldPop && typeof oldPop._cleanup === 'function') oldPop._cleanup(); }; const sortTrigger = picker.querySelector('#pk_sort_trigger'); const sortMenu = picker.querySelector('#pk_sort_menu'); const sortTxt = picker.querySelector('#pk_sort_txt'); const closeBtn = picker.querySelector('#pk_picker_close_btn'); closeBtn.onmouseover = () => closeBtn.style.background = 'var(--pk-hl)'; closeBtn.onmouseout = () => closeBtn.style.background = 'transparent'; closeBtn.onclick = () => picker.remove(); const applySort = () => { const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); const getCharWeight = (str) => { if (!str) return 0; const c = str.charAt(0); if (/[0-9]/.test(c)) return 20; if (/[\u4e00-\u9fa5]/.test(c)) return 30; if (/[a-zA-Z]/.test(c)) return 40; return 10; }; const compareNames = (nameA, nameB) => { const rankA = getCharWeight(nameA); const rankB = getCharWeight(nameB); if (rankA !== rankB) return rankA - rankB; return collator.compare(nameA, nameB); }; currentList.sort((a, b) => { if (a.kind !== b.kind) return a.kind === 'drive#folder' ? -1 : 1; const isSysA = a.name === CONF.SYSTEM_FOLDER_NAME && (!a.parent_id || a.parent_id === '' || a.parent_id === 'root'); const isSysB = b.name === CONF.SYSTEM_FOLDER_NAME && (!b.parent_id || b.parent_id === '' || b.parent_id === 'root'); if (isSysA !== isSysB) return isSysA ? -1 : 1; if (sortMode === 'az') return compareNames(a.name, b.name); if (sortMode === 'za') return compareNames(b.name, a.name); if (sortMode === 'new') return new Date(b.modified_time) - new Date(a.modified_time); if (sortMode === 'old') return new Date(a.modified_time) - new Date(b.modified_time); return 0; }); }; const renderList = () => { listEl.innerHTML = ''; if (currentList.length === 0) { listEl.innerHTML = `
${CONF.emptySVG}
${L.str_no_files}
`; return; } currentList.forEach(item => { const div = document.createElement('div'); div.style.cssText = "display:flex; align-items:center; padding:10px 8px; cursor:pointer; border-radius:6px; transition:background 0.1s; border-bottom:1px dashed var(--pk-bd);"; const isDir = item.kind === 'drive#folder'; const isSelected = selectedFile && selectedFile.id === item.id; const isProtected = isDir && ((typeof isSystemItem === 'function') ? isSystemItem(item) : false); const tagHtml = isProtected ? `${L.tag_default}` : ''; let checkHtml = ""; if (!isDir && fileFilter) { checkHtml = ``; } const iconSrc = item.icon_link || ''; let iconHtml = ''; if (isDir) { const fallbackSvg = CONF.typeIcons.folder.replace(/width="\d+"/, 'width="24"').replace(/height="\d+"/, 'height="24"'); iconHtml = iconSrc ? `` : `
${fallbackSvg}
`; } else { const fallbackSvg = getIcon(item).replace(/width="\d+"/, 'width="24"').replace(/height="\d+"/, 'height="24"'); iconHtml = iconSrc ? `` : `
${fallbackSvg}
`; } div.innerHTML = ` ${checkHtml} ${iconHtml}
${esc(item.name)} ${tagHtml}
${!isDir ? `
${fmtSize(item.size)}
` : ''} `; div.onmouseover = () => div.style.background = 'var(--pk-hl)'; div.onmouseout = () => div.style.background = 'transparent'; if (isSelected) div.style.background = 'var(--pk-sel-bg)'; div.onclick = (e) => { if (isDir) { selectedFile = null; loadFolder(item.id, item.name); } else if (fileFilter) { selectedFile = (selectedFile && selectedFile.id === item.id) ? null : item; renderList(); } }; listEl.appendChild(div); }); }; let _pickerCrumbIdx = 0; let _lastPickerScroll = 0; const showPickerDropdown = async (e, parentId, triggerEl) => { const old = document.querySelector('.pk-crumb-pop'); if (old) { const wasSame = old._sourceEl === triggerEl; if (typeof old._cleanup === 'function') old._cleanup(); if (wasSame) return; } triggerEl.style.background = 'transparent'; triggerEl.innerHTML = CONF.crumbIcons.down; const svgD = triggerEl.querySelector('svg'); if (svgD) { svgD.style.width = '14px'; svgD.style.height = '14px'; svgD.style.display = 'block'; } const pop = document.createElement('div'); pop.className = 'pk-crumb-pop pk-scroll pk-show'; if (document.querySelector('.pk-ov')?.classList.contains('pk-dark')) pop.classList.add('pk-dark'); pop.style.zIndex = '2147483647'; pop._sourceEl = triggerEl; document.body.appendChild(pop); const rect = getLogicalRect(triggerEl); pop.style.top = (rect.bottom + 5) + 'px'; pop.style.left = rect.left + 'px'; const cleanup = () => { if (pop.parentNode) pop.remove(); triggerEl.innerHTML = CONF.crumbIcons.right; const svgR = triggerEl.querySelector('svg'); if (svgR) { svgR.style.width = '14px'; svgR.style.height = '14px'; svgR.style.display = 'block'; svgR.style.opacity = '0.6'; } document.removeEventListener('mousedown', closer); window.removeEventListener('resize', cleanup); }; pop._cleanup = cleanup; const closer = (ev) => { if (!pop.contains(ev.target) && ev.target !== triggerEl) cleanup(); }; document.addEventListener('mousedown', closer); window.addEventListener('resize', cleanup); const cacheKey = parentId || 'root'; let folders = null; if (typeof globalCache !== 'undefined' && globalCache.has(cacheKey)) { const raw = globalCache.get(cacheKey); if (Array.isArray(raw) || (raw && raw.items && !raw.nextToken)) { const items = Array.isArray(raw) ? raw : raw.items; folders = items.filter(f => f.kind === 'drive#folder'); } } const renderMenu = (list) => { const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); const getCharWeight = (str) => { if (!str) return 0; const c = str.charAt(0); if (/[0-9]/.test(c)) return 20; if (/[\u4e00-\u9fa5]/.test(c)) return 30; if (/[a-zA-Z]/.test(c)) return 40; return 10; }; const compareNames = (nameA, nameB) => { const rankA = getCharWeight(nameA); const rankB = getCharWeight(nameB); if (rankA !== rankB) return rankA - rankB; return collator.compare(nameA, nameB); }; list.sort((a, b) => { const isSysA = a.name === CONF.SYSTEM_FOLDER_NAME && (!a.parent_id || a.parent_id === '' || a.parent_id === 'root'); const isSysB = b.name === CONF.SYSTEM_FOLDER_NAME && (!b.parent_id || b.parent_id === '' || b.parent_id === 'root'); if (isSysA !== isSysB) return isSysA ? -1 : 1; if (sortMode === 'az') return compareNames(a.name, b.name); if (sortMode === 'za') return compareNames(b.name, a.name); if (sortMode === 'new') return new Date(b.modified_time || 0) - new Date(a.modified_time || 0); if (sortMode === 'old') return new Date(a.modified_time || 0) - new Date(b.modified_time || 0); return 0; }); if (list.length === 0) { cleanup(); return; } pop.innerHTML = ''; list.forEach(f => { const itemDiv = document.createElement('div'); itemDiv.className = 'pk-crumb-item'; const iconSrc = f.icon_link || ''; const fallbackSvg = CONF.typeIcons.folder.replace(/width="\d+"/, 'width="18"').replace(/height="\d+"/, 'height="18"'); const iconHtml = iconSrc ? `${fallbackSvg}` : fallbackSvg; const isInPath = S.path.some(pathNode => pathNode.id === f.id); const textStyle = isInPath ? 'font-weight:bold; color:var(--pk-pri);' : ''; const isProtected = (typeof isSystemItem === 'function') ? isSystemItem(f) : false; const tagHtml = isProtected ? `${L.tag_default}` : ''; itemDiv.innerHTML = `${iconHtml}${esc(f.name)}${tagHtml}`; itemDiv.onclick = (ev) => { ev.stopPropagation(); cleanup(); const idx = currentPath.findIndex(p => p.id === parentId); if (idx !== -1) { currentPath = currentPath.slice(0, idx + 1); loadFolder(f.id, f.name); } }; pop.appendChild(itemDiv); }); }; if (folders !== null) { renderMenu(folders); } else { pop.innerHTML = `
`; try { const listItems = await apiList(parentId || '', 1000, null, null, false, true); if (typeof globalCache !== 'undefined') globalCache.set(cacheKey, listItems); renderMenu(listItems.filter(f => f.kind === 'drive#folder')); } catch (err) { cleanup(); } } }; const renderCrumb = () => { crumbEl.innerHTML = ''; currentPath.forEach((p, i) => { const sp = document.createElement('span'); const isLast = i === currentPath.length - 1; if (i === 0) { const homeIcon = CONF.icons.home.replace(' { if (!isLast) { currentPath = currentPath.slice(0, i + 1); } loadFolder(p.id, null, true); }; sp.onmouseover = () => sp.style.background = 'var(--pk-hl)'; sp.onmouseout = () => sp.style.background = 'transparent'; crumbEl.appendChild(sp); let showArrow = !isLast; if (isLast) { if (currentList && currentList.some(item => item.kind === 'drive#folder')) { showArrow = true; } } if (showArrow) { const sep = document.createElement('span'); sep.className = 'pk-picker-arrow'; sep.innerHTML = CONF.crumbIcons.right; const svg = sep.querySelector('svg'); if(svg) { svg.style.width = '14px'; svg.style.height = '14px'; svg.style.display = 'block'; svg.style.opacity = '0.6'; } sep.style.cssText = "margin:0 2px; display:flex; align-items:center; cursor:pointer; padding:4px; border-radius:4px; transition:all 0.2s; flex-shrink:0; color:var(--pk-icon-c);"; sep.onmouseover = () => { sep.style.background = 'var(--pk-hl)'; sep.style.color = 'var(--pk-pri)'; if(svg) svg.style.opacity='1'; }; sep.onmouseout = () => { sep.style.background = 'transparent'; sep.style.color = 'var(--pk-icon-c)'; if(svg) svg.style.opacity='0.6'; }; sep.onclick = (e) => { e.stopPropagation(); if (typeof showPickerDropdown === 'function') showPickerDropdown(e, p.id, sep); }; crumbEl.appendChild(sep); } }); _pickerCrumbIdx = currentPath.length - 1; requestAnimationFrame(() => { crumbEl.scrollLeft = crumbEl.scrollWidth; }); }; const handlePickerWheel = (e) => { e.preventDefault(); document.querySelectorAll('.pk-crumb-pop').forEach(p => { if (typeof p._cleanup === 'function') p._cleanup(); }); const now = Date.now(); if (now - _lastPickerScroll < 120) return; _lastPickerScroll = now; const nodes = Array.from(crumbEl.children).filter(c => !c.classList.contains('pk-picker-arrow')); if (!nodes.length) return; if (e.deltaY < 0) _pickerCrumbIdx = Math.max(0, _pickerCrumbIdx - 1); else _pickerCrumbIdx = Math.min(nodes.length - 1, _pickerCrumbIdx + 1); const target = nodes[_pickerCrumbIdx]; if (target) { const centerOffset = target.offsetLeft + (target.offsetWidth / 2) - (crumbEl.clientWidth / 2); crumbEl.scrollTo({ left: centerOffset, behavior: 'smooth' }); } }; crumbEl.addEventListener('wheel', handlePickerWheel, { passive: false }); const loadFolder = async (id, name, isBack = false) => { listEl.innerHTML = `
`; if (name && !isBack) currentPath.push({ id, name }); renderCrumb(); const cacheKey = id || 'root'; let items = null; if (typeof globalCache !== 'undefined' && globalCache.has(cacheKey)) { const raw = globalCache.get(cacheKey); const isComplete = Array.isArray(raw) || (raw && raw.items && !raw.nextToken); if (isComplete) { items = Array.isArray(raw) ? raw : raw.items; } } try { if (items === null) { items = await apiList(id || '', 1000, null, null, false, true); if (typeof globalCache !== 'undefined') globalCache.set(cacheKey, items); indexParents(id, name || L.picker_all, items); } currentList = items.filter(i => { if (i.kind === 'drive#folder') return true; if (fileFilter && fileFilter(i)) return true; return false; }); applySort(); renderList(); renderCrumb(); } catch (e) { listEl.innerHTML = `
${L.str_error}: ${esc(e.message)}
`; } }; picker.querySelector('#pk_picker_cancel').onclick = () => picker.remove(); picker.querySelector('#pk_picker_ok').onclick = () => { const cur = currentPath[currentPath.length - 1]; const returnPathChain = JSON.parse(JSON.stringify(currentPath)); if (fileFilter && selectedFile) { onConfirm(selectedFile.id, selectedFile.name, selectedFile, returnPathChain); } else { onConfirm(cur.id, cur.name, null, returnPathChain); } picker.remove(); }; if (!fileFilter) { picker.querySelector('#pk_picker_new').onclick = async () => { const cur = currentPath[currentPath.length - 1]; const name = await showPrompt(L.msg_newfolder_prompt, '', L.picker_new); if (name) { try { const res = await fetch('https://api-drive.mypikpak.com/drive/v1/files', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ kind: 'drive#folder', parent_id: cur.id || '', name: name }) }); if (!res.ok) throw new Error("Create Failed"); const data = await res.json(); const newFolder = data.file || data; const parentId = cur.id || 'root'; if (typeof globalCache !== 'undefined') globalCache.delete(parentId); if (S.cache) S.cache.delete(parentId); globalDirtyFolders.add(parentId); gmSet('pk_fmod_' + parentId, new Date(getServerNow()).toISOString()); if (newFolder && newFolder.id) await loadFolder(newFolder.id, newFolder.name); else await loadFolder(cur.id, null, true); } catch(e) { showAlert(e.message); } } }; } sortTrigger.onclick = (e) => { e.stopPropagation(); const isOpening = sortMenu.style.display !== 'block'; sortMenu.style.display = isOpening ? 'block' : 'none'; sortTrigger.style.background = isOpening ? 'var(--pk-hl)' : 'transparent'; }; picker.querySelectorAll('.pk-sort-opt').forEach(el => { el.onclick = () => { sortMode = el.dataset.val; sortTxt.textContent = el.textContent; sortMenu.style.display = 'none'; sortTrigger.style.background = 'transparent'; applySort(); renderList(); }; el.onmouseover = () => { el.style.background = 'var(--pk-hl)'; el.style.color = 'var(--pk-pri)'; }; el.onmouseout = () => { el.style.background = 'transparent'; el.style.color = 'var(--pk-fg)'; }; }); const closeSortMenu = () => { if (sortMenu) sortMenu.style.display = 'none'; if (sortTrigger) sortTrigger.style.background = 'transparent'; }; setTimeout(() => document.addEventListener('click', closeSortMenu), 0); const _orgRemove = picker.remove.bind(picker); picker.remove = () => { document.removeEventListener('click', closeSortMenu); _orgRemove(); }; loadFolder(initialId || '', null, true); }; const initClipboardMagnetFocusWatcher = () => { if (window.__pkClipboardMagnetFocusWatcherBound) return; window.__pkClipboardMagnetFocusWatcherBound = true; const state = { lastCheckAt: 0, lastDeniedAt: 0, lastPromptAt: 0, lastSignature: '', prompting: false, processing: false, ignored: new Map(), queue: [], queued: new Set(), previewCache: new Map(), previewCircuitUntil: 0 }; const getClipText = () => getStrings(); const normalizeHash = (link) => { const match = String(link || '').match(/urn:btih:([^&]+)/i); return match ? match[1].toUpperCase() : String(link || '').trim().toLowerCase(); }; const isIgnoredSignature = (signature) => { const until = state.ignored.get(signature) || 0; if (!until) return false; if (Date.now() < until) return true; state.ignored.delete(signature); return false; }; const markIgnoredSignature = (signature) => { state.ignored.set(signature, Date.now() + CONF.clipboardMagnetIgnoreTTL); }; const getDefaultMagnetSaveTarget = () => { const curFolder = S.path[S.path.length - 1] || { id: '', name: L.lbl_default_folder }; const curId = curFolder.id || ''; const isVirtual = curId.startsWith('virtual_') || curId.includes('_root') || curId === 'analyze_root'; const isHomeSubDir = !S.trashMode && !S.shareMode && !S.offlineMode && !S.starredMode && !S.recentMode && !S.isFlattened && !S.dupMode && S.path.length > 1 && !isVirtual; return { id: isHomeSubDir ? curId : '', name: isHomeSubDir ? (curFolder.name || L.lbl_default_folder) : L.lbl_default_folder, path: isHomeSubDir ? S.path.filter(p => !p.id.startsWith('virtual_')) : null }; }; const createMagnetCloudTasks = (links, targetId) => submitCloudLinks(links, { targetId: targetId || '', skipSnapshot: true, logPrefix: 'Magnet Task Create Failed' }); const extractMagnetLinks = (rawText) => { const text = String(rawText || '').trim(); if (!text || text.length > CONF.clipboardMagnetMaxChars) return []; const unique = new Map(); const addMagnet = (value) => { let link = String(value || '').trim(); if (!link) return; const urnOnly = link.match(/^urn:btih:([a-fA-F0-9]{40}|[a-zA-Z2-7]{32})(?:[^\s"'<>`]*)?$/i); if (urnOnly) link = `magnet:?xt=urn:btih:${urnOnly[1].toUpperCase()}`; const pureHash = link.match(/^([a-fA-F0-9]{40}|[a-zA-Z2-7]{32})$/i); if (pureHash) link = `magnet:?xt=urn:btih:${pureHash[1].toUpperCase()}`; if (!/^magnet:\?/i.test(link)) return; const hashMatch = link.match(/[?&]xt=urn:btih:([a-fA-F0-9]{40}|[a-zA-Z2-7]{32})/i); if (!hashMatch) return; const signature = hashMatch[1].toUpperCase(); if (!unique.has(signature)) unique.set(signature, link); }; if (!/(magnet:\?|urn:btih:)/i.test(text)) { const lines = text.split(/\r?\n/).map(x => x.trim()).filter(Boolean); if (lines.length === 1) addMagnet(lines[0]); return Array.from(unique.values()); } const magnetRegex = /magnet:\?[^\s"'<>`]+/gi; let match; while ((match = magnetRegex.exec(text))) addMagnet(match[0]); const urnRegex = /urn:btih:([a-fA-F0-9]{40}|[a-zA-Z2-7]{32})(?:[^\s"'<>`]*)?/gi; while ((match = urnRegex.exec(text))) addMagnet(match[0]); return Array.from(unique.values()); }; const requestMagnetPreview = (link) => { return new Promise((resolve) => { const hash = normalizeHash(link); const now = Date.now(); const cached = state.previewCache.get(hash); if (cached && now - cached.at < cached.ttl) { resolve(cached.data); return; } const finish = (data) => { const ttl = data && data.ok ? CONF.magnetPreviewCacheTTL : CONF.magnetPreviewErrorCacheTTL; state.previewCache.set(hash, { at: Date.now(), ttl, data }); resolve(data); }; if (state.previewCircuitUntil && now < state.previewCircuitUntil) { finish({ ok: false, code: 'rate_limited' }); return; } const url = `${CONF.magnetPreviewApi}?url=${encodeURIComponent(link)}`; const handleStatus = (status) => { if (status === 429) { state.previewCircuitUntil = Date.now() + CONF.magnetPreviewCircuitTTL; finish({ ok: false, code: 'rate_limited', status }); return true; } if (status >= 500) { state.previewCircuitUntil = Date.now() + CONF.magnetPreviewCircuitTTL; finish({ ok: false, code: 'network', status }); return true; } if (status && (status < 200 || status >= 300)) { finish({ ok: false, code: 'network', status }); return true; } return false; }; if (typeof GM_xmlhttpRequest !== 'function') { fetch(url, { cache: 'no-store' }).then(r => { if (handleStatus(r.status)) return null; return r.json(); }).then(data => { if (data) finish({ ok: true, code: 'ok', data }); }).catch(() => finish({ ok: false, code: 'network' })); return; } GM_xmlhttpRequest({ method: 'GET', url, responseType: 'json', timeout: CONF.magnetPreviewTimeout, onload: (res) => { if (handleStatus(res.status)) return; try { const data = res.response && typeof res.response === 'object' ? res.response : JSON.parse(res.responseText || '{}'); finish({ ok: true, code: 'ok', data }); } catch (e) { finish({ ok: false, code: 'network' }); } }, onerror: () => finish({ ok: false, code: 'network' }), ontimeout: () => finish({ ok: false, code: 'timeout' }) }); }); }; const showMagnetPreviewModal = (links, preview) => { return new Promise((resolve) => { const TXT = getClipText(); const data = preview && preview.ok && preview.data ? preview.data : {}; let saveTarget = getDefaultMagnetSaveTarget(); const toWhatslinkImageUrl = (value) => { let url = String(value || '').trim(); if (!url) return ''; if (/^\/\//.test(url)) url = `https:${url}`; else if (/^\//.test(url)) url = `https://whatslink.info${url}`; else if (!/^https?:\/\//i.test(url) && /\.(webp|png|jpe?g|gif)(\?|#|$)/i.test(url)) url = `https://whatslink.info/${url.replace(/^\.?\//, '')}`; return /^https?:\/\//i.test(url) ? url : ''; }; const pickShot = (item) => { if (!item) return null; if (typeof item === 'string') { const src = toWhatslinkImageUrl(item); return src ? { src, time: 0 } : null; } if (Array.isArray(item)) { for (const sub of item) { const shot = pickShot(sub); if (shot) return shot; } return null; } if (typeof item === 'object') { const officialSrc = toWhatslinkImageUrl(item.screenshot); if (officialSrc) return { src: officialSrc, time: Number(item.time || 0) || 0 }; const keys = ['url', 'src', 'image', 'img', 'thumbnail', 'thumb', 'preview', 'poster', 'file', 'path']; for (const key of keys) { const shot = pickShot(item[key]); if (shot) return { src: shot.src, time: Number(item.time || shot.time || 0) || 0 }; } for (const val of Object.values(item)) { const shot = pickShot(val); if (shot) return { src: shot.src, time: Number(item.time || shot.time || 0) || 0 }; } } return null; }; const collectShots = (source) => { const list = []; const seen = new Set(); const push = (value) => { const shot = pickShot(value); if (shot && shot.src && !seen.has(shot.src)) { seen.add(shot.src); list.push(shot); } }; if (Array.isArray(source.screenshots)) source.screenshots.forEach(push); if (source.screenshot) push({ screenshot: source.screenshot, time: source.time }); if (Array.isArray(source.thumbnails)) source.thumbnails.forEach(push); if (Array.isArray(source.images)) source.images.forEach(push); if (Array.isArray(source.files)) source.files.forEach(file => { if (file && (file.screenshots || file.screenshot || file.thumbnail || file.thumb || file.preview || file.poster || file.image || file.url || file.path)) push(file); }); return list.slice(0, CONF.magnetPreviewMaxShots); }; const fmtShotTime = (seconds) => { const total = Math.floor(Number(seconds || 0)); if (!Number.isFinite(total) || total <= 0) return ''; const h = Math.floor(total / 3600); const m = Math.floor((total % 3600) / 60); const s = total % 60; return h > 0 ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}` : `${m}:${String(s).padStart(2, '0')}`; }; const shots = collectShots(data); const name = data.name || TXT.str_magnet_unknown_name; const size = Number(data.size || 0) > 0 ? fmtSize(data.size) : TXT.str_magnet_unknown_name; const count = Number(data.count || 0) > 0 ? String(data.count) : String(links.length); const type = data.file_type || data.type || TXT.str_magnet_unknown_name; const hashText = normalizeHash(links[0]); const heroHtml = shots[0] ? `` : `
${esc(TXT.str_no_preview)}
`; const shotsHtml = shots.length > 1 ? `
${shots.map((shot, idx) => `
${fmtShotTime(shot.time) ? `${esc(fmtShotTime(shot.time))}` : ''}
`).join('')}
` : ''; const getPreviewFailText = () => { if (preview && preview.ok) return ''; if (preview && preview.code === 'rate_limited') return TXT.msg_magnet_preview_rate_limited; if (preview && preview.code === 'timeout') return TXT.msg_magnet_preview_timeout; if (preview && preview.code === 'network') return TXT.msg_magnet_preview_network; return TXT.msg_magnet_preview_fail; }; const failText = getPreviewFailText(); const warnHtml = failText ? `
${esc(failText)}
` : ''; const m = showModal(`
${heroHtml}
${esc(name)}
${esc(TXT.msg_magnet_preview_desc)}
${warnHtml} ${shotsHtml}
${esc(TXT.lbl_magnet_count)}
${esc(count)}
${esc(TXT.lbl_magnet_size)}
${esc(size)}
${esc(TXT.lbl_magnet_type)}
${esc(type)}
${esc(TXT.lbl_magnet_hash)}:${esc(hashText)}
${esc(L.lbl_save_to)} ${CONF.typeIcons.folder} ${esc(saveTarget.name)} ${L.tip_cloud_save_path ? `${CONF.icons.help}` : ''} ${esc(L.btn_modify)}
${esc(TXT.lbl_magnet_preview_source)} whatslink.info
`); const box = m.querySelector('.pk-modal'); if (box) { box.style.width = '420px'; box.style.padding = '0'; box.style.borderRadius = '16px'; } const heroBox = m.querySelector('.pk-magnet-hero'); const setHeroShot = (src, thumb) => { if (!heroBox || !src) return; let heroImg = heroBox.querySelector('img'); if (!heroImg) { heroBox.innerHTML = ``; heroImg = heroBox.querySelector('img'); } heroImg.src = src; heroImg.setAttribute('draggable', 'false'); heroImg.setAttribute('referrerpolicy', 'no-referrer'); m.querySelectorAll('.pk-magnet-thumb-wrap').forEach(x => x.classList.remove('active')); if (thumb) thumb.classList.add('active'); }; m.querySelectorAll('.pk-magnet-thumb-wrap').forEach(thumb => { thumb.addEventListener('dragstart', e => e.preventDefault()); thumb.addEventListener('click', () => setHeroShot(thumb.dataset.shotSrc || thumb.querySelector('img')?.getAttribute('src'), thumb)); }); const heroImg = m.querySelector('.pk-magnet-hero img'); if (heroImg) heroImg.addEventListener('dragstart', e => e.preventDefault()); const saveNameEl = m.querySelector('#pk_magnet_save_name'); const changeDirEl = m.querySelector('#pk_magnet_change_dir'); if (changeDirEl) { changeDirEl.onclick = () => { showFolderSelector(saveTarget.id, (id, name, fullItem, selectedPathChain) => { saveTarget.id = id || ''; saveTarget.name = name || L.lbl_default_folder; saveTarget.path = selectedPathChain || null; if (saveNameEl) saveNameEl.textContent = saveTarget.name; }, saveTarget.path); }; } const close = () => { m.remove(); resolve({ confirm: false }); }; m.querySelector('.pk-modal-close').onclick = close; m.querySelector('#pk_magnet_cancel').onclick = close; m.querySelector('#pk_magnet_continue').onclick = () => { m.remove(); resolve({ confirm: true, targetId: saveTarget.id, targetName: saveTarget.name }); }; }); }; const processMagnetQueue = async () => { if (state.processing) return; state.processing = true; try { while (state.queue.length > 0) { const task = state.queue.shift(); if (!task || !task.link || !task.signature) continue; if (isIgnoredSignature(task.signature)) { state.queued.delete(task.signature); continue; } if (state.lastSignature === task.signature && Date.now() - state.lastPromptAt < CONF.clipboardMagnetPromptGap) { state.queued.delete(task.signature); continue; } state.prompting = true; state.lastSignature = task.signature; state.lastPromptAt = Date.now(); let result = null; try { const preview = await requestMagnetPreview(task.link); result = await showMagnetPreviewModal([task.link], preview); } catch (e) { console.error('Magnet preview queue failed:', e); result = { confirm: false }; } finally { state.prompting = false; } if (!result || !result.confirm) { markIgnoredSignature(task.signature); state.queued.delete(task.signature); continue; } try { await createMagnetCloudTasks([task.link], result.targetId || ''); } catch (e) { console.error('Magnet cloud task failed:', e); if (L.str_action_failed) showToast(L.str_action_failed, 'error'); } state.queued.delete(task.signature); await sleep(80); } } finally { state.processing = false; if (state.queue.length > 0) processMagnetQueue(); } }; const handleMagnetLinks = (links) => { if (!links || links.length === 0) return; let added = false; const now = Date.now(); links.forEach(link => { const cleanLink = String(link || '').trim(); if (!/^magnet:\?/i.test(cleanLink)) return; const signature = normalizeHash(cleanLink); if (!signature) return; if (isIgnoredSignature(signature)) return; if (state.queued.has(signature)) return; if (state.lastSignature === signature && now - state.lastPromptAt < CONF.clipboardMagnetPromptGap) return; state.queue.push({ link: cleanLink, signature }); state.queued.add(signature); added = true; }); if (added) processMagnetQueue(); }; const handleRawText = (rawText) => { const magnetLinks = extractMagnetLinks(rawText); if (magnetLinks.length === 0) return; handleMagnetLinks(magnetLinks); }; const shouldSkipFocusCheck = () => { if (gmGet('pk_clipboard_magnet_focus', true) !== true) return true; if (document.hidden) return true; if (!navigator.clipboard || typeof navigator.clipboard.readText !== 'function') return true; if (document.querySelector('#pk_cloud_input')) return true; if (document.querySelector('.pk-modal-ov') && !state.processing && !state.prompting) return true; const now = Date.now(); if (now - state.lastCheckAt < CONF.clipboardMagnetFocusCooldown) return true; if (now - state.lastDeniedAt < CONF.clipboardMagnetDenyCooldown) return true; return false; }; const checkClipboardMagnet = async () => { if (shouldSkipFocusCheck()) return; state.lastCheckAt = Date.now(); let rawText = ''; try { rawText = await navigator.clipboard.readText(); } catch (e) { state.lastDeniedAt = Date.now(); return; } handleRawText(rawText); }; const scheduleClipboardMagnetCheck = () => { if (window.__pkClipboardMagnetFocusTimer) clearTimeout(window.__pkClipboardMagnetFocusTimer); window.__pkClipboardMagnetFocusTimer = setTimeout(checkClipboardMagnet, 350); }; document.addEventListener('paste', (e) => { if (gmGet('pk_clipboard_magnet_paste', CONF.clipboardMagnetPaste) === false) return; if (document.querySelector('#pk_cloud_input')) return; const active = document.activeElement; if (active && (active.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(active.tagName))) return; const rawText = e.clipboardData ? e.clipboardData.getData('text/plain') : ''; handleRawText(rawText); }, true); document.addEventListener('visibilitychange', () => { if (!document.hidden) scheduleClipboardMagnetCheck(); }); window.addEventListener('focus', scheduleClipboardMagnetCheck); window.addEventListener('pageshow', scheduleClipboardMagnetCheck); }; initClipboardMagnetFocusWatcher(); if (btnCloud) { btnCloud.onclick = () => { const curFolder = S.path[S.path.length - 1]; const isVirtual = curFolder.id.startsWith('virtual_') || curFolder.id.includes('_root') || curFolder.id === 'analyze_root'; const isHomeSubDir = !S.trashMode && !S.shareMode && !S.offlineMode && !S.starredMode && !S.recentMode && !S.isFlattened && !S.dupMode && S.path.length > 1 && !isVirtual; let saveToId = isHomeSubDir ? (curFolder.id || '') : ''; let saveToName = isHomeSubDir ? (curFolder.name || L.lbl_default_folder) : L.lbl_default_folder; let currentSavePath = isHomeSubDir ? S.path.filter(p => !p.id.startsWith('virtual_')) : null; const m = showModal(`

${L.title_cloud_task}

${L.lbl_save_to}
${CONF.typeIcons.folder.replace('width="30"', 'width="20"').replace('height="30"', 'height="20"')}
${saveToName}
${L.btn_modify}
${L.btn_via_torrent}
`); const modalBox = m.querySelector('.pk-modal'); modalBox.style.width = "560px"; modalBox.style.padding = "30px"; const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) { closeBtn.style.top = "36px"; closeBtn.style.right = "30px"; } const input = m.querySelector('#pk_cloud_input'); const submit = m.querySelector('#cloud_submit'); const dirLabel = m.querySelector('#pk_cloud_dir_name'); m.querySelector('#pk_cloud_change_dir').onclick = () => { showFolderSelector(saveToId, (id, name, fullItem, selectedPathChain) => { saveToId = id; saveToName = name; dirLabel.textContent = name; if (selectedPathChain) { currentSavePath = selectedPathChain; } }, currentSavePath); }; const smartFixCheckbox = m.querySelector('#pk_cloud_smart_fix'); if (smartFixCheckbox) smartFixCheckbox.onchange = () => input.dispatchEvent(new Event('input')); const parseAndCleanLinks = parseCloudLinks; input.oninput = () => { const rawVal = input.value.trim(); const isSmartFix = smartFixCheckbox ? smartFixCheckbox.checked : false; const linesRaw = rawVal.split('\n').map(l => l.trim()).filter(l => l); const hasInput = linesRaw.length > 0; const finalLinks = parseAndCleanLinks(rawVal, isSmartFix); const allValid = hasInput && (finalLinks.length === linesRaw.length || finalLinks.length > 0); const isError = hasInput && !allValid; m.querySelector('#pk_cloud_error').style.display = isError ? 'block' : 'none'; submit.disabled = !allValid; }; m.querySelector('#cloud_cancel').onclick = () => m.remove(); const torrentTrigger = m.querySelector('#pk_cloud_torrent_trigger'); const torrentFile = m.querySelector('#pk_cloud_torrent_file'); torrentTrigger.onclick = () => torrentFile.click(); torrentFile.onchange = async (e) => { const files = e.target.files; if (!files || files.length === 0) return; m.remove(); const fb = FloatBarManager.create(L.str_parsing_torrent); let successCount = 0; let failCount = 0; for (let i = 0; i < files.length; i++) { const file = files[i]; fb.update(`${L.str_parsing_torrent} (${i + 1}/${files.length})`); try { const magnetLink = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = async (evt) => { try { const buf = new Uint8Array(evt.target.result); let pos = 0; const decodeSkip = () => { if (pos >= buf.length) return; const c = buf[pos]; if (c === 100 || c === 108) { pos++; while (pos < buf.length && buf[pos] !== 101) decodeSkip(); pos++; } else if (c === 105) { pos++; while (pos < buf.length && buf[pos] !== 101) pos++; pos++; } else if (c >= 48 && c <= 57) { let colon = pos; while (colon < buf.length && buf[colon] !== 58) colon++; const lenStr = new TextDecoder().decode(buf.slice(pos, colon)); const len = parseInt(lenStr, 10); pos = colon + 1 + len; } }; if (buf[0] !== 100) throw new Error(L.err_invalid_torrent); pos = 1; let infoHash = null; while (pos < buf.length && buf[pos] !== 101) { const keyStart = pos; decodeSkip(); let colon = keyStart; while (colon < buf.length && buf[colon] !== 58) colon++; const kLen = parseInt(new TextDecoder().decode(buf.slice(keyStart, colon))); const keyStr = new TextDecoder().decode(buf.slice(colon + 1, colon + 1 + kLen)); if (keyStr === "info") { const infoStart = pos; decodeSkip(); const infoEnd = pos; const infoBuf = buf.slice(infoStart, infoEnd); const hashBuf = await crypto.subtle.digest("SHA-1", infoBuf); infoHash = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join(''); break; } else { decodeSkip(); } } if (infoHash) resolve(`magnet:?xt=urn:btih:${infoHash}&dn=${encodeURIComponent(file.name)}`); else reject(new Error(L.err_torrent_no_info)); } catch (err) { reject(err); } }; reader.onerror = () => reject(new Error(L.err_file_read)); reader.readAsArrayBuffer(file); }); fb.update(`${L.msg_creating_cloud_task} (${i + 1}/${files.length})`); let retry = 0; while (retry < 3) { try { await apiAddOfflineTask(magnetLink, saveToId); successCount++; break; } catch (reqErr) { if (reqErr.message && reqErr.message.includes('429')) { retry++; await sleep(2000 * retry); } else { throw reqErr; } } } } catch (e) { console.error(`Torrent parse/upload failed for ${file.name}:`, e); failCount++; } } fb.destroy(); if (failCount > 0) { showToast(L.msg_cloud_task_finish.replace('{s}', successCount).replace('{f}', failCount), 'warning'); } else if (successCount > 0) { showToast(L.msg_cloud_task_success.replace('{n}', successCount)); } if (successCount > 0) { if (typeof globalNeedsSync !== 'undefined') globalNeedsSync = true; setTimeout(() => updateQuotaUI(), 1000); const curPathId = S.path[S.path.length - 1].id || ''; if (S.offlineMode || (saveToId && curPathId === saveToId)) { load(false, true); } else if (!saveToId && S.path.length === 1 && window.pkSmartRefreshTrigger) { window.pkSmartRefreshTrigger(true); } } }; submit.onclick = async () => { if (submit.disabled) return; const rawVal = input.value.trim(); if (!rawVal) return; const isSmartFix = smartFixCheckbox ? smartFixCheckbox.checked : false; const finalLinks = parseAndCleanLinks(rawVal, isSmartFix); await submitCloudLinks(finalLinks, { targetId: saveToId, targetName: saveToName, currentPath: currentSavePath, sourceModal: m, removeSourceModal: true, onTargetChange: (id, name, fullItem, selectedPathChain) => { saveToId = id; saveToName = name; if (dirLabel) dirLabel.textContent = name; if (selectedPathChain) currentSavePath = selectedPathChain; } }); }; }; } UI.btnNavHome.onclick = () => switchTab('home'); if(UI.btnNavStarred) UI.btnNavStarred.onclick = () => switchTab('starred'); if(UI.btnNavRecent) UI.btnNavRecent.onclick = () => switchTab('recent'); if(UI.btnNavHistory) UI.btnNavHistory.onclick = () => switchTab('history'); if(UI.btnNavUpload) UI.btnNavUpload.onclick = () => switchTab('upload'); if(UI.btnNavShare) UI.btnNavShare.onclick = () => switchTab('share'); if(UI.btnNavShareParse) UI.btnNavShareParse.onclick = () => switchTab('shareParse'); if (UI.btnNavOffline) { UI.btnNavOffline.onclick = () => { switchTab('offline'); }; } UI.btnNavTrash.onclick = () => switchTab('trash'); if (UI.btnTrashRefresh) { UI.btnTrashRefresh.onclick = () => { updateQuotaUI(); load(false, true); }; } UI.btnRestore.onclick = async () => { const ids = S.getSelectedIds(); if (ids.length === 0) return; ensureItemMap(); const progressTask = FloatBarManager.create(L.msg_prepare_restore); const updateFloat = progressTask.update; isGUISensitive = true; S.clearSelection(); try { const BATCH_SIZE = 500; const total = ids.length; const taskIds =[]; const affectedParentIds = new Set(); const restoredFolders = []; ids.forEach(id => { const item = S.itemMap.get(id); if (item) { if (item.kind === 'drive#folder') restoredFolders.push(item); if (item.parent_id) affectedParentIds.add(item.parent_id); else affectedParentIds.add('root'); } }); updateFloat(L.msg_submit_request.replace('{c}', 0).replace('{t}', total)); for (let i = 0; i < total; i += BATCH_SIZE) { const chunk = ids.slice(i, i + BATCH_SIZE); const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/files:batchUntrash`, { method: 'POST', headers: getHeaders(), body: JSON.stringify({ ids: chunk }) }); if (!res.ok) throw new Error(`Batch Untrash Error ${res.status}`); const data = await res.json(); if (data.task_id) { taskIds.push(data.task_id); } updateFloat(L.msg_submit_request.replace('{c}', Math.min(i + BATCH_SIZE, total)).replace('{t}', total)); await sleep(50); } if (taskIds.length > 0) { updateFloat(L.msg_wait_server.replace('{c}', 0).replace('{t}', taskIds.length)); const pendingTasks = new Set(taskIds); let pollRetries = 0; const maxPollRetries = 120; while (pendingTasks.size > 0 && pollRetries < maxPollRetries) { await sleep(1000); pollRetries++; const currentIdsToCheck = Array.from(pendingTasks).join(','); const filters = { phase: { eq: "PHASE_TYPE_COMPLETE" }, id: { in: currentIdsToCheck } }; const filterStr = encodeURIComponent(JSON.stringify(filters)); const pollUrl = `https://api-drive.mypikpak.com/drive/v1/tasks?with=reference_resource&type=&thumbnail_size=SIZE_SMALL&limit=100&filters=${filterStr}`; try { const tRes = await fetch(pollUrl, { headers: getHeaders() }); if (tRes.ok) { const tData = await tRes.json(); const completedTasks = tData.tasks || []; completedTasks.forEach(t => { if (pendingTasks.has(t.id)) { pendingTasks.delete(t.id); } }); const doneCount = taskIds.length - pendingTasks.size; updateFloat(L.msg_server_processing.replace('{c}', doneCount).replace('{t}', taskIds.length)); } } catch (err) { console.warn("[Restore] Poll failed, retrying...", err); } } if (pendingTasks.size > 0) { console.warn(`[Restore] Timeout waiting for tasks: ${Array.from(pendingTasks)}`); } } const allIdSet = new Set(ids); ids.forEach(id => S.itemMap.delete(id)); S.items = S.items.filter(x => !allIdSet.has(x.id)); ids.forEach(id => { const reviveFromTombstone = (fid) => { if (globalTombstoneCache.has(fid)) { const savedData = globalTombstoneCache.get(fid); globalCache.set(fid, savedData); globalTombstoneCache.delete(fid); const list = Array.isArray(savedData) ? savedData : (savedData.items ||[]); list.forEach(child => { if (child.kind === 'drive#folder') { reviveFromTombstone(child.id); } }); } }; const wipeCrawlerMemory = (fid) => { scannedFolderIds.delete(fid); const children = []; for (const [childId, parentInfo] of globalParentIndex.entries()) { if (parentInfo.id === fid) { children.push(childId); } } children.forEach(childId => wipeCrawlerMemory(childId)); }; reviveFromTombstone(id); wipeCrawlerMemory(id); }); if (typeof globalCache !== 'undefined') { const cleanListChunk = (raw) => { if (Array.isArray(raw)) return raw.filter(f => !allIdSet.has(f.id)); if (raw && Array.isArray(raw.items)) { raw.items = raw.items.filter(f => !allIdSet.has(f.id)); return raw; } return raw; }; for (const key of globalCache.keys()) { if (key && (key.endsWith('_root') || key === 'root_trashed')) { globalCache.set(key, cleanListChunk(globalCache.get(key))); } } for (const key of S.cache.keys()) { if (key && (key.endsWith('_root') || key === 'root_trashed')) { S.cache.set(key, cleanListChunk(S.cache.get(key))); } } } refresh(); affectedParentIds.forEach(pid => { if (pid && pid !== 'root') gmSet('pk_fmod_' + pid, new Date(getServerNow()).toISOString()); }); affectedParentIds.forEach(pid => { const keys = (pid === 'root' || pid === '') ? ['root', ''] : [pid]; keys.forEach(k => { if (typeof globalCache !== 'undefined') globalCache.delete(k); if (S.cache) S.cache.delete(k); if (typeof scannedFolderIds !== 'undefined') scannedFolderIds.delete(k); if (typeof globalDirtyFolders !== 'undefined') globalDirtyFolders.add(k); }); const queueId = (pid === 'root') ? '' : pid; backgroundQueue.unshift({ id: queueId, name: "Refill_Restore", retryCount: 0 }); }); restoredFolders.forEach(folder => { scannedFolderIds.delete(folder.id); backgroundQueue.unshift({ id: folder.id, name: folder.name, retryCount: 0 }); }); runBackgroundCrawler(); if (typeof globalNeedsSync !== 'undefined') globalNeedsSync = true; showToast(L.msg_restore_done.replace('{n}', total)); } catch(e) { showAlert(`${L.str_error}: ${e.message}`); load(false, true); } finally { if (progressTask) progressTask.destroy(); isGUISensitive = false; } }; UI.btnDelForever.onclick = async () => { const ids = S.getSelectedIds(); const count = ids.length; if (count === 0) return; ensureItemMap(); if (!await showConfirm(L.msg_del_forever_confirm.replace('{n}', count))) return; await executeBatchDelete(ids, { hardDelete: true, silent: false }); }; UI.btnEmptyTrash.onclick = async () => { if (!await showConfirm(L.msg_empty_trash_confirm)) return; if (S.items.length === 0) return; S._isEmptyingTrash = true; setLoad(true); updateLoadTxt(L.str_deleting); try { const res = await fetch('https://api-drive.mypikpak.com/drive/v1/files/trash:empty', { method: 'PATCH', headers: getHeaders() }); if (!res.ok) throw new Error(`API Error ${res.status}`); S.items = []; S.display = []; S.itemMap.clear(); S.clearSelection(); if (typeof globalCache !== 'undefined') globalCache.delete('root_trashed'); if (S.cache) S.cache.delete('root_trashed'); refresh(); updateStat(); showToast(L.msg_trash_emptied); } catch (e) { showAlert(`${L.str_error}: ${e.message}`); } finally { setLoad(false); S._isEmptyingTrash = false; } }; const openSettingsModal = () => { const inputStyle = `width:100%; height:44px; padding:0 15px; border:2px solid var(--pk-bd); border-radius:8px; background:var(--pk-bg); color:var(--pk-fg); font-size:14px; font-weight:600; outline:none; transition:border-color 0.2s; box-sizing:border-box;`; const areaStyle = `width:100%; min-height:60px; max-height:120px; padding:12px 15px; border:2px solid var(--pk-bd); border-radius:8px; background:var(--pk-bg); color:var(--pk-fg); font-size:13px; font-weight:600; outline:none; transition:border-color 0.2s; box-sizing:border-box; resize:vertical; line-height:1.5; font-family:inherit; cursor:auto;`; const labelStyle = `position:absolute; top:0; transform:translateY(-50%); left:10px; background:var(--pk-bg); padding:0 5px; line-height:1; font-size:11px; color:var(--pk-pri); font-weight:bold; pointer-events:none; z-index:1;`; const curLang = gmGet('pk_lang', lang); const curEngine = gmGet('pk_search_engine', 'google'); const curDefaultVideoQuality = getDefaultVideoQualityPref(); const curDefaultOpenPlayer = getDefaultOpenPlayerPref(); const curVideoLoadProgressCache = shouldLoadVideoProgressCache(); const curAriaUrl = gmGet('pk_aria2_url', ''); const curAriaToken = gmGet('pk_aria2_token', ''); const curAriaKeepStructure = getBoolPref('pk_aria2_keep_structure', CONF.aria2KeepFolderStructure); const curDownloadAccelEnable = getBoolPref('pk_download_accel_enable', CONF.downloadAccelEnable); const curDownloadAccelDomain = gmGet('pk_download_accel_domain', CONF.downloadAccelDomain); const curDownloadAccelMode = normalizeDownloadAccelMode(gmGet('pk_download_accel_mode', CONF.downloadAccelMode)); const curDownloadAccelQueryParam = normalizeDownloadAccelQueryParam(gmGet('pk_download_accel_query_param', CONF.downloadAccelQueryParam)); const curBlur = gmGet('pk_blur_thumb', false); const curBlurScope = gmGet('pk_blur_scope', curBlur ? 'list' : 'off'); const curHideButtonText = gmGet('pk_hide_button_text', false); let selectedLang = curLang; let selectedEngine = curEngine; let selectedDefaultVideoQuality = curDefaultVideoQuality; let selectedDefaultOpenPlayer = curDefaultOpenPlayer; let selectedDownloadAccelMode = curDownloadAccelMode; let totalStorageBytes = 0; const keys = typeof GM_listValues !== 'undefined' ? GM_listValues() : Object.keys(localStorage); keys.forEach(k => { if (k.startsWith('pk_')) { const val = typeof GM_getValue !== 'undefined' ? GM_getValue(k) : localStorage.getItem(k); totalStorageBytes += (k.length + JSON.stringify(val || '').length); } }); if (typeof globalCache !== 'undefined') { for (const [k, v] of globalCache.entries()) { try { totalStorageBytes += k.toString().length + JSON.stringify(v).length; } catch(e){} } } const storageDisplay = fmtSize(totalStorageBytes); const m = showModal(`

${L.modal_settings_title}

${L.label_lang}
${CONF.crumbIcons.down}
简体中文
繁體中文
English
한국어
日本語
Indonesia
Bahasa Melayu
${L.label_script_update_info}
${L.label_script_current_version}
${esc(getScriptVersion())}
${L.label_script_latest_version}
${esc(getScriptUpdateLatestText(readScriptUpdateCache()))}
${L.label_script_last_check}
${esc(formatScriptUpdateCheckedAt((readScriptUpdateCache() || {}).checkedAt))}
${L.label_turbo_mode}
${L.label_privacy_mode}
${L.label_blur_cover} ${curBlurScope === 'off' ? L.opt_privacy_off : curBlurScope === 'list' ? L.opt_privacy_list : curBlurScope === 'grid' ? L.opt_privacy_grid : L.opt_privacy_both}
${CONF.crumbIcons.down}
${L.opt_privacy_off}
${L.opt_privacy_list}
${L.opt_privacy_grid}
${L.opt_privacy_both}
${L.label_hide_button_text}
${L.title_blacklist}
${L.label_clipboard_magnet_focus}
${L.lbl_browse_exp}
${L.label_sort_pref}
${L.label_view_pref}
${L.label_search_engine}
${CONF.crumbIcons.down}
${L.opt_engine_google}
${L.opt_engine_yandex}
${L.opt_engine_saucenao}
${L.opt_engine_tracemoe}
${L.lbl_video_playback_settings}
${L.label_default_open_player}
${CONF.crumbIcons.down}
${L.opt_player_script}
${L.opt_player_potplayer}
${L.label_default_video_quality}
${CONF.crumbIcons.down}
${L.str_original}
${L.opt_quality_1080p}
${L.opt_quality_720p}
${L.opt_quality_480p}
${L.lbl_dl_filter}
${L.lbl_ana_min}
${CONF.crumbIcons.down.replace('points="6 9 12 15 18 9"', 'points="18 15 12 9 6 15"')}
${CONF.crumbIcons.down}
-
${L.lbl_ana_max}
${CONF.crumbIcons.down.replace('points="6 9 12 15 18 9"', 'points="18 15 12 9 6 15"')}
${CONF.crumbIcons.down}
${CONF.crumbIcons.down}
MB
GB
TB
${L.label_dl_filter_ext}
${L.label_dl_filter_name}
${L.desc_dl_filter}
${L.label_download_accel_enable}
${L.label_download_accel_mode}
${CONF.crumbIcons.down}
${L.label_download_accel_mode_prefix}
${L.label_download_accel_mode_query}
${L.label_download_accel_domain}
${L.label_download_accel_query_param}
${L.label_aria2_config}
${L.label_aria2_url}
${L.btn_default}
${L.lbl_aria2_status}
${L.label_aria2_token}
${L.lbl_pwd_manage}
${CONF.icons.vault.replace('width="16"','width="20"').replace('height="16"','width="20"')} ${L.title_pwd_vault}
${L.lbl_config_manage}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { Object.assign(modalBox.style, { width: 'auto', padding: '0', overflow: 'hidden', height: 'auto', minHeight: 'auto' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '26px', right: '26px' }); } const updateSettingsScriptVersionInfo = (info = readScriptUpdateCache()) => { const curEl = m.querySelector('#txt_script_current_version'); const latestEl = m.querySelector('#txt_script_latest_version'); const checkEl = m.querySelector('#txt_script_last_check'); if (curEl) curEl.textContent = getScriptVersion(); if (latestEl) { const hasNew = isScriptUpdateNew(info); latestEl.textContent = getScriptUpdateLatestText(info); latestEl.classList.toggle('new', hasNew); latestEl.onclick = hasNew ? () => { const m = {tc:'README(tc).md',en:'README(en).md',ko:'README(ko).md',ja:'README(ja).md',id:'README(id).md',ms:'README(ms).md'}; const f = m[getLang()]; window.open(`${CONF.scriptUpdateProjectUrl}/blob/main/${f ? `i18n/${f}` : 'README.md'}`, '_blank', 'noopener,noreferrer');} : null; } if (checkEl) checkEl.textContent = formatScriptUpdateCheckedAt(info && info.checkedAt); }; updateSettingsScriptVersionInfo(); fetchScriptUpdateInfo(false).then(info => { updateSettingsScriptVersionInfo(info); if (isScriptUpdateNew(info)) showScriptUpdateToast(info); }).catch(() => updateSettingsScriptVersionInfo(readScriptUpdateCache())); const bindSelect = (id, currentVal, onSelect) => { const container = m.querySelector(`#${id}`); const trigger = container.querySelector('.pk-select-trigger'); const menu = container.querySelector('.pk-select-menu'); const txt = container.querySelector('span'); const items = container.querySelectorAll('.pk-select-item'); items.forEach(item => { if (item.dataset.val === currentVal) { item.classList.add('act'); txt.textContent = item.textContent; } item.onclick = (e) => { e.stopPropagation(); items.forEach(i => i.classList.remove('act')); item.classList.add('act'); txt.textContent = item.textContent; menu.style.display = 'none'; onSelect(item.dataset.val); }; }); trigger.onclick = (e) => { e.stopPropagation(); m.querySelectorAll('.pk-select-menu').forEach(om => { if(om !== menu) om.style.display = 'none'; }); menu.style.display = menu.style.display === 'block' ? 'none' : 'block'; }; }; bindSelect('cs_set_lang', curLang, (val) => { selectedLang = val; }); bindSelect('cs_set_engine', curEngine, (val) => { selectedEngine = val; }); bindSelect('cs_default_open_player', curDefaultOpenPlayer, (val) => { selectedDefaultOpenPlayer = normalizeDefaultOpenPlayer(val); }); bindSelect('cs_default_video_quality', curDefaultVideoQuality, (val) => { selectedDefaultVideoQuality = normalizeDefaultVideoQuality(val); }); bindSelect('cs_download_accel_mode', curDownloadAccelMode, (val) => { selectedDownloadAccelMode = normalizeDownloadAccelMode(val); updateAccelModeTip(); updateAccelBorder(); }); const ariaInp = m.querySelector('#set_aria_url'); const ariaTok = m.querySelector('#set_aria_token'); const ariaEye = m.querySelector('#btn_aria_token_eye'); const ariaDot = m.querySelector('#aria_test_dot'); const ariaTxt = m.querySelector('#aria_test_txt'); const ariaBox = m.querySelector('#aria_test_res'); const ariaGroup = m.querySelector('#pk_aria2_group'); const updateAriaGroupBorder = () => { if (ariaGroup) ariaGroup.classList.toggle('pk-inner-active', !!(ariaInp.value.trim() || ariaTok.value.trim())); }; let ariaTimer = null; let ariaTokenVisible = false; const runAriaTest = async () => { const url = ariaInp.value.trim(); const token = ariaTok.value.trim(); const showTip = () => showAlert(L.tip_mixed_content, L.lbl_aria2_status); if (!url) { ariaDot.className = 'pk-aria-dot'; ariaTxt.textContent = L.lbl_aria2_status; ariaBox.onclick = showTip; ariaBox.style.cursor = 'pointer'; return; } ariaDot.className = 'pk-aria-dot wait'; ariaTxt.textContent = L.str_connecting; ariaBox.onclick = showTip; ariaBox.style.cursor = 'pointer'; const testUrl = normalizeAriaRpcUrl(url); const payload = { jsonrpc: '2.0', method: 'aria2.getVersion', id: 'pk_live_test', params: buildAriaRpcParams(token) }; try { await aria2RpcRequest(testUrl, payload, 3000); ariaDot.className = 'pk-aria-dot ok'; ariaTxt.textContent = L.str_connected; ariaBox.onclick = null; ariaBox.style.cursor = 'default'; } catch (e) { ariaDot.className = 'pk-aria-dot err'; ariaTxt.textContent = L.str_conn_fail; ariaBox.onclick = showTip; ariaBox.style.cursor = 'pointer'; } }; const debouncedTest = () => { clearTimeout(ariaTimer); ariaTimer = setTimeout(runAriaTest, 600); }; ariaInp.oninput = (e) => { if (e.target.oninput) e.target.style.borderColor = e.target.value.trim() ? 'var(--pk-pri)' : 'var(--pk-bd)'; updateAriaGroupBorder(); debouncedTest(); }; ariaTok.oninput = (e) => { if (e.target.oninput) e.target.style.borderColor = e.target.value.trim() ? 'var(--pk-pri)' : 'var(--pk-bd)'; updateAriaGroupBorder(); debouncedTest(); }; if (ariaEye) { ariaEye.onclick = (e) => { e.preventDefault(); e.stopPropagation(); ariaTokenVisible = !ariaTokenVisible; ariaTok.style.webkitTextSecurity = ariaTokenVisible ? 'none' : 'disc'; ariaEye.innerHTML = ariaTokenVisible ? CONF.icons.eyeOff : CONF.icons.eye; }; } m.querySelector('#btn_aria_default').onclick = () => { ariaInp.value = 'http://localhost:6800/jsonrpc'; ariaInp.style.borderColor = 'var(--pk-pri)'; updateAriaGroupBorder(); debouncedTest(); }; setTimeout(runAriaTest, 200); const clickAway = () => m.querySelectorAll('.pk-select-menu').forEach(menu => menu.style.display = 'none'); setTimeout(() => document.addEventListener('click', clickAway), 0); const _orgRemove = m.remove.bind(m); m.remove = () => { document.removeEventListener('click', clickAway); _orgRemove(); }; const extInp = m.querySelector('#set_dl_filter_ext'); const nameInp = m.querySelector('#set_dl_filter_name'); const sizeMinInp = m.querySelector('#set_dl_filter_size_min'); const sizeMaxInp = m.querySelector('#set_dl_filter_size_max'); const groupEl = m.querySelector('#pk_dl_group'); const updateDlBorders = () => { const hasE = extInp.value.trim() !== ''; const hasN = nameInp.value.trim() !== ''; const hasSMin = sizeMinInp.value.trim() !== ''; const hasSMax = sizeMaxInp.value.trim() !== ''; extInp.classList.toggle('pk-active-border', hasE); nameInp.classList.toggle('pk-active-border', hasN); sizeMinInp.classList.toggle('pk-active-border', hasSMin); sizeMaxInp.classList.toggle('pk-active-border', hasSMax); groupEl.classList.toggle('pk-typing-active', hasE || hasN || hasSMin || hasSMax); }; extInp.oninput = nameInp.oninput = sizeMinInp.oninput = sizeMaxInp.oninput = updateDlBorders; updateDlBorders(); const accelDomainInp = m.querySelector('#set_download_accel_domain'); const accelQueryParamInp = m.querySelector('#set_download_accel_query_param'); const accelEnableInp = m.querySelector('#set_download_accel_enable'); const accelGroupEl = m.querySelector('#pk_download_accel_group'); const updateAccelModeTip = () => { const isQuery = selectedDownloadAccelMode === 'query'; if (accelQueryParamInp) accelQueryParamInp.closest('div').style.display = isQuery ? 'block' : 'none'; if (accelDomainInp) accelDomainInp.placeholder = isQuery ? 'https://download.example.com/proxy' : 'https://download.example.com'; }; const updateAccelBorder = () => { const hasDomain = !!(accelDomainInp && accelDomainInp.value.trim()); const customParam = !!(accelQueryParamInp && accelQueryParamInp.value.trim() && normalizeDownloadAccelQueryParam(accelQueryParamInp.value) !== CONF.downloadAccelQueryParam); const hasTypedValue = hasDomain || customParam; if (accelGroupEl) accelGroupEl.classList.toggle('pk-typing-active', hasTypedValue); if (accelDomainInp) { accelDomainInp.classList.toggle('pk-active-border', hasDomain); accelDomainInp.style.borderColor = hasDomain ? 'var(--pk-pri)' : 'var(--pk-bd)'; } if (accelQueryParamInp) { accelQueryParamInp.classList.toggle('pk-active-border', customParam); accelQueryParamInp.style.borderColor = customParam ? 'var(--pk-pri)' : 'var(--pk-bd)'; } }; if (accelDomainInp) accelDomainInp.oninput = updateAccelBorder; if (accelQueryParamInp) accelQueryParamInp.oninput = updateAccelBorder; if (accelEnableInp) accelEnableInp.onchange = updateAccelBorder; updateAccelModeTip(); updateAccelBorder(); const curDlSizeUnit = gmGet('pk_dl_filter_size_unit', 'MB'); bindSelect('cs_set_dl_size_unit', curDlSizeUnit, (val) => {}); m.querySelector('#dl_size_dec_min').onclick = (ev) => { ev.preventDefault(); ev.stopPropagation(); let v = Math.floor(Number(sizeMinInp.value)) || 0; if (v > 0) sizeMinInp.value = v - 1; ensureDlSizeRange(); updateDlBorders(); }; m.querySelector('#dl_size_inc_min').onclick = (ev) => { ev.preventDefault(); ev.stopPropagation(); let v = Math.floor(Number(sizeMinInp.value)) || 0; sizeMinInp.value = v + 1; ensureDlSizeRange(); updateDlBorders(); }; m.querySelector('#dl_size_dec_max').onclick = (ev) => { ev.preventDefault(); ev.stopPropagation(); let v = Math.floor(Number(sizeMaxInp.value)) || 0; if (v > 0) sizeMaxInp.value = v - 1; ensureDlSizeRange(); updateDlBorders(); }; m.querySelector('#dl_size_inc_max').onclick = (ev) => { ev.preventDefault(); ev.stopPropagation(); let v = Math.floor(Number(sizeMaxInp.value)) || 0; sizeMaxInp.value = v + 1; ensureDlSizeRange(); updateDlBorders(); }; const ensureDlSizeRange = () => { let minVal = sizeMinInp.value.trim() === '' ? -1 : Math.floor(Number(sizeMinInp.value)); let maxVal = sizeMaxInp.value.trim() === '' ? -1 : Math.floor(Number(sizeMaxInp.value)); if (minVal >= 0 && maxVal >= 0 && minVal > maxVal) { if (document.activeElement === sizeMinInp) { sizeMaxInp.value = minVal; } else if (document.activeElement === sizeMaxInp) { sizeMinInp.value = maxVal; } else { sizeMaxInp.value = minVal; } } }; sizeMinInp.addEventListener('blur', ensureDlSizeRange); sizeMaxInp.addEventListener('blur', ensureDlSizeRange); sizeMinInp.addEventListener('change', ensureDlSizeRange); sizeMaxInp.addEventListener('change', ensureDlSizeRange); m.querySelector('#btn_open_vault').onclick = (e) => { e.stopPropagation(); const subM = document.createElement('div'); subM.className = 'pk-modal-ov'; subM.style.zIndex = (++modalZIndexCounter).toString(); if (document.querySelector('.pk-ov').classList.contains('pk-dark')) subM.classList.add('pk-dark'); const savedCount = gmGet('pk_pwd_try_count', 10); const savedPwds = (() => { try { return JSON.parse(gmGet('pk_pwd_vault', '[]')).map(x => typeof x === 'object' ? x.p : x).join('\n'); } catch { return ''; } })(); subM.innerHTML = `
${CONF.icons.close}

${CONF.icons.vault.replace('width="16"','width="22"').replace('height="16"','width="22"')} ${L.title_pwd_vault}

${L.lbl_pwd_try_count}
${L.tip_pwd_manual}
${L.btn_cancel}
`; document.body.appendChild(subM); const cntInp = subM.querySelector('#vault_cnt_val'); subM.querySelector('#vault_cnt_dec').onclick = (ev) => { ev.preventDefault(); ev.stopPropagation(); let v = Math.floor(Number(cntInp.value)) || 10; cntInp.value = Math.max(10, v - 1); }; subM.querySelector('#vault_cnt_inc').onclick = (ev) => { ev.preventDefault(); ev.stopPropagation(); let v = Math.floor(Number(cntInp.value)) || 0; cntInp.value = Math.min(50, v + 1); }; cntInp.oninput = () => { let raw = cntInp.value; if (raw === "") return; let v = parseInt(raw); if (isNaN(v)) { cntInp.value = 10; return; } if (v > 50) cntInp.value = 50; }; cntInp.onblur = () => { let v = parseInt(cntInp.value); if (isNaN(v) || v < 10) cntInp.value = 10; }; const area = subM.querySelector('#vault_pwd_area'); area.onfocus = () => area.style.borderColor = 'var(--pk-pri)'; area.onblur = () => area.style.borderColor = 'var(--pk-bd)'; area.value = savedPwds; area.oninput = () => { let lines = area.value.split('\n'); let changed = false; let msg = ""; if (lines.length > 50) { lines = lines.slice(0, 50); changed = true; msg = L.err_vault_max; } for (let i = 0; i < lines.length; i++) { if (lines[i].length > 127) { lines[i] = lines[i].substring(0, 127); changed = true; msg = L.err_pwd_len; } } if (changed) { const cursor = area.selectionStart; area.value = lines.join('\n'); area.setSelectionRange(cursor, cursor); showToast(msg, 'error'); area.style.borderColor = '#d93025'; setTimeout(() => { if(area) area.style.borderColor = 'var(--pk-pri)'; }, 1000); } }; const doSave = () => { let cnt = Math.floor(Number(cntInp.value)); if (isNaN(cnt) || cnt < 10) cnt = 10; if (cnt > 50) cnt = 50; const inputPwds = area.value.split('\n').map(s => s.trim()).filter(s => s); if (inputPwds.length > 50) { showToast(L.err_vault_max, 'error'); area.style.borderColor = '#d93025'; return; } let changed = false; try { const oldList = JSON.parse(gmGet('pk_pwd_vault', '[]')).map(x => typeof x === 'object' ? x : {p: x, h: 0}); const hitMap = new Map(oldList.map(x => [x.p, x.h])); const newList = [...new Set(inputPwds)].map(p => ({ p: p, h: hitMap.get(p) || 0 })); newList.sort((a, b) => b.h - a.h); const oldVaultRaw = gmGet('pk_pwd_vault', '[]'); const newVaultRaw = JSON.stringify(newList); changed = String(oldVaultRaw) !== newVaultRaw || Number(gmGet('pk_pwd_try_count', 10)) !== cnt; if (changed) { gmSet('pk_pwd_try_count', cnt); gmSet('pk_pwd_vault', newVaultRaw); } } catch(e) { const newVaultRaw = JSON.stringify([...new Set(inputPwds)]); changed = String(gmGet('pk_pwd_vault', '[]')) !== newVaultRaw || Number(gmGet('pk_pwd_try_count', 10)) !== cnt; if (changed) { gmSet('pk_pwd_try_count', cnt); gmSet('pk_pwd_vault', newVaultRaw); } } subM.remove(); if (changed) showToast(L.msg_settings_saved_plain); }; subM.querySelector('#vault_save').onclick = doSave; subM.querySelector('#vault_cancel').onclick = () => subM.remove(); subM.querySelector('.pk-modal-close').onclick = () => subM.remove(); }; m.querySelector('#btn_cfg_clean').onclick = async () => { const gmKeys = typeof GM_listValues !== 'undefined' ? GM_listValues() : []; const lsKeys = Object.keys(localStorage).filter(k => typeof k === 'string' && k.startsWith('pk_')); const keys = Array.from(new Set([...(Array.isArray(gmKeys) ? gmKeys : []), ...lsKeys])); const sizes = { index: 0, pref: 0, rules: 0, vault: 0, history: 0, cache: 0 }; if (typeof globalCache !== 'undefined') { for (const [k, v] of globalCache.entries()) { try { sizes.index += k.toString().length + JSON.stringify(v).length; } catch(e){} } } const getCat = (k) => { if (!k.startsWith('pk_')) return null; if (k.startsWith('pk_archive_pwd_') || k === 'pk_pwd_vault' || k === 'pk_pwd_try_count') return 'vault'; if (k.startsWith('pk_duration_')) return 'history'; const cacheKeys = ['pk_captured_captcha', 'pk_i18n_manifest', 'pk_script_update_cache', 'pk_potplayer_launch_state', 'pk_potplayer_protocol_state']; if (k.startsWith('pk_fmod_') || k.startsWith('pk_i18n_') || k.startsWith(CONF.scriptUpdateDismissPrefix) || cacheKeys.includes(k)) return 'cache'; const ruleKeys =['pk_blacklist', 'pk_blacklist_folders', 'pk_aria2_url', 'pk_aria2_token', 'pk_download_accel_enable', 'pk_download_accel_domain', 'pk_download_accel_mode', 'pk_download_accel_query_param', 'pk_dl_filter_ext', 'pk_dl_filter_name', 'pk_dl_filter_size_min', 'pk_dl_filter_size_max', 'pk_dl_filter_size_unit', 'pk_search_engine', 'pk_search_history', 'pk_expired_shares', 'pk_share_limits', 'pk_bn_find_hist', 'pk_bn_rep_hist']; if (ruleKeys.includes(k) || k.startsWith('pk_scan_last_') || k.startsWith('pk_analyze_last_') || k === 'pk_dup_strictness') return 'rules'; return 'pref'; }; keys.forEach(k => { const cat = getCat(k); if (cat) { let val = typeof GM_getValue !== 'undefined' ? GM_getValue(k) : undefined; if (val === undefined || val === null) val = localStorage.getItem(k); sizes[cat] += (k.length + (val ? JSON.stringify(val).length : 0)); } }); const renderLbl = (cat, txt, isChecked = false, isMandatory = false) => { const sz = sizes[cat]; if (sz === 0 && cat !== 'index' && cat !== 'cache') return ''; const szStr = fmtSize(sz); const checkAttr = (isChecked || isMandatory) ? 'checked' : ''; const disAttr = isMandatory ? 'disabled' : ''; const cursor = isMandatory ? 'not-allowed' : 'pointer'; const opacity = isMandatory ? '0.7' : '1'; return ``; }; const htmlOptions =[ renderLbl('index', L.opt_cfg_index, true, true), renderLbl('pref', L.opt_cfg_pref), renderLbl('rules', L.opt_cfg_rules), renderLbl('vault', L.opt_cfg_vault), renderLbl('history', L.opt_cfg_history), renderLbl('cache', L.opt_cfg_cache) ].filter(Boolean).join(''); if (!htmlOptions) return; const cleanM = showModal(`

${L.title_clean_data}

${htmlOptions}
`); cleanM.querySelector('#clean_cancel').onclick = () => cleanM.remove(); cleanM.querySelector('#clean_confirm').onclick = async () => { const selected = Array.from(cleanM.querySelectorAll('.clean-opt:checked')).map(el => el.value); if (selected.length === 0) { cleanM.remove(); return; } if (!await showConfirm(L.msg_clean_confirm)) return; if (selected.includes('index')) { if (typeof globalCache !== 'undefined') globalCache.clear(); if (typeof S !== 'undefined' && S.cache) S.cache.clear(); if (typeof globalLineageMap !== 'undefined') globalLineageMap.clear(); if (typeof globalParentIndex !== 'undefined') globalParentIndex.clear(); if (typeof globalDirtyFolders !== 'undefined') globalDirtyFolders.clear(); if (typeof scannedFolderIds !== 'undefined') scannedFolderIds.clear(); if (typeof backgroundQueue !== 'undefined') backgroundQueue.length = 0; if (typeof isBackgroundRunning !== 'undefined') isBackgroundRunning = false; if (typeof DurationProber !== 'undefined') DurationProber.reset(); } if (selected.includes('cache') && window.localforage) { try { await window.localforage.dropInstance({ name: 'pk_thumbs', storeName: 'snapshots' }); } catch (e) { console.warn("Clear thumb cache error:", e); } } keys.forEach(k => { const cat = getCat(k); if (cat && selected.includes(cat)) { try { if (typeof GM_deleteValue !== 'undefined') { GM_deleteValue(k); } else if (typeof GM_setValue !== 'undefined') { GM_setValue(k, ''); } localStorage.removeItem(k); } catch (e) { console.warn("Delete config error:", e); } } }); showToast(L.msg_clean_success); setTimeout(() => location.reload(), 1500); }; }; m.querySelector('#btn_cfg_export').onclick = () => { const gmKeys = typeof GM_listValues !== 'undefined' ? GM_listValues() : []; const lsKeys = Object.keys(localStorage).filter(k => typeof k === 'string' && k.startsWith('pk_')); const keys = Array.from(new Set([...(Array.isArray(gmKeys) ? gmKeys : []), ...lsKeys])); const config = { "_pk_metadata": { "signature": "PIKPAK_ENHANCEMENT_MASTER", "version": version, "export_at": new Date().toISOString(), "author": "digbug82", "scope": "whitelist" } }; const exportExactKeys = new Set([ 'pk_lang', 'pk_theme', 'pk_turbo_mode', 'pk_file_view_mode', 'pk_view_independent', 'pk_folder_view_prefs', 'pk_keep_pos', 'pk_pos_left', 'pk_pos_top', 'pk_blur_thumb', 'pk_blur_scope', 'pk_hide_button_text', 'pk_comic_mode', 'pk_clipboard_magnet_focus', 'pk_clipboard_magnet_paste', 'pk_audio_play_mode', 'pk_audio_vol_level', 'pk_audio_vol_muted', 'pk_audio_mini_pos', 'pk_sort_independent', 'pk_folder_first', 'pk_folder_sort_prefs', 'pk_global_sort_pref', 'pk_ext_player', 'pk_default_open_player', 'pk_play_mode', 'pk_skip_intro', 'pk_skip_outro', 'pk_vol_muted', 'pk_vol_level', 'pk_suppress_global_warn', 'pk_blacklist', 'pk_blacklist_folders', 'pk_aria2_url', 'pk_aria2_token', 'pk_aria2_keep_structure', 'pk_download_accel_enable', 'pk_download_accel_domain', 'pk_download_accel_mode', 'pk_download_accel_query_param', 'pk_dl_filter_ext', 'pk_dl_filter_name', 'pk_dl_filter_size_min', 'pk_dl_filter_size_max', 'pk_dl_filter_size_unit', 'pk_search_engine', 'pk_default_video_quality', 'pk_video_load_progress_cache', 'pk_search_history', 'pk_expired_shares', 'pk_share_limits', 'pk_bn_find_hist', 'pk_bn_rep_hist', 'pk_dup_strictness', 'pk_skip_bl_on_del', 'pk_pwd_vault', 'pk_pwd_try_count' ]); const exportPrefixKeys = [ 'pk_scan_last_', 'pk_analyze_last_', 'pk_archive_pwd_', 'pk_duration_' ]; const isWhitelistedExportKey = (k) => exportExactKeys.has(k) || exportPrefixKeys.some(prefix => k.startsWith(prefix)); const readStoredValue = (k) => { let v = typeof GM_getValue !== 'undefined' ? GM_getValue(k, null) : null; if ((v === null || v === undefined || v === '') && localStorage.getItem(k) !== null) v = localStorage.getItem(k); return v; }; const pkKeys = keys.filter(k => isWhitelistedExportKey(k)); const getCatWeight = (k) => { if (k.startsWith('pk_archive_pwd_') || k === 'pk_pwd_vault' || k === 'pk_pwd_try_count' || k === 'pk_share_limits') return 3; if (k.startsWith('pk_duration_')) return 4; const ruleKeys = ['pk_blacklist', 'pk_blacklist_folders', 'pk_aria2_url', 'pk_aria2_token', 'pk_download_accel_enable', 'pk_download_accel_domain', 'pk_download_accel_mode', 'pk_download_accel_query_param', 'pk_dl_filter_ext', 'pk_dl_filter_name', 'pk_dl_filter_size_min', 'pk_dl_filter_size_max', 'pk_dl_filter_size_unit', 'pk_search_engine', 'pk_search_history', 'pk_expired_shares', 'pk_share_limits', 'pk_bn_find_hist', 'pk_bn_rep_hist', 'pk_dup_strictness', 'pk_skip_bl_on_del', 'pk_clipboard_magnet_focus']; if (ruleKeys.includes(k) || k.startsWith('pk_scan_last_') || k.startsWith('pk_analyze_last_')) return 2; return 1; }; pkKeys.sort((a, b) => { const wA = getCatWeight(a); const wB = getCatWeight(b); if (wA !== wB) return wA - wB; return a.localeCompare(b); }); pkKeys.forEach(k => { const v = readStoredValue(k); if (v !== null && v !== undefined && v !== '') config[k] = v; }); config._pk_metadata.exported_keys = pkKeys.length; const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const domEl = document.querySelector('.name.ellipsis'); let rawUserName = domEl ? (domEl.title || domEl.innerText) : 'Default'; const userName = rawUserName.trim().replace(/[\\/:*?"<>|]/g, '_'); const now = new Date(); const dateStr = now.toISOString().slice(0, 10).replace(/-/g, ''); const timeStr = now.getHours().toString().padStart(2, '0') + now.getMinutes().toString().padStart(2, '0'); const fileName = `PKM_Backup_${userName}_${dateStr}_${timeStr}.json`; const a = document.createElement('a'); a.href = url; a.download = fileName; a.click(); URL.revokeObjectURL(url); }; const fileInput = m.querySelector('#cfg_import_input'); m.querySelector('#btn_cfg_import').onclick = () => fileInput.click(); fileInput.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; if (!await showConfirm(L.msg_import_confirm)) { fileInput.value = ''; return; } const reader = new FileReader(); reader.onload = (ev) => { try { const config = JSON.parse(ev.target.result); if (!config._pk_metadata || config._pk_metadata.signature !== "PIKPAK_ENHANCEMENT_MASTER") { throw new Error("INVALID_SIGNATURE"); } const importExactKeys = new Set([ 'pk_lang', 'pk_theme', 'pk_turbo_mode', 'pk_file_view_mode', 'pk_view_independent', 'pk_folder_view_prefs', 'pk_keep_pos', 'pk_pos_left', 'pk_pos_top', 'pk_blur_thumb', 'pk_blur_scope', 'pk_hide_button_text', 'pk_comic_mode', 'pk_clipboard_magnet_focus', 'pk_clipboard_magnet_paste', 'pk_audio_play_mode', 'pk_audio_vol_level', 'pk_audio_vol_muted', 'pk_audio_mini_pos', 'pk_sort_independent', 'pk_folder_first', 'pk_folder_sort_prefs', 'pk_global_sort_pref', 'pk_ext_player', 'pk_default_open_player', 'pk_play_mode', 'pk_skip_intro', 'pk_skip_outro', 'pk_vol_muted', 'pk_vol_level', 'pk_suppress_global_warn', 'pk_blacklist', 'pk_blacklist_folders', 'pk_aria2_url', 'pk_aria2_token', 'pk_aria2_keep_structure', 'pk_download_accel_enable', 'pk_download_accel_domain', 'pk_download_accel_mode', 'pk_download_accel_query_param', 'pk_dl_filter_ext', 'pk_dl_filter_name', 'pk_dl_filter_size_min', 'pk_dl_filter_size_max', 'pk_dl_filter_size_unit', 'pk_search_engine', 'pk_default_video_quality', 'pk_video_load_progress_cache', 'pk_search_history', 'pk_expired_shares', 'pk_share_limits', 'pk_bn_find_hist', 'pk_bn_rep_hist', 'pk_dup_strictness', 'pk_skip_bl_on_del', 'pk_pwd_vault', 'pk_pwd_try_count' ]); const importPrefixKeys = [ 'pk_scan_last_', 'pk_analyze_last_', 'pk_archive_pwd_', 'pk_duration_' ]; const isWhitelistedImportKey = (k) => importExactKeys.has(k) || importPrefixKeys.some(prefix => k.startsWith(prefix)); const readStoredValue = (k) => { let v = typeof GM_getValue !== 'undefined' ? GM_getValue(k, null) : null; if ((v === null || v === undefined || v === '') && localStorage.getItem(k) !== null) v = localStorage.getItem(k); return v; }; const writeStoredValue = (k, v) => { if (typeof GM_setValue !== 'undefined') GM_setValue(k, v); else localStorage.setItem(k, typeof v === 'string' ? v : JSON.stringify(v)); if (k === 'pk_turbo_mode') localStorage.setItem(k, String(v)); }; const parseMaybeJson = (v) => { if (typeof v === 'string') { const s = v.trim(); if ((s.startsWith('[') && s.endsWith(']')) || (s.startsWith('{') && s.endsWith('}'))) { try { return JSON.parse(s); } catch {} } } return v; }; const isPlainObject = (v) => !!v && typeof v === 'object' && !Array.isArray(v); const mergeArrayUnique = (localArr, importedArr, limit = 100) => { const seen = new Set(); const merged = []; [...localArr, ...importedArr].forEach(item => { const key = (item && typeof item === 'object') ? JSON.stringify(item) : `v:${String(item)}`; if (seen.has(key)) return; seen.add(key); merged.push(item); }); return merged.slice(0, limit); }; const importKeys = Object.keys(config).filter(k => isWhitelistedImportKey(k)); importKeys.forEach(k => { const importedVal = config[k]; const localVal = readStoredValue(k); if (localVal === null || localVal === undefined || localVal === '') { writeStoredValue(k, importedVal); return; } try { if (k === 'pk_blacklist' || k === 'pk_blacklist_folders') { const localSet = new Set(String(localVal || '').split('\n').map(s => s.trim()).filter(s => s)); const importedSet = new Set(String(importedVal || '').split('\n').map(s => s.trim()).filter(s => s)); importedSet.forEach(v => localSet.add(v)); writeStoredValue(k, Array.from(localSet).join('\n')); return; } if (k === 'pk_dl_filter_ext' || k === 'pk_dl_filter_name') { const localSet = new Set(String(localVal || '').split(/[,,\n]/).map(s => s.trim()).filter(s => s)); const importedSet = new Set(String(importedVal || '').split(/[,,\n]/).map(s => s.trim()).filter(s => s)); importedSet.forEach(v => localSet.add(v)); writeStoredValue(k, Array.from(localSet).join(', ')); return; } const localObj = parseMaybeJson(localVal); const importedObj = parseMaybeJson(importedVal); if (k === 'pk_pwd_vault') { const localArr = Array.isArray(localObj) ? localObj : []; const importedArr = Array.isArray(importedObj) ? importedObj : []; const map = new Map(); localArr.forEach(x => { const p = typeof x === 'object' ? x.p : x; const h = typeof x === 'object' ? (Number(x.h) || 0) : 0; if (p) map.set(p, { p, h }); }); importedArr.forEach(x => { const p = typeof x === 'object' ? x.p : x; const h = typeof x === 'object' ? (Number(x.h) || 0) : 0; if (!p) return; if (map.has(p)) map.get(p).h += h; else map.set(p, { p, h }); }); writeStoredValue(k, JSON.stringify(Array.from(map.values()).sort((a, b) => b.h - a.h).slice(0, 50))); return; } if (k === 'pk_expired_shares') { const localArr = Array.isArray(localObj) ? localObj : []; const importedArr = Array.isArray(importedObj) ? importedObj : []; const map = new Map(); localArr.forEach(x => { if (x && x.id) map.set(x.id, x); }); importedArr.forEach(x => { if (x && x.id) map.set(x.id, x); }); writeStoredValue(k, JSON.stringify(Array.from(map.values()))); return; } if (Array.isArray(localObj) && Array.isArray(importedObj)) { writeStoredValue(k, JSON.stringify(mergeArrayUnique(localObj, importedObj, 100))); return; } if (isPlainObject(localObj) && isPlainObject(importedObj)) { writeStoredValue(k, JSON.stringify(Object.assign({}, localObj, importedObj))); return; } writeStoredValue(k, importedVal); } catch (e2) { writeStoredValue(k, importedVal); } }); showToast(L.msg_import_success); fileInput.value = ''; setTimeout(() => location.reload(), 1500); } catch (err) { let errorTip = ""; if (err.message === "INVALID_SIGNATURE") { errorTip = L.err_invalid_config; } else { errorTip = L.err_json_format; console.error("[Config Import]", err); } showAlert(errorTip, L.str_error); fileInput.value = ''; } }; reader.readAsText(file); }; const thumbScopeWrap = m.querySelector('#cs_thumb_scope'); const thumbScopeInput = m.querySelector('#set_thumb_scope'); const thumbScopeTrigger = m.querySelector('#cs_thumb_scope .pk-select-trigger'); const thumbScopeMenu = m.querySelector('#cs_thumb_scope .pk-select-menu'); const thumbScopeTxt = m.querySelector('#txt_thumb_scope'); if (thumbScopeWrap && thumbScopeInput && thumbScopeTrigger && thumbScopeMenu && thumbScopeTxt) { thumbScopeTrigger.onclick = (e) => { e.stopPropagation(); thumbScopeMenu.style.display = thumbScopeMenu.style.display === 'block' ? 'none' : 'block'; }; m.querySelectorAll('#cs_thumb_scope .pk-select-item').forEach(item => { item.onclick = (e) => { e.stopPropagation(); m.querySelectorAll('#cs_thumb_scope .pk-select-item').forEach(i => i.classList.remove('act')); item.classList.add('act'); thumbScopeInput.value = item.dataset.val; thumbScopeTxt.textContent = item.textContent; thumbScopeMenu.style.display = 'none'; }; }); m.addEventListener('click', () => { thumbScopeMenu.style.display = 'none'; }); } m.querySelector('#set_cancel').onclick = () => m.remove(); m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#set_save').click(); } }); m.querySelector('#set_save').onclick = async () => { const newTurbo = m.querySelector('#set_turbo').checked; const oldTurbo = gmGet('pk_turbo_mode', false); const newUrl = m.querySelector('#set_aria_url').value.trim(); const newToken = m.querySelector('#set_aria_token').value.trim(); const newAriaKeepStructure = m.querySelector('#set_aria_keep_structure').checked; const newDownloadAccelEnable = m.querySelector('#set_download_accel_enable').checked; const rawDownloadAccelDomain = m.querySelector('#set_download_accel_domain').value.trim(); const newDownloadAccelDomain = normalizeDownloadAccelBase(rawDownloadAccelDomain); const newDownloadAccelMode = normalizeDownloadAccelMode(selectedDownloadAccelMode); const newDownloadAccelQueryParam = normalizeDownloadAccelQueryParam(m.querySelector('#set_download_accel_query_param').value); const newBlurScope = m.querySelector('#set_thumb_scope').value; const newHideButtonText = m.querySelector('#set_hide_button_text').checked; const newKeepPos = m.querySelector('#set_keep_pos').checked; const newSkipBl = m.querySelector('#set_skip_bl').checked; const newClipboardMagnetFocus = m.querySelector('#set_clipboard_magnet_focus').checked; const newComicMode = m.querySelector('#set_comic_mode').checked; const sortPref = m.querySelector('input[name="set_sort_pref"]:checked').value; const viewPref = m.querySelector('input[name="set_view_pref"]:checked').value; const saveBtn = m.querySelector('#set_save'); const oldSig = JSON.stringify([curLang, oldTurbo, gmGet('pk_aria2_url', ''), gmGet('pk_aria2_token', ''), getBoolPref('pk_aria2_keep_structure', CONF.aria2KeepFolderStructure), getBoolPref('pk_download_accel_enable', CONF.downloadAccelEnable), normalizeDownloadAccelBase(gmGet('pk_download_accel_domain', CONF.downloadAccelDomain)), normalizeDownloadAccelMode(gmGet('pk_download_accel_mode', CONF.downloadAccelMode)), normalizeDownloadAccelQueryParam(gmGet('pk_download_accel_query_param', CONF.downloadAccelQueryParam)), gmGet('pk_blur_scope', gmGet('pk_blur_thumb', false) ? 'list' : 'off'), gmGet('pk_hide_button_text', false), gmGet('pk_keep_pos', true), gmGet('pk_skip_bl_on_del', true), gmGet('pk_clipboard_magnet_focus', true), gmGet('pk_comic_mode', true), gmGet('pk_sort_independent', false) ? 'indep' : 'global', gmGet('pk_view_independent', false) ? 'indep' : 'global', curEngine, curDefaultOpenPlayer, curDefaultVideoQuality, curVideoLoadProgressCache, gmGet('pk_dl_filter_ext', ''), gmGet('pk_dl_filter_size_min', ''), gmGet('pk_dl_filter_size_max', ''), gmGet('pk_dl_filter_size_unit', 'MB'), gmGet('pk_dl_filter_name', '')]); const newSig = JSON.stringify([selectedLang, newTurbo, newUrl, newToken, newAriaKeepStructure, newDownloadAccelEnable, newDownloadAccelDomain, newDownloadAccelMode, newDownloadAccelQueryParam, newBlurScope, newHideButtonText, newKeepPos, newSkipBl, newClipboardMagnetFocus, newComicMode, sortPref, viewPref, selectedEngine, normalizeDefaultOpenPlayer(selectedDefaultOpenPlayer), normalizeDefaultVideoQuality(selectedDefaultVideoQuality), !!m.querySelector('#set_video_load_progress_cache').checked, m.querySelector('#set_dl_filter_ext').value.trim(), m.querySelector('#set_dl_filter_size_min').value.trim(), m.querySelector('#set_dl_filter_size_max').value.trim(), m.querySelector('#cs_set_dl_size_unit .pk-select-item.act') ? m.querySelector('#cs_set_dl_size_unit .pk-select-item.act').dataset.val : 'MB', m.querySelector('#set_dl_filter_name').value.trim()]); const hasSettingsChanged = oldSig !== newSig; const reloadSettingsChanged = curLang !== selectedLang || newTurbo !== oldTurbo; const ariaChanged = newUrl !== gmGet('pk_aria2_url', '') || newToken !== gmGet('pk_aria2_token', ''); if (rawDownloadAccelDomain && !newDownloadAccelDomain) { const domainInp = m.querySelector('#set_download_accel_domain'); if (domainInp) domainInp.style.borderColor = '#d93025'; showToast(L.msg_download_accel_invalid_domain, 'error'); return; } if (!hasSettingsChanged) { m.remove(); return; } const applyChangesAndClose = async () => { gmSet('pk_blur_scope', newBlurScope); gmSet('pk_blur_thumb', newBlurScope !== 'off'); gmSet('pk_hide_button_text', newHideButtonText); if (el) { el.classList.toggle('pk-hide-btn-text', newHideButtonText); if (typeof refreshQuotaText === 'function') refreshQuotaText(); } gmSet('pk_keep_pos', newKeepPos); gmSet('pk_comic_mode', newComicMode); gmSet('pk_clipboard_magnet_focus', newClipboardMagnetFocus); gmSet('pk_skip_bl_on_del', newSkipBl); const wasIndep = gmGet('pk_sort_independent', false); const isIndep = (sortPref === 'indep'); gmSet('pk_sort_independent', isIndep); if (wasIndep !== isIndep) { S._sortContextSig = null; S._sortManualSig = null; if (typeof applyResolvedSortState === 'function') applyResolvedSortState(); syncFolderFirstFromGlobal(); } const isViewIndep = (viewPref === 'indep'); gmSet('pk_view_independent', isViewIndep); persistViewPreference(); await gmSetAsync('pk_lang', selectedLang); gmSet('pk_search_engine', selectedEngine); gmSet('pk_default_open_player', normalizeDefaultOpenPlayer(selectedDefaultOpenPlayer)); gmSet('pk_default_video_quality', normalizeDefaultVideoQuality(selectedDefaultVideoQuality)); gmSet('pk_video_load_progress_cache', !!m.querySelector('#set_video_load_progress_cache').checked); gmSet('pk_turbo_mode', newTurbo); gmSet('pk_dl_filter_ext', m.querySelector('#set_dl_filter_ext').value.trim()); gmSet('pk_dl_filter_size_min', m.querySelector('#set_dl_filter_size_min').value.trim()); gmSet('pk_dl_filter_size_max', m.querySelector('#set_dl_filter_size_max').value.trim()); gmSet('pk_dl_filter_size_unit', m.querySelector('#cs_set_dl_size_unit .pk-select-item.act') ? m.querySelector('#cs_set_dl_size_unit .pk-select-item.act').dataset.val : 'MB'); gmSet('pk_dl_filter_name', m.querySelector('#set_dl_filter_name').value.trim()); gmSet('pk_download_accel_enable', newDownloadAccelEnable); gmSet('pk_download_accel_domain', newDownloadAccelDomain); gmSet('pk_download_accel_mode', newDownloadAccelMode); gmSet('pk_download_accel_query_param', newDownloadAccelQueryParam); gmSet('pk_aria2_url', newUrl); gmSet('pk_aria2_token', newToken); gmSet('pk_aria2_keep_structure', newAriaKeepStructure); if (curLang !== selectedLang) { await ensureI18nReady(true, selectedLang); } m.remove(); const savedPack = (pkRemoteI18n && pkRemoteI18n.lang === selectedLang) ? pkRemoteI18n.data : T_LOCAL.zh; const savedMsgKey = reloadSettingsChanged ? 'msg_settings_saved' : 'msg_settings_saved_plain'; showToast((savedPack && savedPack[savedMsgKey]) || T_LOCAL.zh[savedMsgKey] || T_LOCAL.zh.msg_settings_saved); if (curLang !== selectedLang) { setTimeout(() => location.reload(), 500); return; } if (newTurbo !== oldTurbo) { setTimeout(() => location.reload(), 300); return; } if (curLang !== selectedLang) { let safePath = [...S.path]; if (safePath.some(n => n.id === 'virtual_search_root' || n.id === 'analyze_root')) { safePath = S.preSearchPath || [{ id: '', name: (savedPack && savedPack.btn_nav_home) || L.btn_nav_home }]; } globalSavedState = { path: safePath, trashMode: S.trashMode, shareMode: S.shareMode, shareParseMode: S.shareParseMode, starredMode: S.starredMode, recentMode: S.recentMode, historyMode: S.historyMode, offlineMode: S.offlineMode, uploadMode: S.uploadMode, isMaximized: UI.win.classList.contains('pk-maximized'), scrollTop: UI.vp ? UI.vp.scrollTop : 0, uploadTasks: S.uploadTasks }; document.removeEventListener('keydown', keyHandler); document.removeEventListener('mouseup', mouseHandler); if (typeof destroyTooltip === 'function') destroyTooltip(); if (visibilityListener && visibilityListener.abort) visibilityListener.abort(); el.remove(); await ensureI18nReadyBeforeOpen(selectedLang); await openManager(S.cache, S.preLoadPromise); } else { renderVisible(); } }; if (saveBtn.dataset.pkSaving === '1') return; saveBtn.dataset.pkSaving = '1'; saveBtn.disabled = true; saveBtn.textContent = L.str_saving; if (!ariaChanged || (!newUrl && !newToken)) { await applyChangesAndClose(); return; } try { const fetchUrl = normalizeAriaRpcUrl(newUrl || 'http://localhost:6800/jsonrpc'); const payload = { jsonrpc: '2.0', method: 'aria2.getVersion', id: 'pk_test', params: buildAriaRpcParams(newToken) }; await aria2RpcRequest(fetchUrl, payload, 5000); await applyChangesAndClose(); } catch (e) { if (await showConfirm(L.msg_aria2_test_fail, L.title_aria2_fail)) { await applyChangesAndClose(); } else { delete saveBtn.dataset.pkSaving; saveBtn.disabled = false; saveBtn.textContent = L.btn_save; } } }; }; UI.btnSettings.onclick = (e) => { if (e) e.stopPropagation(); const existing = document.getElementById('pk-settings-pop'); if (existing) { existing.remove(); return; } const isMax = UI.win.classList.contains('pk-maximized'); const pop = document.createElement('div'); pop.id = 'pk-settings-pop'; if (isMax) pop.className = 'pk-pop-max'; pop.style.cssText = ` position: absolute; background: var(--pk-bg); border: 1px solid var(--pk-bd); border-radius: 8px; padding: 4px 0; box-shadow: 0 4px 15px rgba(0,0,0,0.15); z-index: 2147483647; min-width: 140px; display: flex; flex-direction: column; `; if (document.querySelector('.pk-ov')?.classList.contains('pk-dark')) pop.classList.add('pk-dark'); pop.innerHTML = `
${CONF.icons.settings} ${L.btn_settings}
${CONF.icons.logout} ${L.btn_logout}
`; document.body.appendChild(pop); const updatePosition = () => { if (!pop.isConnected) return; const rect = getLogicalRect(UI.btnSettings); const winEl = document.querySelector('.pk-win'); const isMax = winEl && winEl.classList.contains('pk-maximized'); let popLeft, popBottom; if (isMax) { popLeft = rect.left; popBottom = window.innerHeight - rect.top + 8; } else { popLeft = rect.right + 10; popBottom = window.innerHeight - rect.bottom; } pop.style.bottom = popBottom + 'px'; pop.style.left = popLeft + 'px'; }; updatePosition(); window.addEventListener('resize', updatePosition); const cleanup = () => { window.removeEventListener('resize', updatePosition); document.removeEventListener('mousedown', closer); pop.remove(); }; const closer = (ev) => { if (!pop.contains(ev.target) && !UI.btnSettings.contains(ev.target)) cleanup(); }; setTimeout(() => document.addEventListener('mousedown', closer), 10); pop.querySelector('#pk-set-menu-settings').onclick = (ev) => { ev.stopPropagation(); cleanup(); openSettingsModal(); }; pop.querySelector('#pk-set-menu-logout').onclick = async (ev) => { ev.stopPropagation(); cleanup(); if (await showConfirm(L.msg_logout_confirm)) { const keysToRemove = []; for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); if (k && (k.startsWith('credentials') || k.startsWith('captcha') || k === 'pk_captured_captcha')) { keysToRemove.push(k); } } keysToRemove.forEach(k => localStorage.removeItem(k)); if (typeof purgeAllCachesOnLogout === 'function') purgeAllCachesOnLogout(); if (!location.href.includes('/login')) window.location.href = 'https://mypikpak.com/drive/login'; } }; }; const ctx = el.querySelector('#pk-ctx'); if (!window.pkPropCache) window.pkPropCache = new Map(); ctx.querySelector('#ctx-property').onclick = async () => { ctx.style.display = 'none'; const id = S.getSelectedIds()[0]; if (!id) return; let item = S.itemMap.get(id); if (!item) return; setLoad(true); updateLoadTxt(L.loading_detail); const isFolder = item.kind === 'drive#folder'; try { const freshData = await apiGet(id); item = { ...item, ...freshData }; } catch(e) {} if (isFolder) { const localFMod = gmGet('pk_fmod_' + item.id); if (localFMod) item.modified_time = localFMod; } setLoad(false); let cachedProp = window.pkPropCache.get(item.id); if (!cachedProp) { cachedProp = { size: BigInt(0), fileCount: 0, folderCount: 0, isDone: false, scanned: new Set(), isRunning: false }; window.pkPropCache.set(item.id, cachedProp); } let realSize = isFolder ? cachedProp.size : BigInt(item.size || 0); let fileCount = isFolder ? cachedProp.fileCount : 0; let folderCount = isFolder ? cachedProp.folderCount : 0; if (isFolder && item.usage && item.usage.size && item.usage.size !== "0") { realSize = BigInt(item.usage.size); fileCount = item.usage.file_count; folderCount = item.usage.folder_count; cachedProp.size = realSize; cachedProp.fileCount = fileCount; cachedProp.folderCount = folderCount; cachedProp.isDone = true; } const countStr = isFolder ? L.fmt_prop_count.replace('{f}', fileCount).replace('{d}', folderCount) : "-"; const formatTime = (iso) => fmtDate ? fmtDate(iso) : iso; let sourceStr = L.str_prop_unknown; let magnetLink = item.params?.url || item.audit?.source_url || ""; if (magnetLink) { sourceStr = L.str_prop_cloud; } else { sourceStr = L.str_prop_user; } const btnStyle = "margin-left:10px; padding:2px 8px; font-size:12px; background:var(--pk-pri); color:#fff; border:none; border-radius:4px; cursor:pointer; height:24px; white-space:nowrap;"; const rowStyle = "display:flex; align-items:center; margin-bottom:12px; font-size:13px; line-height:1.5;"; const labelStyle = "width:80px; color:#888; flex-shrink:0;"; const valStyle = "color:var(--pk-fg); flex:1; word-break:break-all;"; const mkRow = (lbl, val, copyVal = null, isHtml = false, valId = null) => { if (!val && val !== 0 && val !== "-") return ""; let btnHtml = copyVal ? `` : ""; return `
${lbl}:
${isHtml ? val : esc(val)}
${btnHtml}
`; }; let pathRowHtml = ""; const isSpecialView = S.shareMode || S.offlineMode || S.recentMode || S.historyMode || S.trashMode || S.starredMode; const curPathNode = S.path[S.path.length - 1]; const shouldHidePath = isSpecialView || (S.analyzeMode && curPathNode && curPathNode.id !== 'analyze_root'); if (!shouldHidePath) { const homeText = L.btn_nav_home; let parts = (item._lineage && Array.isArray(item._lineage)) ? item._lineage.filter(p => p.id !== item.id).map(p => p.name) : S.path.map(p => p.name); parts = parts.filter(n => n && n !== 'Root' && n !== L.str_root_dir_cn); if (parts[0] !== homeText) parts.unshift(homeText); const pathStr = parts.join('/'); let pathDisplayHtml = esc(pathStr); if (pathStr.startsWith(homeText)) { const homeSvg = CONF.icons.home.replace('width="24"','width="15"').replace('height="24"','height="15"').replace('viewBox="0 0 24 24"','viewBox="0 0 24 24" style="margin-right:4px;flex-shrink:0;vertical-align:-2.5px;"'); const restPath = pathStr.substring(homeText.length); const homeGroup = `${homeSvg}${esc(homeText)}`; pathDisplayHtml = `
${homeGroup}${esc(restPath)}
`; } pathRowHtml = mkRow(L.lbl_prop_path, pathDisplayHtml, pathStr, true); } const html = `
${mkRow(L.lbl_prop_name, item.name)} ${mkRow(L.lbl_prop_size, fmtSize(realSize.toString()) || "0 B", null, false, 'pk_prop_size_val')} ${isFolder ? mkRow(L.lbl_prop_count, countStr, null, false, 'pk_prop_count_val') : ''} ${mkRow(L.lbl_prop_ctime, formatTime(item.created_time))} ${mkRow(L.lbl_prop_mtime, formatTime(item.modified_time))} ${mkRow(L.lbl_prop_source, sourceStr)} ${magnetLink ? mkRow(L.lbl_prop_link, magnetLink, magnetLink) : ''} ${pathRowHtml}
`; const m = showModal(`

${L.title_property}

${html} `); m.querySelectorAll('.pk-btn-copy-prop').forEach(btn => { btn.onclick = (e) => { const txt = e.target.getAttribute('data-val'); GM_setClipboard(txt); const oldTxt = e.target.textContent; e.target.textContent = "OK"; e.target.style.background = "#4CAF50"; setTimeout(() => { e.target.textContent = oldTxt; e.target.style.background = "var(--pk-pri)"; }, 1500); }; }); if (isFolder && !cachedProp.isDone) { const sizeEl = m.querySelector('#pk_prop_size_val'); const countEl = m.querySelector('#pk_prop_count_val'); let lastUiUpdateTime = performance.now(); const updateUI = () => { const now = performance.now(); if (now - lastUiUpdateTime > 80) { if (sizeEl && sizeEl.isConnected) sizeEl.textContent = fmtSize(cachedProp.size.toString()) || "0 B"; if (countEl && countEl.isConnected) countEl.textContent = L.fmt_prop_count.replace('{f}', cachedProp.fileCount).replace('{d}', cachedProp.folderCount); lastUiUpdateTime = now; } }; if (!cachedProp.isRunning) { cachedProp.isRunning = true; const localAbortCtrl = new AbortController(); const rootNodes =[{ id: item.id, name: item.name, lineage: [], retryCount: 0 }]; coreRecursiveEngine(rootNodes, { signal: localAbortCtrl.signal, onFolder: (f) => { if (f.id !== item.id && !cachedProp.scanned.has(f.id)) { cachedProp.folderCount++; cachedProp.scanned.add(f.id); } updateUI(); }, onFile: (f) => { if (!cachedProp.scanned.has(f.id)) { cachedProp.fileCount++; cachedProp.size += BigInt(f.size || 0); cachedProp.scanned.add(f.id); } updateUI(); }, onProgress: () => {} }).then(() => { cachedProp.isDone = true; cachedProp.isRunning = false; if (sizeEl && sizeEl.isConnected) sizeEl.textContent = fmtSize(cachedProp.size.toString()) || "0 B"; if (countEl && countEl.isConnected) countEl.textContent = L.fmt_prop_count.replace('{f}', cachedProp.fileCount).replace('{d}', cachedProp.folderCount); }).catch(e => { cachedProp.isRunning = false; }); } else { const timer = setInterval(() => { if (!document.contains(m)) { clearInterval(timer); return; } if (sizeEl) sizeEl.textContent = fmtSize(cachedProp.size.toString()) || "0 B"; if (countEl) countEl.textContent = L.fmt_prop_count.replace('{f}', cachedProp.fileCount).replace('{d}', cachedProp.folderCount); if (cachedProp.isDone) clearInterval(timer); }, 200); } } }; const btnLocate = ctx.querySelector('#ctx-locate'); if (btnLocate) { btnLocate.onclick = async () => { ctx.style.display = 'none'; const id = S.getSelectedIds()[0]; if (!id) return; let item = S.itemMap.get(id); if (!item) return; setLoad(true); updateLoadTxt(L.str_loc_tracing); try { if (item.kind === 'drive#task' || S.recentMode || S.uploadMode) { const lookupId = (item.kind === 'drive#task' || S.uploadMode) ? item.file_id : item.id; if (!lookupId) { showToast(L.err_folder_not_ready, 'error'); setLoad(false); return; } try { item = await apiGet(lookupId); } catch (e) { const errText = e.message || ""; if (errText.includes('404') || errText.includes('400')) { showToast(L.err_item_deleted, 'error'); } else { showToast(`${L.str_error}: ${e.message}`, 'error'); } setLoad(false); return; } } let pathChain = []; if (item._lineage && Array.isArray(item._lineage)) { pathChain = item._lineage.filter(p => p.id !== item.id && p.id !== 'virtual_search_root' && p.id !== 'analyze_root' && p.id !== 'recent_root' ); } else if (item.parent_id && typeof globalLineageMap !== 'undefined' && globalLineageMap.has(item.parent_id)) { pathChain = [...globalLineageMap.get(item.parent_id)]; } else { let currParentId = item.parent_id; for (let i = 0; i < 15; i++) { if (!currParentId || currParentId === 'root' || currParentId === '') break; if (typeof globalLineageMap !== 'undefined' && globalLineageMap.has(currParentId)) { const cachedLineage = globalLineageMap.get(currParentId); pathChain.unshift(...cachedLineage); break; } try { const res = await apiGet(currParentId); pathChain.unshift({ id: res.id, name: res.name }); currParentId = res.parent_id; } catch (e) { break; } } } if (pathChain.length > 0 && (pathChain[0].id === '' || pathChain[0].id === 'root')) { pathChain[0].id = ''; pathChain[0].name = L.btn_nav_home; } else { pathChain.unshift({ id: '', name: L.btn_nav_home }); } const targetContextId = (item.parent_id === 'root' || !item.parent_id) ? '' : item.parent_id; const needsRestoreGlobalCheck = S.starredMode || S.recentMode || S.historyMode || S.offlineMode || S.uploadMode || S.shareMode || S.shareParseMode || S.isFlattened || S.dupMode || S.analyzeMode; if (S.shareParseMode) resetShareParseListState(false); S.starredMode = false; S.trashMode = false; S.shareMode = false; S.shareParseMode = false; S.offlineMode = false; S.recentMode = false; S.historyMode = false; S.uploadMode = false; S.dupMode = false; S.isFlattened = false; S.analyzeMode = false; if (UI.chkSearchPath) UI.chkSearchPath.checked = false; if (UI.btnNavStarred) UI.btnNavStarred.classList.remove('act'); if (UI.btnNavRecent) UI.btnNavRecent.classList.remove('act'); if (UI.btnNavHistory) UI.btnNavHistory.classList.remove('act'); if (UI.btnNavShare) UI.btnNavShare.classList.remove('act'); if (UI.btnNavShareParse) UI.btnNavShareParse.classList.remove('act'); if (UI.btnNavOffline) UI.btnNavOffline.classList.remove('act'); if (UI.btnNavUpload) UI.btnNavUpload.classList.remove('act'); if (UI.btnNavHome) UI.btnNavHome.classList.add('act'); [UI.btnAria2, UI.btnDown, UI.btnExt, UI.btnExportM3U, UI.btnImgSearch].forEach(b => { if(b) b.style.display = 'inline-flex'; }); [UI.btnUpPause, UI.btnUpStart, UI.btnUpDel, UI.btnUpClearAll].forEach(b => { if(b) b.style.display = 'none'; }); const upSep = document.getElementById('pk-up-sep'); if(upSep) upSep.style.display = 'none'; [UI.btnNewFolder, UI.btnDel, UI.btnCopy, UI.btnCut, UI.btnPaste, UI.btnRename, UI.btnBulkRename, UI.btnPrune, UI.btnUnzip, UI.btnMigrate, UI.btnRefresh, UI.btnBlacklistManager].forEach(b => { if(b) b.style.display = 'inline-flex'; }); if(UI.uploadWrap) UI.uploadWrap.style.display = 'inline-flex'; if(UI.actionBar) UI.actionBar.style.display = 'flex'; const mainHeader = UI.win ? UI.win.querySelector('.pk-grid-hd') : null; if (mainHeader) mainHeader.style.display = ''; if(UI.searchInput && UI.searchInput.parentNode) UI.searchInput.parentNode.style.display = 'flex'; S.path = pathChain; syncLocalUploadVisibility(); if (UI.lblGlobal) UI.lblGlobal.style.display = 'flex'; if (UI.chkGlobal && needsRestoreGlobalCheck && typeof S.wasGlobalChecked !== 'undefined') { UI.chkGlobal.checked = S.wasGlobalChecked; } if (UI.scan) UI.scan.style.display = 'flex'; if (UI.btnAnalyze) UI.btnAnalyze.style.display = 'flex'; if (UI.bottomGrp) UI.bottomGrp.style.display = 'flex'; if (UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'flex'; if (UI.btnNewFolder) UI.btnNewFolder.style.display = 'inline-flex'; if (UI.btnPaste) UI.btnPaste.style.display = S.clipItems && S.clipItems.length > 0 ? 'inline-flex' : 'inline-flex'; if (typeof applyResolvedSortState === 'function') applyResolvedSortState(targetContextId || null); const locateTargetViewMode = (typeof resolvePreferredViewMode === 'function') ? resolvePreferredViewMode(targetContextId || 'root') : (gmGet('pk_file_view_mode', 'grid') === 'list' ? 'list' : 'grid'); const shouldHoldLocateGridSync = needsRestoreGlobalCheck && locateTargetViewMode === 'grid'; if (shouldHoldLocateGridSync) { S._folderViewSyncHold = true; S._locateGridSyncPending = true; beginFolderViewSync(); if (UI.vp) { UI.vp.style.visibility = 'hidden'; UI.vp.style.opacity = '0'; UI.vp.style.pointerEvents = 'none'; } const hd = UI.win && UI.win.querySelector('.pk-grid-hd'); if (hd) hd.style.visibility = 'hidden'; } await load(); let trackCount = 0; let trackerInterval = null; const releaseLocateViewSync = () => { if (!S._folderViewSyncHold && !S._locateGridSyncPending) return; const doRelease = () => { if (typeof renderVisible === 'function') renderVisible(); if (UI.vp) { void UI.vp.offsetHeight; } requestAnimationFrame(() => { requestAnimationFrame(() => { if (typeof renderVisible === 'function') renderVisible(); S._folderViewSyncHold = false; S._locateGridSyncPending = false; if (UI.vp) { UI.vp.style.visibility = ''; UI.vp.style.opacity = ''; UI.vp.style.pointerEvents = ''; } const hd = UI.win && UI.win.querySelector('.pk-grid-hd'); if (hd) hd.style.visibility = ''; S._folderViewSyncing = false; }); }); }; setTimeout(() => { requestAnimationFrame(() => { requestAnimationFrame(() => { requestAnimationFrame(doRelease); }); }); }, 120); }; const stopTracking = (releaseViewSync = true) => { if (trackerInterval) { clearInterval(trackerInterval); trackerInterval = null; } if (releaseViewSync) releaseLocateViewSync(); }; let hasTriedRecovery = false; const performLocate = () => { const currentPathNode = S.path[S.path.length - 1]; const currentContextId = currentPathNode ? (currentPathNode.id || '') : ''; if (currentContextId !== targetContextId) { stopTracking(); return; } if (!S.loading && !S.itemMap.has(item.id) && !hasTriedRecovery) { hasTriedRecovery = true; const cacheKey = S.getRealCacheKey(currentContextId); S.cache.delete(cacheKey); if (typeof globalCache !== 'undefined') globalCache.delete(cacheKey); globalDirtyFolders.add(currentContextId || 'root'); updateLoadTxt(L.str_loc_stale); load(false, true); return; } if (S.loading || !S.itemMap.has(item.id)) return; S.sel.clear(); S.sel.add(item.id); S.activeId = item.id; const targetIdx = S.display.findIndex(x => x.id === item.id); if (targetIdx !== -1) { const vpHeight = UI.vp.clientHeight; let rowTop = targetIdx * CONF.rowHeight; if (isGridView()) { syncLayoutMetrics(); const gridLayout = getGridLayout(); const cols = Math.max(1, gridLayout.cols || 1); rowTop = Math.floor(targetIdx / cols) * CONF.rowHeight; } const centerScroll = Math.max(0, rowTop - (vpHeight / 2) + (CONF.rowHeight / 2)); if (Math.abs(UI.vp.scrollTop - centerScroll) > (CONF.rowHeight / 2)) { UI.vp.scrollTop = centerScroll; } renderVisible(); const row = UI.in.querySelector(`.pk-row[data-id="${item.id}"]`); if (row) { delete row.dataset.flashing; row.style.transition = ''; row.style.backgroundColor = ''; row.style.border = ''; row.style.borderRadius = ''; row.style.boxShadow = ''; row.style.outline = ''; row.className = getRowClassName(true, true, S.movingIds.has(item.id)); const chk = row.querySelector('input[type="checkbox"]'); if (chk && !chk.checked) chk.checked = true; } } requestAnimationFrame(() => { if (typeof renderVisible === 'function') renderVisible(); releaseLocateViewSync(); }); }; performLocate(); trackerInterval = setInterval(() => { trackCount++; const limit = hasTriedRecovery ? 25 : 10; if (S.loading || trackCount <= limit) performLocate(); else stopTracking(); }, 200); } catch (e) { console.error(e); showAlert(`${L.str_error}: ${e.message}`); if (UI.vp) UI.vp.style.opacity = '1'; } finally { setLoad(false); setTimeout(() => { if (UI.vp && !S._folderViewSyncHold && !S._locateGridSyncPending) { UI.vp.style.opacity = '1'; } }, 100); } }; } ctx.querySelector('#ctx-ext-play').onclick = () => { ctx.style.display = 'none'; UI.btnExt.click(); }; ctx.querySelector('#ctx-open').onclick = () => { ctx.style.display = 'none'; const id = S.getSelectedIds()[0]; if (!id) return; const item = S.items.find(x => x.id === id); if (!item) return; if (S.offlineMode && item.phase !== 'PHASE_TYPE_COMPLETE') { const locateBtn = document.getElementById('ctx-locate'); if (locateBtn) locateBtn.click(); return; } if (S.uploadMode && item.status !== 'DONE') { if (item.file_id) { const locateBtn = document.getElementById('ctx-locate'); if (locateBtn) locateBtn.click(); } return; } if (item._isShareItem) { if (item.kind === 'drive#folder') handleShareParseFolderOpen(item); else if (isArchiveLike(item)) handleOpenShareArchive(item); else if (isAudioLikeItem(item) || isVideoLikeItem(item) || isImageLikeItem(item)) handleOpenShareFile(item); else if (isTxtFileLike(item)) handleOpenShareTextFile(item); else showUnsupportedOpenToast(); return; } if (item.kind === 'drive#folder') { if (S.loading) return; S.path.push({ id: item.id, name: item.name }); load(); } else { const mime = (item.mime_type || "").toLowerCase(); const name = (item.name || "").toLowerCase(); if (name.endsWith('.torrent')) { handleTorrentFile(item); } else if (mime.includes('zip') || mime.includes('rar') || mime.includes('7z') || mime.includes('compressed') || mime.includes('archive') || name.endsWith('.zip') || name.endsWith('.rar') || name.endsWith('.7z') || name.endsWith('.tar') || name.endsWith('.gz')) { handleOpenArchive(item); } else if (isAudioLikeItem(item)) { playAudio(item); } else if (mime.startsWith('video')) { playVideo(item); } else if (mime.startsWith('image')) { showImage(item); } else if (!S.offlineMode && !S.uploadMode && isTxtFileLike(item)) { handleOpenTextFile(item); } else { showUnsupportedOpenToast(); } } }; const starBtnCtx = ctx.querySelector('#ctx-star'); if (starBtnCtx) { starBtnCtx.onclick = async (e) => { ctx.style.display = 'none'; const action = e.target.getAttribute('data-action'); const isStar = (action === 'star'); const rawIds = S.getSelectedIds(); const ids = rawIds.filter(id => { const it = S.itemMap.get(id); if (!it) return false; if (!S.trashMode && isSystemItem(it)) return false; const isCurrentlyStarred = !!(it.starred || (it.tags && it.tags.some(t => t.name === 'STAR'))); if (isStar && isCurrentlyStarred) return false; if (!isStar && !isCurrentlyStarred) return false; return true; }); if (ids.length === 0) { showToast(isStar ? L.msg_star_added : L.msg_unstar_done); return; } const starTask = FloatBarManager.create(isStar ? L.msg_starring : L.msg_unstarring); const total = ids.length; let successCount = 0; try { const url = `https://api-drive.mypikpak.com/drive/v1/files:${action}`; const headers = getHeaders(); const BATCH_SIZE = 100; for (let i = 0; i < total; i += BATCH_SIZE) { const chunk = ids.slice(i, i + BATCH_SIZE); const chunkSet = new Set(chunk); starTask.update(`${isStar ? L.msg_starring : L.msg_unstarring} ${Math.min(i + BATCH_SIZE, total)} / ${total}`); const res = await fetch(url, { method: 'POST', headers: headers, body: JSON.stringify({ "ids": chunk }) }); if (!res.ok) { const errText = await res.text(); if (res.status === 400 && errText.includes('captcha')) throw new Error(L.err_captcha_simple); throw new Error(`API ${res.status}`); } chunk.forEach(id => { if (isStar) S.starredSet.add(id); else S.starredSet.delete(id); const syncObject = (o) => { if (!o) return; o.starred = isStar; if (!o.tags) o.tags = []; if (isStar) { if (!o.tags.some(t => t.name === 'STAR')) o.tags.push({name: 'STAR', type: 0}); } else { o.tags = o.tags.filter(t => t.name !== 'STAR'); } }; syncObject(S.itemMap.get(id)); const deepSync = (cacheMap) => { if (!cacheMap) return; cacheMap.forEach((data) => { const list = Array.isArray(data) ? data : (data?.items || []); const target = list.find(f => f.id === id); if (target) syncObject(target); }); }; deepSync(globalCache); deepSync(S.cache); }); if (!isStar && S.starredMode && S.path.length === 1) { S.items = S.items.filter(it => !chunkSet.has(it.id)); S.display = S.display.filter(d => !d.isHeader && !chunkSet.has(d.id)); renderVisible(); updateStat(); } else { renderVisible(); } successCount += chunk.length; if (total > BATCH_SIZE) await sleep(50); } starTask.destroy(); showToast(isStar ? L.msg_star_added : L.msg_unstar_done); } catch (err) { console.error(err); if (starTask) starTask.destroy(); ids.forEach(id => { const revertStatus = !isStar; if (revertStatus) S.starredSet.add(id); else S.starredSet.delete(id); const item = S.itemMap.get(id); if (item) { item.starred = revertStatus; if (!item.tags) item.tags = []; if (revertStatus) { if (!item.tags.some(t => t.name === 'STAR')) item.tags.push({name: 'STAR', type: 0}); } else { item.tags = item.tags.filter(t => t.name !== 'STAR'); } } }); renderVisible(); showAlert(err.message); } }; } const observeUnzipTask = (taskId, folderId, fileId, skipUiRefresh = false) => { const checkStatus = async () => { try { const res = await fetch(`https://api-drive.mypikpak.com/decompress/v1/progress?task_id=${taskId}`, { headers: getHeaders() }); if (!res.ok) return; const data = await res.json(); if (data.phase === 'PHASE_TYPE_COMPLETE') { if (S) S.clearSelection(); let physicalParentId = folderId || 'root'; if (fileId && S.itemMap.has(fileId)) { const it = S.itemMap.get(fileId); if (!it.params) it.params = {}; it.params.global_file_kind = '1'; if (it.parent_id) physicalParentId = it.parent_id; else if (it.parent_id === '') physicalParentId = 'root'; } S.items.forEach(it => { if ((it.kind === 'drive#task' || S.offlineMode || S.uploadMode) && it.file_id === fileId) { if (!it.params) it.params = {}; it.params.global_file_kind = '1'; } }); if (skipUiRefresh) return; if (S && S.getSelectedCount() > 0) S.clearSelection(); refresh(); updateStat(); const dirtyTargets = new Set(); if (folderId) dirtyTargets.add(folderId); else dirtyTargets.add('root'); dirtyTargets.add(physicalParentId); dirtyTargets.forEach(target => { globalDirtyFolders.add(target); if (target === 'root') globalDirtyFolders.add(''); if (typeof globalCache !== 'undefined') globalCache.delete(target); if (typeof pkState !== 'undefined' && pkState && pkState.cache) pkState.cache.delete(target); if (typeof scannedFolderIds !== 'undefined') scannedFolderIds.delete(target === 'root' ? '' : target); backgroundQueue.unshift({ id: target === 'root' ? '' : target, name: 'Unzipped_Update', retryCount: 0 }); }); runBackgroundCrawler(); const curPathNode = S.path[S.path.length - 1]; const curId = curPathNode.id || 'root'; if (dirtyTargets.has(curId) || curId === 'virtual_search_root' || S.isFlattened || S.dupMode) { if (window.pkSmartRefreshTrigger) { setTimeout(() => window.pkSmartRefreshTrigger(true), 1200); } } else { dirtyTargets.forEach(target => { apiList(target === 'root' ? '' : target, 500, null, null, false, true).then(newFiles => { if (typeof globalCache !== 'undefined') globalCache.set(target, newFiles); }).catch(()=>{}); }); } return; } if (data.phase === 'PHASE_TYPE_RUNNING' || data.phase === 'PHASE_TYPE_PENDING') { setTimeout(checkStatus, 4000); } } catch (e) { setTimeout(checkStatus, 8000); } }; checkStatus(); }; const askForPassword = (fileName, errorMsg, isBatch = false) => { return new Promise((resolve) => { const txtCancel = isBatch ? L.btn_skip : L.btn_cancel; const txtConfirm = isBatch ? L.btn_ok : L.btn_view_file; const m = showModal(`
${esc(fileName)}
${CONF.typeIcons.archive.replace(/width="\d+"/, 'width="64"').replace(/height="\d+"/, 'height="64"')}
${L.title_input_pwd}
${esc(errorMsg) || L.err_pwd_simple}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) { modalBox.style.padding = '0'; modalBox.style.width = "420px"; modalBox.style.height = "420px"; modalBox.style.display = "flex"; modalBox.style.flexDirection = "column"; modalBox.style.overflow = "hidden"; } const closeBtn = m.querySelector('.pk-modal-close'); if(closeBtn) { closeBtn.style.top = "21px"; closeBtn.style.right = "24px"; closeBtn.style.color = "#999"; } const inp = m.querySelector('#retry_pwd'); const clearBtn = m.querySelector('#pwd_clear_btn'); setTimeout(() => inp.focus(), 50); inp.addEventListener('input', () => { clearBtn.style.display = inp.value ? 'flex' : 'none'; }); clearBtn.onclick = () => { inp.value = ''; clearBtn.style.display = 'none'; inp.focus(); }; clearBtn.onmouseover = () => clearBtn.style.color = '#666'; clearBtn.onmouseout = () => clearBtn.style.color = '#999'; const doResolve = (val) => { m.remove(); resolve(val); }; m.querySelector('#ask_skip').onclick = () => doResolve(null); m.querySelector('.pk-modal-close').onclick = () => doResolve(null); m.querySelector('#ask_retry').onclick = () => doResolve(inp.value); inp.onkeydown = (e) => { if(e.key === 'Enter') doResolve(inp.value); }; }); }; const isArchivePasswordSignal = (data = {}, msg = "") => { const code = Number(data?.error_code ?? data?.code ?? 0); const status = String(data?.status || data?.status_code || data?.error || "").toUpperCase(); const text = String(msg || data?.msg || data?.error_description || data?.status_text || data?.message || "").toLowerCase(); return code === 10023 || status.includes('PASS_WORD') || status.includes('PASSWORD') || /pass[_-]?word/.test(text) || /密码|密碼|パスワード|비밀번호|암호|kata\s*sandi|kata\s*laluan|sandi/.test(text); }; const isArchiveAlreadyRunningSignal = (data = {}, msg = "") => { const status = String(data?.status || data?.status_code || data?.phase || data?.error || "").toUpperCase(); const text = String(msg || data?.msg || data?.error_description || data?.status_text || data?.message || "").toLowerCase(); return ((status.includes('DECOMPRESS') || status.includes('UNZIP')) && (status.includes('RUN') || status.includes('PROCESS') || status.includes('PENDING'))) || /decompressing|unzipping|already.*(running|exists)|task.*(running|exists)|正在解压|正在解壓|解压中|解壓中|展開中|解凍中|압축.*해제|sedang.*ekstrak|sedang.*menyahmampat/i.test(text); }; const handleOpenArchive = async (file) => { if (S.trashMode) return; setLoad(true); updateLoadTxt(L.loading); const Vault = { get: () => { try { const raw = JSON.parse(gmGet('pk_pwd_vault', '[]')); return raw.map(x => typeof x === 'object' ? x.p : x); } catch { return []; } }, save: (p) => { if (!p) return; try { let list = JSON.parse(gmGet('pk_pwd_vault', '[]')).map(x => typeof x === 'object' ? x : {p: x, h: 0}); let item = list.find(x => x.p === p); if (item) item.h = (item.h || 0) + 1; else list.push({p: p, h: 1}); list.sort((a, b) => b.h - a.h); if (list.length > 50) list = list.slice(0, 50); gmSet('pk_pwd_vault', JSON.stringify(list)); } catch(e) {} } }; try { let detail = file; if (!detail.gcid && !detail.hash) detail = await apiGet(file.id); const gcid = detail.gcid || detail.hash || detail.md5_checksum || ""; let currentPwd = ""; let isVerified = false; let hasTriedAuto = false; let serverBusyRetry = 0; while (!isVerified) { const payload = { gcid, file_id: file.id, password: currentPwd, path: "" }; const res = await fetch(`https://api-drive.mypikpak.com/decompress/v1/list`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(payload) }); const data = await res.json().catch(() => ({})); const errStr = (data.status_text || data.error_description || data.msg || data.error || "").toLowerCase(); const isPwdRequired = isArchivePasswordSignal(data, errStr); if (data.status === 'OK' && res.ok) { isVerified = true; if (currentPwd) Vault.save(currentPwd); } else if (isPwdRequired) { if (!currentPwd && !hasTriedAuto) { hasTriedAuto = true; const tryLimit = gmGet('pk_pwd_try_count', 10); const candidates = Vault.get().slice(0, tryLimit); if (candidates.length > 0) { setLoad(false); const matchingTask = FloatBarManager.create(L.msg_smart_matching_n.replace('{n}', candidates.length)); const checkTask = async (pwd, idx) => { const tieredDelay = Math.floor(idx / 5) * 1200; await sleep((idx * 150) + tieredDelay); if (isVerified) return Promise.reject("Aborted"); try { const autoRes = await fetch(`https://api-drive.mypikpak.com/decompress/v1/list`, { method: 'POST', headers: getHeaders(), body: JSON.stringify({ gcid, file_id: file.id, password: pwd, path: "" }) }); const autoData = await autoRes.json(); if (autoData.status === 'OK') return pwd; } catch(e) {} throw new Error("Wrong Pwd"); }; try { const correctPwd = await Promise.any(candidates.map((p, i) => checkTask(p, i))); if (correctPwd) { currentPwd = correctPwd; isVerified = true; Vault.save(correctPwd); } } catch (e) { } finally { matchingTask.destroy(); if (!isVerified) setLoad(true); } if (isVerified) break; } } setLoad(false); const promptMsg = currentPwd ? L.err_pwd_simple : ""; const userPwd = await askForPassword(file.name, promptMsg); if (userPwd === null) { setLoad(false); return; } currentPwd = userPwd; setLoad(true); updateLoadTxt(L.str_verifying); } else { if (res.status === 500 || res.status === 502) { if (serverBusyRetry < 5) { serverBusyRetry++; console.warn(`[Archive] Server 500 Error. Retry ${serverBusyRetry}/5...`); updateLoadTxt(L.str_server_indexing.replace('{n}', serverBusyRetry)); await sleep(1500); continue; } } throw new Error(errStr || `API Error ${res.status}`); } } setLoad(false); const result = await showArchivePreview(file, currentPwd); if (result && result.confirm) { if (result.password) Vault.save(result.password); handleUnzip([file], result.password, true, result.taskId); } } catch (e) { setLoad(false); showAlert(`${L.str_error}: ${e.message}`); } }; const handleTorrentFile = async (file) => { if (parseInt(file.size) > 10 * 1024 * 1024) { showAlert(`${L.str_error_crit}: ${L.err_invalid_links}`); return; } const fb = FloatBarManager.create(L.str_processing); try { const physicalId = (file.file_id || (file.params && file.params.file_id)) || file.id; let detail = file; if (!detail.web_content_link) detail = await apiGet(physicalId); const res = await fetch(detail.web_content_link); const buffer = await res.arrayBuffer(); const buf = new Uint8Array(buffer); if (buf[0] !== 100) throw new Error(L.err_invalid_torrent); let pos = 0; let safetyCounter = 0; const MAX_ITERATIONS = 100000; const decodeSkip = () => { if (++safetyCounter > MAX_ITERATIONS) throw new Error(L.err_torrent_complex); if (pos >= buf.length) return; const c = buf[pos]; if (c === 100 || c === 108) { pos++; while (pos < buf.length && buf[pos] !== 101) { decodeSkip(); if (pos > buf.length) break; } pos++; } else if (c === 105) { pos++; while (pos < buf.length && buf[pos] !== 101) pos++; pos++; } else if (c >= 48 && c <= 57) { let colon = pos; while (colon < buf.length && buf[colon] !== 58) colon++; if (colon >= buf.length) throw new Error(L.err_torrent_format); const len = parseInt(new TextDecoder().decode(buf.slice(pos, colon))); if (isNaN(len)) throw new Error(L.err_torrent_len); pos = colon + 1 + len; } else { throw new Error(L.err_torrent_char); } }; let infoHash = null; pos = 1; while (pos < buf.length && buf[pos] !== 101) { if (++safetyCounter > MAX_ITERATIONS) break; const keyStart = pos; decodeSkip(); let colon = keyStart; while (buf[colon] !== 58 && colon < buf.length) colon++; const kLen = parseInt(new TextDecoder().decode(buf.slice(keyStart, colon))); const keyStr = new TextDecoder().decode(buf.slice(colon + 1, colon + 1 + kLen)); if (keyStr === "info") { const infoStart = pos; decodeSkip(); const infoEnd = pos; const infoBuf = buf.slice(infoStart, infoEnd); const hashBuf = await crypto.subtle.digest("SHA-1", infoBuf); infoHash = Array.from(new Uint8Array(hashBuf)).map(b => b.toString(16).padStart(2, '0')).join(''); break; } } if (!infoHash) throw new Error("Invalid Torrent File"); const magnet = `magnet:?xt=urn:btih:${infoHash}&dn=${encodeURIComponent(file.name)}`; fb.update(L.msg_creating_cloud_task); await apiAddOfflineTask(magnet, file.parent_id || ""); showToast(L.msg_cloud_task_success.replace('{n}', 1)); if (typeof globalNeedsSync !== 'undefined') globalNeedsSync = true; if (S.offlineMode) load(false, true); } catch (e) { showAlert(`${L.str_error}: ${e.message}`); } finally { fb.destroy(); } }; const sendUnzipRequest = async (file, password, archiveFiles = []) => { let detail = file; if (!detail.gcid && !detail.hash) { try { detail = await apiGet(file.id); } catch(e) {} } const gcid = detail.gcid || detail.hash || detail.md5_checksum || ""; const pickedFiles = Array.isArray(archiveFiles) ? archiveFiles.filter(Boolean).map(f => Object.assign({}, f, { checked: true })) : []; const payload = { file_id: file.id, files: pickedFiles, password: password || "", default_parent: true, gcid: gcid }; const res = await fetch(`https://api-drive.mypikpak.com/decompress/v1/decompress`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(payload) }); const data = await res.json().catch(() => ({})); const rawMsg = data.msg || data.error || data.error_description || data.status_text || ""; const errStr = rawMsg.toLowerCase(); const isPwdError = isArchivePasswordSignal(data, rawMsg); if (isPwdError) { throw { isError: true, isPwd: true, code: 10023, msg: L.err_pwd_simple }; } if (!res.ok) { throw { isError: true, code: res.status, msg: rawMsg || `HTTP ${res.status}` }; } if (data.status && data.status !== 'OK' && data.code !== 0) { throw { isError: true, code: data.code || -1, msg: rawMsg || data.status }; } return data; }; const showArchivePreview = async (file, initialPassword = "", archiveOpts = {}) => { let currentPath = ""; let pathNodes = [{ name: L.picker_all, path: '' }]; let currentPwd = initialPassword; const archiveName = archiveOpts.title || file.name || L.str_unknown_name; const getArchiveGcid = async () => { if (archiveOpts.gcid) return archiveOpts.gcid; let detail = file; if (!detail.gcid && !detail.hash) { try { detail = await apiGet(file.id); } catch(e) {} } return detail.gcid || detail.hash || detail.md5_checksum || ""; }; const fetchArchiveList = async (pathValue, pwdValue, timeout = 0) => { const payload = { gcid: await getArchiveGcid(), file_id: archiveOpts.fileId || file.id, password: pwdValue || "", path: pathValue || "" }; const init = { method: 'POST', headers: archiveOpts.getHeaders ? archiveOpts.getHeaders() : getHeaders(), body: JSON.stringify(payload) }; if (timeout && AbortSignal && AbortSignal.timeout) init.signal = AbortSignal.timeout(timeout); const res = await fetch(archiveOpts.listUrl || `https://api-drive.mypikpak.com/decompress/v1/list`, init); const data = await res.json().catch(() => ({})); return { res, data }; }; const submitArchiveUnzip = (pwdValue, archiveFiles) => archiveOpts.submitUnzip ? archiveOpts.submitUnzip(pwdValue || "", archiveFiles || []) : sendUnzipRequest(file, pwdValue || "", archiveFiles || []); let preCheckData = null; setLoad(true); updateLoadTxt(L.loading); try { const gcid = await getArchiveGcid(); let isVerified = false; let isFirstTry = true; while(!isVerified) { const { res, data } = await fetchArchiveList("", currentPwd); const errStr = (data.status_text || data.error_description || data.msg || data.error || "").toLowerCase(); const isPwdErr = isArchivePasswordSignal(data, errStr); if (isPwdErr) { setLoad(false); const newPwd = await askForPassword(archiveName, isFirstTry ? "" : L.err_pwd_simple); if (newPwd !== null) { currentPwd = newPwd; isFirstTry = false; setLoad(true); } else { return { confirm: false }; } } else if (!res.ok || (data.status && data.status !== 'OK')) { throw new Error(data.status_text || `API Error ${res.status}`); } else { if (currentPwd) { if (gcid) gmSet('pk_archive_pwd_' + gcid, currentPwd); if (typeof updateGlobalPool === 'function') updateGlobalPool(currentPwd); } preCheckData = data; isVerified = true; } } } catch (e) { setLoad(false); showAlert(`${L.str_error}: ${e.message}`); return { confirm: false }; } finally { setLoad(false); } return new Promise(async (resolve) => { const m = showModal(`
${esc(archiveName)}
A-Z
`); const modalBox = m.querySelector('.pk-modal'); modalBox.style.padding = '0'; modalBox.style.width = "600px"; modalBox.style.height = "500px"; modalBox.style.maxHeight = "85vh"; modalBox.style.display = "flex"; modalBox.style.flexDirection = "column"; modalBox.style.overflow = "hidden"; const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) { closeBtn.style.top = "17px"; closeBtn.style.right = "20px"; closeBtn.style.color = "#999"; closeBtn.style.zIndex = "10"; } const listContainer = m.querySelector('#arc_list_container'); const crumbContainer = m.querySelector('#arc_crumb'); const loading = m.querySelector('#arc_loading'); const arcSortWrap = m.querySelector('#arc_sort_wrap'); const arcSortBtn = m.querySelector('#arc_sort_btn'); const arcSortTxt = m.querySelector('#arc_sort_txt'); const arcSortMenu = m.querySelector('#arc_sort_menu'); let arcCrumbIdx = 0; let lastArcScroll = 0; let archiveSortMode = 'az'; let currentArchiveData = preCheckData; const selectedArchiveMap = new Map(); const getArchiveKey = (f, fullPath) => `${f.index ?? ''}::${f.path || fullPath || f.filename || f.name || ''}`; const updateUnzipBtn = () => { const btn = m.querySelector('#unzip_confirm'); if (!btn || btn.disabled) return; btn.textContent = selectedArchiveMap.size ? L.btn_unzip_selected.replace('{n}', selectedArchiveMap.size) : L.btn_unzip_all; }; const getArchiveSortLabel = () => archiveSortMode === 'za' ? 'Z-A' : (archiveSortMode === 'large' ? L.picker_sort_large : (archiveSortMode === 'small' ? L.picker_sort_small : 'A-Z')); const closeArchiveSortMenu = () => { if (!arcSortMenu) return; arcSortMenu.style.display = 'none'; if (arcSortBtn) arcSortBtn.style.background = 'transparent'; document.removeEventListener('mousedown', onArchiveSortDocDown, true); }; const onArchiveSortDocDown = (ev) => { if ((arcSortMenu && arcSortMenu.contains(ev.target)) || (arcSortBtn && arcSortBtn.contains(ev.target))) return; closeArchiveSortMenu(); }; const toArchiveSize = (f) => { try { return BigInt(String(f.filesize || f.size || 0).replace(/[^0-9]/g, '') || '0'); } catch(e) { return 0n; } }; const getArchiveItemName = (f) => String(f.filename || f.file_name || f.name || ''); const sortArchiveItems = (items) => { const arr = Array.isArray(items) ? items.slice() : []; return arr.sort((a, b) => { const na = getArchiveItemName(a), nb = getArchiveItemName(b); if (archiveSortMode === 'large' || archiveSortMode === 'small') { const sa = toArchiveSize(a), sb = toArchiveSize(b); if (sa !== sb) return (sa > sb ? 1 : -1) * (archiveSortMode === 'large' ? -1 : 1); return na.localeCompare(nb, undefined, { numeric: true, sensitivity: 'base' }); } return na.localeCompare(nb, undefined, { numeric: true, sensitivity: 'base' }) * (archiveSortMode === 'za' ? -1 : 1); }); }; if (arcSortBtn && arcSortMenu) { arcSortBtn.onclick = (e) => { e.stopPropagation(); const open = arcSortMenu.style.display === 'block'; if (open) { closeArchiveSortMenu(); return; } closeArchiveCrumbDropdown(); arcSortMenu.style.display = 'block'; arcSortBtn.style.background = 'var(--pk-hl)'; document.addEventListener('mousedown', onArchiveSortDocDown, true); }; arcSortBtn.onmouseenter = () => { if (arcSortMenu.style.display !== 'block') arcSortBtn.style.background = 'var(--pk-hl)'; }; arcSortBtn.onmouseleave = () => { if (arcSortMenu.style.display !== 'block') arcSortBtn.style.background = 'transparent'; }; arcSortMenu.querySelectorAll('.pk-arc-sort-opt').forEach(opt => { opt.onmouseenter = () => opt.style.background = 'var(--pk-hl)'; opt.onmouseleave = () => opt.style.background = 'transparent'; opt.onclick = (e) => { e.stopPropagation(); archiveSortMode = opt.dataset.val || 'az'; if (arcSortTxt) arcSortTxt.textContent = getArchiveSortLabel(); closeArchiveSortMenu(); if (currentArchiveData) renderData(currentArchiveData, currentPath); }; }); } crumbContainer.onwheel = (e) => { e.preventDefault(); const now = Date.now(); if (now - lastArcScroll < 120) return; lastArcScroll = now; const nodes = [...crumbContainer.children].filter(c => c.textContent.trim() !== ""); if (!nodes.length) return; if (e.deltaY < 0) arcCrumbIdx = Math.max(0, arcCrumbIdx - 1); else arcCrumbIdx = Math.min(nodes.length - 1, arcCrumbIdx + 1); const target = nodes[arcCrumbIdx]; const containerWidth = crumbContainer.offsetWidth; const centerOffset = target.offsetLeft + (target.offsetWidth / 2) - (containerWidth / 2); crumbContainer.scrollTo({ left: centerOffset, behavior: 'smooth' }); }; const closeArchiveCrumbDropdown = () => { closeArchiveSortMenu(); const oldPop = document.getElementById('pk-arc-crumb-pop'); if (oldPop) oldPop.remove(); crumbContainer.querySelectorAll('.pk-arc-crumb-sep').forEach(sep => { sep.classList.remove('pk-active'); sep.innerHTML = CONF.crumbIcons.right; }); }; const showArchiveCrumbDropdown = async (e, node, idx, triggerEl) => { e.stopPropagation(); const isActive = triggerEl.classList.contains('pk-active'); closeArchiveCrumbDropdown(); if (isActive) return; triggerEl.classList.add('pk-active'); triggerEl.innerHTML = CONF.crumbIcons.down; const pop = document.createElement('div'); pop.id = 'pk-arc-crumb-pop'; pop.className = 'pk-crumb-pop pk-arc-crumb-pop'; ['--pk-bg','--pk-fg','--pk-hl','--pk-bd','--pk-pri'].forEach(k => { const v = getComputedStyle(m).getPropertyValue(k).trim(); if (v) pop.style.setProperty(k, v); }); pop.style.background = 'var(--pk-bg)'; pop.style.color = 'var(--pk-fg)'; pop.style.border = '1px solid var(--pk-bd)'; pop.innerHTML = `
${esc(L.loading)}
`; document.body.appendChild(pop); const rect = triggerEl.getBoundingClientRect(); const popLeft = Math.max(8, Math.min(rect.left, window.innerWidth - 328)); const popTop = Math.min(rect.bottom + 6, window.innerHeight - 80); pop.style.left = popLeft + 'px'; pop.style.top = popTop + 'px'; requestAnimationFrame(() => pop.classList.add('pk-show')); const cleanup = () => { document.removeEventListener('mousedown', onDocDown, true); if (pop.isConnected) pop.remove(); if (triggerEl.isConnected) { triggerEl.classList.remove('pk-active'); triggerEl.innerHTML = CONF.crumbIcons.right; } }; const onDocDown = (ev) => { if (pop.contains(ev.target) || triggerEl.contains(ev.target)) return; cleanup(); }; document.addEventListener('mousedown', onDocDown, true); try { const listPath = node.path || ""; const { res, data } = await fetchArchiveList(listPath, currentPwd); if (!res.ok || (data.status && data.status !== 'OK')) throw new Error(data.status_text || L.str_load_failed_simple); const folders = (data.files || data.list || []).filter(f => { const size = f.filesize || f.size || 0; return f.kind === 'drive#folder' || (size == 0 && !f.mime_type); }); pop.innerHTML = ''; if (!folders.length) { const empty = document.createElement('div'); empty.className = 'pk-crumb-item pk-crumb-empty'; empty.textContent = L.str_empty_dir; pop.appendChild(empty); return; } folders.forEach(f => { const itemName = f.filename || f.file_name || f.name || L.str_unknown_name; const fullPath = f.path || ((node.path || "") + itemName + "/"); const item = document.createElement('div'); item.className = 'pk-crumb-item'; const iconSrc = f.icon_link || ''; const iconHtml = iconSrc ? `` : ''; item.innerHTML = `${iconHtml}${esc(itemName)}`; item.onclick = (ev) => { ev.stopPropagation(); cleanup(); pathNodes = pathNodes.slice(0, idx + 1); pathNodes.push({ name: itemName, path: fullPath }); updateView(fullPath); }; pop.appendChild(item); }); } catch (err) { pop.innerHTML = `
${esc(err.message || L.str_load_failed_simple)}
`; } }; const renderData = (data, path) => { closeArchiveCrumbDropdown(); currentPath = path || ''; currentArchiveData = data; selectedArchiveMap.clear(); updateUnzipBtn(); crumbContainer.innerHTML = ''; const items = sortArchiveItems(data.files || data.list || []); const currentDirHasFolders = items.some(f => { const size = f.filesize || f.size || 0; return f.kind === 'drive#folder' || (size == 0 && !f.mime_type); }); pathNodes.forEach((node, idx) => { const isLast = idx === pathNodes.length - 1; const span = document.createElement('span'); span.textContent = node.name; span.style.cssText = isLast ? "font-weight:bold; color:var(--pk-fg); cursor:default;" : "color:#888; cursor:pointer; transition:color 0.2s;"; if (!isLast) { span.onmouseover = () => span.style.color = 'var(--pk-pri)'; span.onmouseout = () => span.style.color = '#888'; span.onclick = () => { pathNodes = pathNodes.slice(0, idx + 1); updateView(node.path); }; } crumbContainer.appendChild(span); if (!isLast || currentDirHasFolders) { const sep = document.createElement('span'); sep.className = 'pk-crumb-sep pk-arc-crumb-sep'; sep.innerHTML = CONF.crumbIcons.right; sep.onclick = (e) => showArchiveCrumbDropdown(e, node, idx, sep); crumbContainer.appendChild(sep); } }); arcCrumbIdx = pathNodes.length - 1; requestAnimationFrame(() => { crumbContainer.scrollTo({ left: crumbContainer.scrollWidth, behavior: 'smooth' }); }); listContainer.innerHTML = ''; listContainer.appendChild(loading); if (items.length === 0) { const emptyMsg = document.createElement('div'); emptyMsg.style.cssText = "padding:50px; text-align:center; color:#999; font-size:13px;"; emptyMsg.textContent = L.str_empty_dir; listContainer.appendChild(emptyMsg); } else { items.forEach(f => { const itemName = f.filename || f.file_name || f.name || L.str_unknown_name; const itemSize = f.filesize || f.size || 0; const isDir = f.kind === 'drive#folder' || (itemSize == 0 && !f.mime_type); const fullPath = (path || "") + itemName + (isDir ? "/" : ""); const row = document.createElement('div'); row.style.cssText = "display:flex; align-items:center; height:48px; padding:0 24px; cursor:pointer; transition:background 0.1s; border-bottom:1px solid rgba(0,0,0,0.03);"; row.onmouseover = () => row.style.background = 'var(--pk-hl)'; row.onmouseout = () => row.style.background = row.dataset.pkArcPicked === '1' ? 'var(--pk-hl)' : 'transparent'; const enterFolder = () => { selectedArchiveMap.clear(); updateUnzipBtn(); pathNodes.push({ name: itemName, path: fullPath }); updateView(fullPath); }; const iconSrc = f.icon_link || ''; const fallbackSvg = (isDir ? CONF.typeIcons.folder : getIcon({ name: itemName, mime_type: f.mime_type })) .replace(/width="\d+"/, 'width="28"').replace(/height="\d+"/, 'height="28"'); const iconHtml = iconSrc ? `${fallbackSvg}` : fallbackSvg; row.innerHTML = `
${iconHtml}
${esc(itemName)}
${isDir ? '-' : fmtSize(itemSize)}
`; const check = row.querySelector('.pk-arc-check'); const key = getArchiveKey(f, fullPath); let clickTimer = 0; const setPicked = (picked) => { check.checked = picked; row.dataset.pkArcPicked = picked ? '1' : '0'; row.style.background = picked ? 'var(--pk-hl)' : 'transparent'; if (picked) selectedArchiveMap.set(key, Object.assign({}, f, { filename: f.filename || itemName, path: f.path || fullPath, kind: f.kind || (isDir ? 'drive#folder' : 'drive#file') })); else selectedArchiveMap.delete(key); updateUnzipBtn(); }; check.onclick = (e) => { e.stopPropagation(); if (clickTimer) { clearTimeout(clickTimer); clickTimer = 0; } setPicked(check.checked); }; row.onclick = (e) => { e.stopPropagation(); if (clickTimer) clearTimeout(clickTimer); clickTimer = setTimeout(() => { clickTimer = 0; setPicked(!check.checked); }, isDir ? 180 : 0); }; if (isDir) { row.ondblclick = (e) => { e.stopPropagation(); if (clickTimer) { clearTimeout(clickTimer); clickTimer = 0; } enterFolder(); }; } listContainer.appendChild(row); }); } }; const updateView = async (path) => { loading.style.display = 'flex'; try { const { res, data } = await fetchArchiveList(path, currentPwd); if (res.ok && data.status === 'OK') renderData(data, path); else throw new Error(data.status_text || L.str_load_failed_simple); } catch (e) { const errDiv = document.createElement('div'); errDiv.style.cssText = "padding:50px; text-align:center; color:#d93025; font-size:13px;"; errDiv.textContent = esc(e.message); listContainer.innerHTML = ''; listContainer.appendChild(loading); listContainer.appendChild(errDiv); } finally { loading.style.display = 'none'; } }; let previewSubmitActive = false; let previewUnzipStarted = false; let previewResolveDone = false; let previewProgressTask = null; let previewLastProgress = 0; const resolvePreview = (value) => { if (previewResolveDone) return; previewResolveDone = true; resolve(value); }; const ensurePreviewProgressTask = () => { if (document.contains(m) || !previewUnzipStarted || previewProgressTask) return previewProgressTask; previewProgressTask = FloatBarManager.create(L.str_unzipping.replace('{n}', file.name)); previewProgressTask.update(L.str_unzipping_prog_fmt.replace('{n}', previewLastProgress)); return previewProgressTask; }; const closePreview = () => { closeArchiveSortMenu(); closeArchiveCrumbDropdown(); m.remove(); if (previewSubmitActive || previewUnzipStarted) { ensurePreviewProgressTask(); return; } resolvePreview({ confirm: false }); }; m.querySelector('.pk-modal-close').onclick = closePreview; m.querySelector('#unzip_cancel').onclick = closePreview; m.tabIndex = 0; setTimeout(() => m.focus(), 10); m.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); m.querySelector('#unzip_confirm').click(); } }); m.querySelector('#unzip_confirm').onclick = async () => { const cur = S.path[S.path.length - 1]; const isStarredRoot = S.starredMode && S.path.length === 1; const isRecentRoot = S.recentMode && S.path.length === 1; const isVirtual = S.isFlattened || S.dupMode || isStarredRoot || isRecentRoot || cur.id === 'analyze_root' || cur.id === 'virtual_search_root'; if (isVirtual) { const userConfirmed = await new Promise(res => { const vm = showModal(`

${L.title_confirm}

${L.msg_unzip_virtual_view_warn}
`); vm.querySelector('#vc_cancel').onclick = () => { vm.remove(); res(false); }; vm.querySelector('.pk-modal-close').onclick = () => { vm.remove(); res(false); }; vm.querySelector('#vc_ok').onclick = () => { vm.remove(); res(true); }; vm.tabIndex = 0; setTimeout(() => vm.focus(), 10); vm.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); vm.querySelector('#vc_ok').click(); } }); }); if (!userConfirmed) return; } const btn = m.querySelector('#unzip_confirm'); const progTxt = m.querySelector('#unzip_progress_text'); btn.disabled = true; btn.style.opacity = '0.6'; btn.style.cursor = 'not-allowed'; btn.textContent = L.str_unzipping_state; progTxt.textContent = L.str_unzipping_prog_0; progTxt.style.opacity = '1'; previewSubmitActive = true; try { const archiveFiles = Array.from(selectedArchiveMap.values()); const resp = await submitArchiveUnzip(currentPwd, archiveFiles); if (resp && resp.task_id) { const taskId = resp.task_id; previewUnzipStarted = true; previewSubmitActive = false; let isPolling = true; const currentFolderId = S.path[S.path.length - 1].id || ''; observeUnzipTask(taskId, currentFolderId, file.id); const updatePreviewProgress = (progress) => { const pct = Math.max(0, Math.min(100, Number(progress) || 0)); previewLastProgress = pct; const text = L.str_unzipping_prog_fmt.replace('{n}', pct); if (document.contains(m)) progTxt.textContent = text; else { const task = ensurePreviewProgressTask(); if (task) task.update(text); } }; const finishUnzip = async () => { isPolling = false; previewLastProgress = 100; if (document.contains(m)) { progTxt.textContent = L.str_unzipping_prog_100; await sleep(500); m.remove(); } else if (previewProgressTask) { previewProgressTask.update(L.str_unzipping_prog_100); setTimeout(() => { if (previewProgressTask) { previewProgressTask.destroy(); previewProgressTask = null; } }, 900); } }; const pollProgress = async () => { if (!isPolling) return; if (!document.contains(m)) ensurePreviewProgressTask(); try { const tRes = await fetch(`https://api-drive.mypikpak.com/decompress/v1/progress?task_id=${taskId}`, { headers: getHeaders() }); if (!tRes.ok) { finishUnzip(); return; } const tData = await tRes.json(); if (tData.phase === 'PHASE_TYPE_COMPLETE') { finishUnzip(); return; } if (tData.progress !== undefined) { updatePreviewProgress(tData.progress); if (Number(tData.progress) >= 100) { finishUnzip(); return; } } } catch(e) { if (previewProgressTask) { previewProgressTask.destroy(); previewProgressTask = null; } isPolling = false; resolvePreview({ confirm: true, password: currentPwd, taskId: taskId, alreadyStarted: true }); return; } if (isPolling) setTimeout(pollProgress, 800); }; resolvePreview({ confirm: true, password: currentPwd, taskId: taskId, alreadyStarted: true, alreadyObserved: true }); pollProgress(); } else { previewSubmitActive = false; m.remove(); resolvePreview({ confirm: true, password: currentPwd }); } } catch (e) { previewSubmitActive = false; const errText = getShareParseOfficialErrorMessage(e) || e.msg || ((e && e.message && !/^msg_/.test(e.message) && !e.shareParseKey) ? e.message : '') || L.str_action_failed; if (!document.contains(m)) { showAlert(errText); resolvePreview({ confirm: false }); return; } btn.disabled = false; btn.style.opacity = '1'; btn.style.cursor = 'pointer'; updateUnzipBtn(); progTxt.style.color = '#d93025'; progTxt.textContent = errText; console.error(e); } }; renderData(preCheckData, ""); }); }; const handleUnzip = async (items, verifiedPwd = "", skipPreview = false, externalTaskId = null) => { if (!items || items.length === 0) return; const currentFolderId = S.path[S.path.length - 1].id || ''; const Vault = { get: () => { try { const raw = JSON.parse(gmGet('pk_pwd_vault', '[]')); return raw.map(x => typeof x === 'object' ? x.p : x); } catch { return []; } }, save: (p) => { if (!p) return; try { let list = JSON.parse(gmGet('pk_pwd_vault', '[]')).map(x => typeof x === 'object' ? x : {p: x, h: 0}); let item = list.find(x => x.p === p); if (item) item.h = (item.h || 0) + 1; else list.push({p: p, h: 1}); list.sort((a, b) => b.h - a.h); if (list.length > 50) list = list.slice(0, 50); gmSet('pk_pwd_vault', JSON.stringify(list)); } catch(e) {} } }; const curPathNode = S.path[S.path.length - 1]; const isStarredRoot = S.starredMode && S.path.length === 1; const isRecentRoot = S.recentMode && S.path.length === 1; const isVirtual = S.isFlattened || S.dupMode || isStarredRoot || isRecentRoot || curPathNode.id === 'analyze_root' || curPathNode.id === 'virtual_search_root'; let startFromPreviewTaskId = externalTaskId; let previewResult = null; if (!skipPreview && !verifiedPwd) { if (items.length === 1) { previewResult = await showArchivePreview(items[0], ""); if (!previewResult || !previewResult.confirm) return; verifiedPwd = previewResult.password; if (previewResult.alreadyStarted && previewResult.taskId) { startFromPreviewTaskId = previewResult.taskId; } } else { if (isVirtual) { const userConfirmed = await new Promise(res => { const vm = showModal(`

${L.title_confirm}

${L.msg_unzip_virtual_view_warn}
`); vm.querySelector('#vc_cancel').onclick = () => { vm.remove(); res(false); }; vm.querySelector('.pk-modal-close').onclick = () => { vm.remove(); res(false); }; vm.querySelector('#vc_ok').onclick = () => { vm.remove(); res(true); }; }); if (!userConfirmed) return; } else { if (!await showConfirm(L.msg_unzip_confirm_n.replace('{n}', items.length))) return; } } } if (S.sel.size > 0) { S.clearSelection(); refresh(); } const isSilentMode = (items.length === 1 && !!startFromPreviewTaskId); let progressTask = null; if (!isSilentMode) { progressTask = FloatBarManager.create(L.str_preparing); } let successCount = 0; let failCount = 0; let lastBatchPwd = ""; const MAX_CONCURRENCY = 3; const activePromises = []; let promptMutex = Promise.resolve(); const waitForTaskDone = async (taskId) => { for (let i = 0; i < 300; i++) { try { const res = await fetch(`https://api-drive.mypikpak.com/decompress/v1/progress?task_id=${taskId}`, { headers: getHeaders() }); if (res.ok) { const d = await res.json(); if (d.phase === 'PHASE_TYPE_COMPLETE' || d.phase === 'PHASE_TYPE_ERROR') return; } else if (res.status === 404) { return; } } catch(e) {} await sleep(2000); } }; const processSingleItem = async (file, index) => { try { let detail = file; if (!detail.gcid && !detail.hash) { for(let w = 0; w < 5; w++) { try { detail = await Promise.race([ apiGet(file.id), new Promise((_, r) => setTimeout(() => r(new Error("Timeout")), 2000)) ]); } catch(e) {} if (detail.gcid || detail.hash || detail.md5_checksum) break; await sleep(1000); } } const gcid = detail.gcid || detail.hash || detail.md5_checksum || ""; let isDone = false; let triedVerified = false; let triedBatch = false; let triedEmpty = false; let hasTriedVault = false; let isFirstManualInput = true; let systemRetry = 0; while (!isDone) { let pwdToTry = ""; let currentSource = ""; if (verifiedPwd && !triedVerified) { pwdToTry = verifiedPwd; currentSource = "VERIFIED"; } else if (lastBatchPwd && !triedBatch) { pwdToTry = lastBatchPwd; currentSource = "BATCH"; } else if (!triedEmpty) { pwdToTry = ""; currentSource = "EMPTY"; } else if (!hasTriedVault) { hasTriedVault = true; const tryLimit = gmGet('pk_pwd_try_count', 10); const candidates = Vault.get().slice(0, tryLimit); if (candidates.length > 0) { if (progressTask) progressTask.update(L.msg_smart_matching_file.replace('{n}', file.name)); const checkTask = async (pwd, idx) => { const tieredDelay = Math.floor(idx / 5) * 1200; await sleep((idx * 150) + tieredDelay); try { const autoRes = await fetch(`https://api-drive.mypikpak.com/decompress/v1/list`, { method: 'POST', headers: getHeaders(), body: JSON.stringify({ gcid, file_id: file.id, password: pwd, path: "" }), signal: AbortSignal.timeout(5000) }); const autoData = await autoRes.json(); if (autoData.status === 'OK') return pwd; } catch(e) {} throw new Error("Wrong"); }; try { const correctPwd = await Promise.any(candidates.map((p, i) => checkTask(p, i))); if (correctPwd) { lastBatchPwd = correctPwd; Vault.save(correctPwd); triedBatch = false; continue; } } catch (e) {} } continue; } else { currentSource = "MANUAL"; } if (currentSource === "MANUAL") { const pwdBeforeWait = lastBatchPwd; const previousMutex = promptMutex; let releaseMutex; promptMutex = new Promise(r => releaseMutex = r); await previousMutex; try { if (lastBatchPwd !== pwdBeforeWait && lastBatchPwd !== "") { continue; } setLoad(false); const errorMsg = isFirstManualInput ? "" : L.err_pwd_simple; const userPwd = await askForPassword(file.name, errorMsg, true); isFirstManualInput = false; setLoad(true); if (userPwd !== null) { lastBatchPwd = userPwd; Vault.save(userPwd); verifiedPwd = userPwd; triedVerified = false; continue; } else { failCount++; isDone = true; continue; } } finally { releaseMutex(); } } try { if (progressTask) { const doneCount = successCount + failCount; progressTask.update(`${L.str_unzipping.replace('{n}', file.name)} (${doneCount + 1} / ${items.length})`); } let taskId = null; let needsObserve = true; if (index === 0 && startFromPreviewTaskId) { taskId = startFromPreviewTaskId; startFromPreviewTaskId = null; if (items.length === 1 && previewResult && previewResult.alreadyObserved) { needsObserve = false; } } else { const preRes = await fetch(`https://api-drive.mypikpak.com/decompress/v1/list`, { method: 'POST', headers: getHeaders(), body: JSON.stringify({ gcid, file_id: file.id, password: pwdToTry, path: "" }), signal: AbortSignal.timeout(8000) }); const preData = await preRes.json().catch(() => ({})); const preErrStr = (preData.status_text || preData.error_description || preData.msg || preData.error || "").toLowerCase(); if (isArchivePasswordSignal(preData, preErrStr)) { throw { isPwd: true, code: 10023, msg: L.err_pwd_simple }; } const resp = await sendUnzipRequest(file, pwdToTry); taskId = resp?.task_id; } if (taskId) { if (needsObserve) observeUnzipTask(taskId, currentFolderId, file.id, true); await waitForTaskDone(taskId); } if (pwdToTry) { lastBatchPwd = pwdToTry; Vault.save(pwdToTry); } successCount++; isDone = true; if (!isSilentMode) { const doneCount = successCount + failCount; updateLoadTxt(`${L.str_unzipping.replace('{n}', file.name)}\n(${doneCount} / ${items.length})`); } } catch (err) { const errText = (err.msg || "").toLowerCase(); const isDefinitePwdErr = err.isPwd || isArchivePasswordSignal(err, err.msg || errText); const isAlreadyRunning = isArchiveAlreadyRunningSignal(err, err.msg || errText); const isSystemErr = !isDefinitePwdErr && !isAlreadyRunning && (err.name === 'TimeoutError' || err.code === 400 || err.code === 429 || err.code >= 500 || errText.includes('limit') || errText.includes('busy') || errText.includes('task creation failed')); if (isAlreadyRunning) { console.warn(`[Unzip] Server Lock detected for: ${file.name}. Task is running in background or zombie.`); if (!isSilentMode) { showToast(L.msg_unzip_running_bg.replace('{n}', esc(file.name)), 'warning', 6000); } successCount++; isDone = true; } else if (isSystemErr) { systemRetry++; if (systemRetry < 6) { const delay = 1500 * systemRetry + (Math.random() * 500); updateLoadTxt(L.msg_system_busy_retry.replace('{n}', systemRetry)); await sleep(delay); continue; } failCount++; isDone = true; if (progressTask) progressTask.update(`${L.str_unzipping.replace('{n}', file.name)} (${successCount + failCount} / ${items.length})`); } else if (isDefinitePwdErr) { if (currentSource === "VERIFIED") { triedVerified = true; verifiedPwd = ""; } else if (currentSource === "BATCH") { triedBatch = true; } else if (currentSource === "EMPTY") { triedEmpty = true; } } else { console.error(err); failCount++; isDone = true; if (progressTask) progressTask.update(`${L.str_unzipping.replace('{n}', file.name)} (${successCount + failCount} / ${items.length})`); } } } } catch (fatal) { console.error("Fatal Worker Error:", fatal); failCount++; } }; for (let i = 0; i < items.length; i++) { while (activePromises.length >= MAX_CONCURRENCY) { await Promise.race(activePromises); } const p = processSingleItem(items[i], i); const wrappedP = p.then(() => { const idx = activePromises.indexOf(wrappedP); if (idx > -1) activePromises.splice(idx, 1); }); activePromises.push(wrappedP); } await Promise.all(activePromises); if (successCount > 0 && !isSilentMode) { S.clearSelection(); refresh(); updateStat(); if (window.pkSmartRefreshTrigger) window.pkSmartRefreshTrigger(true); } if (progressTask) progressTask.destroy(); if (!isSilentMode) { setLoad(false); if (successCount > 0) { let msg = L.msg_unzip_batch_submitted.replace('{n}', successCount); if (failCount > 0) msg += " " + L.msg_unzip_batch_skipped.replace('{n}', failCount); if (isVirtual) msg += L.msg_unzip_check_source; showToast(msg); } else if (failCount > 0) { showToast(L.msg_unzip_fail, 'error'); } } }; const renderCalendar = (anchorEl, onSelect) => { const old = document.querySelector('.pk-cal-pop'); if (old) old.remove(); const pop = document.createElement('div'); pop.className = 'pk-cal-pop'; if (document.querySelector('.pk-ov')?.classList.contains('pk-dark')) { pop.classList.add('pk-dark'); } pop.style.cssText = "opacity: 0; animation: none; visibility: hidden; transition: opacity 0.15s ease; z-index: 2147483647 !important"; const now = new Date(getServerNow()); const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); let viewYear = now.getFullYear(); let viewMonth = now.getMonth(); const sideOpts = [ { lbl: L.share_perm, val: -1 }, { lbl: `7 ${L.share_days}`, val: 7 }, { lbl: `14 ${L.share_days}`, val: 14 }, { lbl: `30 ${L.share_days}`, val: 30 } ]; const buildHTML = () => { const navLeft = ``; const navRight = ``; let sideHtml = sideOpts.map(o => `
${o.lbl}
` ).join(''); const monthStr = (L.cal_months || [])[viewMonth] || (viewMonth + 1 + L.unit_month); const headerHtml = `
${navLeft}
${navLeft}
${viewYear} ${monthStr}
${navRight}
${navRight}
`; const weeksHtml = (L.cal_week_days || ["S","M","T","W","T","F","S"]).map(w => `
${w}
` ).join(''); const firstDay = new Date(viewYear, viewMonth, 1).getDay(); const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate(); let daysHtml = ''; for(let i=0; i`; for(let d=1; d<=daysInMonth; d++) { const currentTs = new Date(viewYear, viewMonth, d).getTime(); const isToday = currentTs === todayStart; const isPast = currentTs < todayStart; const isDisabled = isPast; let cls = 'pk-cal-td'; if (isDisabled) cls += ' disabled'; if (isToday) cls += ' today'; daysHtml += `
${d}
`; } return `
${sideHtml}
${L.cal_custom_title}
${headerHtml}
${weeksHtml} ${daysHtml}
`; }; const refresh = () => { pop.innerHTML = buildHTML(); pop.querySelectorAll('.pk-cal-side-item').forEach(el => { el.onclick = (e) => { e.stopPropagation(); const days = parseInt(el.dataset.val); onSelect({ days: days, ts: null }); pop.remove(); document.removeEventListener('mousedown', onClickOutside); }; }); pop.querySelectorAll('.pk-cal-td:not(.disabled)').forEach(el => { el.onclick = (e) => { e.stopPropagation(); const ts = parseInt(el.dataset.ts); const diff = (ts - todayStart) / (1000 * 3600 * 24); const days = Math.max(1, Math.ceil(diff)); onSelect({ days: days, ts: ts }); pop.remove(); document.removeEventListener('mousedown', onClickOutside); }; }); pop.querySelector('#cal_prev_y').onclick = (e) => { e.stopPropagation(); viewYear--; refresh(); }; pop.querySelector('#cal_next_y').onclick = (e) => { e.stopPropagation(); viewYear++; refresh(); }; pop.querySelector('#cal_prev_m').onclick = (e) => { e.stopPropagation(); viewMonth--; if(viewMonth<0){viewMonth=11;viewYear--;} refresh(); }; pop.querySelector('#cal_next_m').onclick = (e) => { e.stopPropagation(); viewMonth++; if(viewMonth>11){viewMonth=0;viewYear++;} refresh(); }; }; refresh(); document.body.appendChild(pop); const updatePosition = () => { if (!pop.isConnected) return; if (anchorEl.offsetParent === null) { cleanup(); return; } const rect = getLogicalRect(anchorEl); let top = rect.bottom + 5; let left = rect.left; if (top + 320 > window.innerHeight) top = rect.top - 320 - 5; if (left + 400 > window.innerWidth) left = window.innerWidth - 400 - 10; if (left < 10) left = 10; pop.style.top = top + 'px'; pop.style.left = left + 'px'; }; updatePosition(); requestAnimationFrame(() => { pop.style.visibility = 'visible'; void pop.offsetHeight; pop.style.opacity = '1'; }); window.addEventListener('resize', updatePosition); window.addEventListener('scroll', updatePosition, true); const onClickOutside = (e) => { if (!pop || !anchorEl || !pop.parentNode) return; if (!pop.contains(e.target) && !anchorEl.contains(e.target)) { cleanup(); } }; const cleanup = () => { window.removeEventListener('resize', updatePosition); window.removeEventListener('scroll', updatePosition, true); document.removeEventListener('mousedown', onClickOutside); pop.remove(); }; setTimeout(() => document.addEventListener('mousedown', onClickOutside), 10); }; const showShareDetail = (item) => { const copyIcon = ``; let statusColor = '#52c41a'; let statusText = L.share_perm; if (item.share_status === 'EXPIRED') { statusColor = '#faad14'; statusText = L.str_share_expired; } else if (item.share_status === 'DELETED') { statusColor = '#ff4d4f'; statusText = L.str_share_deleted; } else if (item.expiration_at && item.expiration_at !== "-1") { statusColor = 'inherit'; const d = new Date(item.expiration_at); const pad = n => String(n).padStart(2, '0'); statusText = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`; } else if (item.share_status_text) { statusText = item.share_status_text; } const labelStyle = "font-size:14px; font-weight:bold; color:var(--pk-fg); margin-bottom:8px;"; const inputWrapStyle = "display:flex; align-items:center; border:1px solid var(--pk-bd); border-radius:4px; padding:0 12px; background:var(--pk-bg); height:40px; transition:border-color 0.2s;"; const inputStyle = "flex:1; border:none; background:transparent; outline:none; font-size:14px; color:var(--pk-fg);"; const copyBtnStyle = "margin-left:10px; display:flex; align-items:center; justify-content:center; transition:color 0.2s;"; const m = showModal(`

${L.title_share_detail}

${esc(item.name || item.title)}
${item.view_count || 0}
${L.lbl_share_view}
${item.save_count || 0}
${L.lbl_share_save}
${L.lbl_share_link_title}
${item.share_url}
${copyIcon}
${L.lbl_share_pwd_title}
${copyIcon}
${L.share_count_ed}
${CONF.crumbIcons.right}
${L.lbl_share_expire_title}
${CONF.crumbIcons.right}
${L.lbl_share_code_title}
${L.btn_add_share_code}
`); const modalContainer = m.querySelector('.pk-modal'); if (modalContainer) { modalContainer.style.padding = '0'; modalContainer.style.width = 'fit-content'; modalContainer.style.display = 'flex'; modalContainer.style.flexDirection = 'column'; modalContainer.style.overflow = 'hidden'; modalContainer.style.maxHeight = '600px'; } m.querySelectorAll('.pk-copy-btn').forEach(btn => { btn.onclick = (e) => { const val = btn.getAttribute('data-val'); if(!val) return; GM_setClipboard(val); const originalSvg = btn.innerHTML; btn.innerHTML = ``; setTimeout(() => btn.innerHTML = originalSvg, 1500); }; }); const pwdField = m.querySelector('#pk_share_pwd_edit'); const pwdCopyBtn = m.querySelector('#pk_copy_pwd'); pwdField.readOnly = true; pwdField.style.cursor = 'pointer'; pwdField.onclick = () => { const subM = document.createElement('div'); subM.className = 'pk-modal-ov'; subM.style.zIndex = (++modalZIndexCounter).toString(); if (document.querySelector('.pk-ov').classList.contains('pk-dark')) subM.classList.add('pk-dark'); subM.innerHTML = `
${CONF.icons.close}

${L.title_edit_pwd}

${L.btn_close_pwd}
${L.btn_cancel}
`; document.body.appendChild(subM); const input = subM.querySelector('#pk_new_pwd_input'); const saveBtn = subM.querySelector('#pk_edit_pwd_save'); input.focus(); const validate = () => { const val = input.value.trim(); const isValid = /^[a-zA-Z0-9]{4,10}$/.test(val); saveBtn.disabled = !isValid; saveBtn.style.opacity = isValid ? '1' : '0.4'; saveBtn.style.cursor = isValid ? 'pointer' : 'not-allowed'; return isValid; }; input.oninput = validate; validate(); const closeBtn = subM.querySelector('#pk_close_pwd_row'); closeBtn.onclick = async (e) => { e.stopPropagation(); closeBtn.style.pointerEvents = 'none'; closeBtn.style.opacity = '0.5'; try { await apiUpdateShare(item.id, { pass_code_option: 'NOT_REQUIRED' }); item.pass_code = ""; pwdField.value = L.str_no_pwd; pwdCopyBtn.setAttribute('data-val', ""); pwdCopyBtn.style.display = 'none'; const mainCopyBtn = m.querySelector('#pk_detail_copy_all'); if (mainCopyBtn) mainCopyBtn.textContent = L.btn_copy_link; renderVisible(); subM.remove(); const toast = document.createElement('div'); toast.style.cssText = "position:absolute; top:180px; left:50%; transform:translateX(-50%); width:max-content; background:var(--pk-toast-bg); color:var(--pk-toast-fg); padding:10px 24px; border-radius:8px; font-size:14px; font-weight:600; z-index:10007; pointer-events:none; box-shadow:0 8px 24px var(--pk-tip-sd); border:1px solid var(--pk-toast-bd);"; toast.textContent = L.msg_pwd_updated; m.querySelector('.pk-modal').appendChild(toast); setTimeout(() => toast.remove(), 1200); } catch (err) { showAlert(err.message); closeBtn.style.pointerEvents = 'auto'; closeBtn.style.opacity = '1'; } }; const doSave = async () => { const newPwd = input.value.trim(); const oldPwd = item.pass_code || ''; if (!validate() || newPwd === oldPwd) { if(newPwd === oldPwd) subM.remove(); return; } saveBtn.disabled = true; saveBtn.style.opacity = '0.7'; saveBtn.textContent = "..."; try { await apiUpdateShare(item.id, { pass_code_option: 'REQUIRED', custom_pass_code: newPwd }); item.pass_code = newPwd; pwdField.value = newPwd; pwdCopyBtn.setAttribute('data-val', newPwd); pwdCopyBtn.style.setProperty('display', 'flex', 'important'); const mainCopyBtn = m.querySelector('#pk_detail_copy_all'); if (mainCopyBtn) mainCopyBtn.textContent = L.btn_copy_link_pwd; renderVisible(); subM.remove(); const toast = document.createElement('div'); toast.style.cssText = "position:absolute; top:180px; left:50%; transform:translateX(-50%); width:max-content; background:var(--pk-toast-bg); color:var(--pk-toast-fg); padding:10px 24px; border-radius:8px; font-size:14px; font-weight:600; z-index:10007; pointer-events:none; box-shadow:0 8px 24px var(--pk-tip-sd); border:1px solid var(--pk-toast-bd);"; toast.textContent = L.msg_pwd_updated; m.querySelector('.pk-modal').appendChild(toast); setTimeout(() => toast.remove(), 1200); } catch (e) { showAlert(`${L.str_error}: ${e.message}`); saveBtn.disabled = false; saveBtn.style.opacity = '1'; saveBtn.textContent = L.btn_save; } }; saveBtn.onclick = doSave; subM.querySelector('#pk_edit_pwd_cancel').onclick = () => subM.remove(); subM.querySelector('.pk-modal-close').onclick = () => subM.remove(); input.onkeydown = (e) => { if(e.key === 'Enter') doSave(); if(e.key === 'Escape') { e.stopPropagation(); subM.remove(); } }; }; const phraseRowsContainer = m.querySelector('#pk_phrase_rows'); const btnAddPhrase = m.querySelector('#pk_btn_add_phrase'); let localPhrases = []; const renderPhraseRows = () => { if (!phraseRowsContainer) return; phraseRowsContainer.innerHTML = localPhrases.map(p => `
${esc(p)}
`).join(''); phraseRowsContainer.querySelectorAll('.pk-phrase-row').forEach(row => { row.onclick = (e) => { const copyBtn = e.target.closest('.pk-copy-btn'); if (copyBtn) { const val = copyBtn.dataset.val; GM_setClipboard(val); const originalHtml = copyBtn.innerHTML; copyBtn.innerHTML = ``; setTimeout(() => { if (copyBtn.isConnected) { copyBtn.innerHTML = originalHtml; } }, 1500); return; } openPhraseEditModal(row.dataset.val); }; row.onmouseenter = () => row.style.background = 'var(--pk-hl)'; row.onmouseleave = () => row.style.background = 'var(--pk-bg)'; }); }; const openPhraseEditModal = (oldVal = "") => { const subM = document.createElement('div'); subM.className = 'pk-modal-ov'; subM.style.zIndex = (++modalZIndexCounter).toString(); if (document.querySelector('.pk-ov').classList.contains('pk-dark')) subM.classList.add('pk-dark'); subM.innerHTML = `
${CONF.icons.close}

${oldVal ? L.title_edit_share_code : L.btn_add_share_code}

${L.btn_del_share_code}
${L.btn_cancel}
`; document.body.appendChild(subM); const input = subM.querySelector('#pk_new_phrase_input'); const saveBtn = subM.querySelector('#pk_edit_phrase_save'); input.focus(); const validate = () => { const val = input.value.trim(); const isValid = val.length >= 5 && val.length <= 18; saveBtn.disabled = !isValid; saveBtn.style.opacity = isValid ? '1' : '0.4'; saveBtn.style.cursor = isValid ? 'pointer' : 'not-allowed'; return isValid; }; input.oninput = validate; validate(); const doRequest = async (newVal) => { saveBtn.disabled = true; saveBtn.style.opacity = '0.7'; saveBtn.textContent = "..."; try { await apiUpdateSharePhrase(item.id, newVal, oldVal); if (!newVal) { localPhrases = localPhrases.filter(x => x !== oldVal); } else if (!oldVal) { localPhrases.push(newVal); } else { localPhrases = localPhrases.map(x => x === oldVal ? newVal : x); } subM.remove(); renderPhraseRows(); renderVisible(); const toast = document.createElement('div'); toast.style.cssText = "position:absolute; top:180px; left:50%; transform:translateX(-50%); width:max-content; background:var(--pk-toast-bg); color:var(--pk-toast-fg); padding:10px 24px; border-radius:8px; font-size:14px; font-weight:600; z-index:10007; pointer-events:none; box-shadow:0 8px 24px var(--pk-tip-sd); border:1px solid var(--pk-toast-bd);"; toast.textContent = L.msg_share_code_updated; m.querySelector('.pk-modal').appendChild(toast); setTimeout(() => toast.remove(), 1200); } catch (err) { showAlert(err.message); saveBtn.disabled = false; saveBtn.style.opacity = '1'; saveBtn.textContent = L.btn_save; } }; saveBtn.onclick = () => { if(validate()) doRequest(input.value.trim()); }; const delBtn = subM.querySelector('#pk_del_phrase_row'); if (delBtn) delBtn.onclick = () => doRequest(""); subM.querySelector('#pk_edit_phrase_cancel').onclick = () => subM.remove(); subM.querySelector('.pk-modal-close').onclick = () => subM.remove(); input.onkeydown = (ev) => { if(ev.key === 'Enter') { if(validate()) saveBtn.onclick(); } if(ev.key === 'Escape') { ev.stopPropagation(); subM.remove(); } }; }; if (btnAddPhrase) btnAddPhrase.onclick = () => openPhraseEditModal(""); apiGetSharePhrases(item.id).then(list => { localPhrases = list; renderPhraseRows(); }); const limitField = m.querySelector('#pk_share_limit_edit'); const limitInput = m.querySelector('#pk_share_limit_val'); limitField.onclick = () => { const currentSave = parseInt(item.save_count || 0); const currentLimit = parseInt(item.limit_count || 0); const subM = document.createElement('div'); subM.className = 'pk-modal-ov'; subM.style.zIndex = (++modalZIndexCounter).toString(); if (document.querySelector('.pk-ov').classList.contains('pk-dark')) subM.classList.add('pk-dark'); subM.innerHTML = `
${CONF.icons.close}

${L.share_count_ed}

${CONF.crumbIcons.down.replace('points="6 9 12 15 18 9"', 'points="18 15 12 9 6 15"')}
${CONF.crumbIcons.down}
${L.btn_cancel}
`; document.body.appendChild(subM); const radios = subM.querySelectorAll('input[name="sh_mod_cnt"]'); const input = subM.querySelector('#sh_mod_cnt_val'); const saveBtn = subM.querySelector('#pk_mod_cnt_save'); const ctrl = subM.querySelector('.pk-num-ctrl'); const updateCtrlState = () => { ctrl.style.opacity = input.disabled ? '0.3' : '1'; ctrl.style.pointerEvents = input.disabled ? 'none' : 'auto'; ctrl.style.cursor = input.disabled ? 'not-allowed' : 'default'; }; radios.forEach(r => r.onchange = () => { input.disabled = r.value !== 'custom'; if (!input.disabled) input.focus(); updateCtrlState(); }); updateCtrlState(); subM.querySelector('#sh_cnt_inc').onclick = (e) => { if (input.disabled) return; e.stopPropagation(); input.value = (parseInt(input.value) || currentSave) + 1; validate(); }; subM.querySelector('#sh_cnt_dec').onclick = (e) => { if (input.disabled) return; e.stopPropagation(); input.value = Math.max(currentSave + 1, (parseInt(input.value) || (currentSave + 2)) - 1); validate(); }; const doSave = async () => { const rVal = subM.querySelector('input[name="sh_mod_cnt"]:checked').value; let newLimit = 0; if (rVal === 'custom') { const val = parseInt(input.value); if (isNaN(val) || val <= 0) { input.style.borderColor = '#d93025'; return; } newLimit = val; } else { newLimit = parseInt(rVal); if (newLimit === -1) newLimit = 0; } if (newLimit > 0 && newLimit <= currentSave) { showToast(L.err_limit_too_low.replace('{n}', newLimit).replace('{s}', currentSave), 'error'); if(input.disabled) input.value = currentSave + 1; return; } saveBtn.textContent = "..."; saveBtn.disabled = true; item.limit_count = newLimit; const store = JSON.parse(gmGet('pk_share_limits', '{}')); if (newLimit > 0) { store[item.id] = newLimit; } else { delete store[item.id]; } gmSet('pk_share_limits', JSON.stringify(store)); if (newLimit === 0) { limitInput.value = L.share_unlimit; } else { limitInput.value = `${newLimit} ${L.share_times}`; } limitInput.style.color = 'var(--pk-fg)'; renderVisible(); if (window.pkSmartRefreshTrigger) { window.pkSmartRefreshTrigger(true); } setTimeout(() => { subM.remove(); showToast(L.msg_limit_updated); }, 200); }; subM.querySelector('#pk_mod_cnt_cancel').onclick = () => subM.remove(); subM.querySelector('.pk-modal-close').onclick = () => subM.remove(); saveBtn.onclick = doSave; input.onkeydown = (e) => { if(e.key === 'Enter') doSave(); }; }; const expField = m.querySelector('#pk_share_exp_edit'); const expInput = m.querySelector('#pk_share_exp_val'); expField.onclick = (e) => { e.stopPropagation(); const existing = document.querySelector('.pk-cal-pop'); if (existing) { existing.remove(); return; } renderCalendar(expField, async (res) => { const selectedDays = res.days; if (selectedDays === null) return; const originalText = expInput.value; expInput.value = "..."; try { const payload = {}; let newText = ""; let newStatusLeft = ""; if (selectedDays === -1) { payload.expiration_at = "-1"; newText = L.share_perm; newStatusLeft = "-1"; } else { const d = res.ts ? new Date(res.ts) : new Date(getServerNow() + selectedDays * 24 * 3600 * 1000); const pad = n => String(n).padStart(2, '0'); const ds = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`; payload.expiration_at = `${ds}T23:59:59.000+08:00`; newText = res.ts ? ds : (selectedDays + L.share_days); if (res.ts) { const diff = Math.max(1, Math.ceil((res.ts - new Date(getServerNow()).setHours(0,0,0,0)) / (1000 * 3600 * 24))); newStatusLeft = diff + L.unit_days.trim(); } else { newStatusLeft = selectedDays + L.share_days; } } await apiUpdateShare(item.id, payload); item.expiration_days = selectedDays; item.expiration_at = payload.expiration_at; item.expiration_left = newStatusLeft; expInput.value = newText; expInput.style.color = 'var(--pk-fg)'; renderVisible(); const toast = document.createElement('div'); toast.style.cssText = "position:absolute; top:180px; left:50%; transform:translateX(-50%); width:max-content; background:var(--pk-toast-bg); color:var(--pk-toast-fg); padding:10px 24px; border-radius:8px; font-size:14px; font-weight:600; z-index:10007; pointer-events:none; box-shadow:0 8px 24px var(--pk-tip-sd); border:1px solid var(--pk-toast-bd);"; toast.textContent = L.msg_exp_updated; m.querySelector('.pk-modal').appendChild(toast); setTimeout(() => toast.remove(), 1200); } catch (err) { showAlert(`${L.str_error}: ${err.message}`); expInput.value = originalText; } }); }; m.querySelector('#pk_detail_cancel').onclick = async () => { if (await showConfirm(L.msg_cancel_share_confirm.replace('{n}', 1))) { setLoad(true); try { await apiCancelShare([item.id]); m.remove(); showAlert(L.msg_cancel_share_done.replace('{n}', 1)); load(false, true); } catch (e) { setLoad(false); showAlert(`${L.str_error}: ${e.message}`); } } }; m.querySelector('#pk_detail_copy_all').onclick = () => { const url = item.share_url; const pwd = item.pass_code || ''; const title = item.name || item.title; let text = url + '\n'; if (pwd) text += `${L.share_copy_pwd}: ${pwd}\n`; text += `${title}\n${L.share_copy_suffix}`; GM_setClipboard(text); const btn = m.querySelector('#pk_detail_copy_all'); const orgTxt = btn.textContent; btn.textContent = L.msg_copy_success; const originalColor = btn.style.backgroundColor; const originalBorder = btn.style.borderColor; btn.style.backgroundColor = "#52c41a"; btn.style.borderColor = "#52c41a"; setTimeout(() => { btn.textContent = orgTxt; btn.style.backgroundColor = originalColor; btn.style.borderColor = originalBorder; }, 1500); }; }; UI.btnCancelShare.onclick = async () => { const selectedIds = S.getSelectedIds(); const n = selectedIds.length; if (n === 0) return; if (!await showConfirm(L.msg_cancel_share_confirm.replace('{n}', n))) return; setLoad(true); updateLoadTxt(L.msg_cancel_share_ing); try { const selectedItems = selectedIds.map(id => S.itemMap.get(id)).filter(Boolean); const serverIds = selectedItems.filter(it => !it._is_local_phantom).map(it => it.id); const phantomIds = selectedItems.filter(it => it._is_local_phantom).map(it => it.id); if (serverIds.length > 0) { await apiCancelShare(serverIds); } if (phantomIds.length > 0) { const graveyard = JSON.parse(gmGet('pk_expired_shares', '[]')); const newGraveyard = graveyard.filter(x => !phantomIds.includes(x.id)); gmSet('pk_expired_shares', JSON.stringify(newGraveyard)); } S.clearSelection(); await load(false, true); showToast(L.msg_cancel_share_done.replace('{n}', n)); } catch (e) { showAlert(`${L.str_error}: ${e.message}`); } finally { setLoad(false); } }; if (UI.btnCopyLinkOffline) { UI.btnCopyLinkOffline.onclick = () => { const ids = S.getSelectedIds(); if (ids.length === 0) return; const tasks = ids.map(id => S.itemMap.get(id)).filter(t => t && (t.source_url || (t.params && t.params.url))); if (tasks.length === 0) return; const urls = tasks.map(t => t.source_url || t.params.url).join('\n'); GM_setClipboard(urls); showToast(L.msg_copy_success); }; } if (UI.btnRetryTask) { UI.btnRetryTask.onclick = async () => { const ids = S.getSelectedIds(); if (ids.length === 0) return; const targets = ids.map(id => S.itemMap.get(id)) .filter(t => t && t.phase === 'PHASE_TYPE_ERROR' && (t.source_url || (t.params && t.params.url))); if (targets.length === 0) { showToast(L.err_no_failed_task, "error"); return; } setLoad(true); updateLoadTxt(L.str_processing); let successCount = 0; try { for (const task of targets) { const url = task.source_url || task.params.url; try { await apiAddOfflineTask(url); await apiCancelTask([task.id]); successCount++; if (typeof globalNeedsSync !== 'undefined') globalNeedsSync = true; } catch (e) { console.error(`[Retry] Failed for ${task.name}:`, e); if (e.message && e.message.includes(L.err_task_exists)) { await apiCancelTask([task.id]).catch(()=>{}); successCount++; } } await sleep(150); } await load(false, true); showToast(L.msg_retry_submitted.replace('{n}', successCount)); } catch (e) { showAlert(`${L.str_error}: ${e.message}`); } finally { setLoad(false); S.clearSelection(); updateStat(); } }; } UI.btnMigrate.onclick = async () => { const selectedIds = S.getSelectedIds(); const selectedCount = selectedIds.length; if (selectedCount === 0) return; const L = getStrings(); const hasConflict = selectedIds.some(id => { const item = S.itemMap.get(id); if (item && item.kind === 'drive#folder') return isPathBusy(item.id); return S.movingIds.has(id); }); if (hasConflict) { showAlert(L.msg_op_blocked_moving); return; } if (selectedCount > 100) { showToast(L.err_migrate_limit, 'error'); return; } const estimatedSize = JSON.stringify(selectedIds).length; if (estimatedSize > 3.8 * 1024 * 1024) { const mb = (estimatedSize / 1024 / 1024).toFixed(2); showAlert(L.err_migrate_too_many.replace('{s}', mb)); return; } if (!await showConfirm(L.msg_migrate_confirm.replace('{n}', selectedCount))) return; setLoad(true); updateLoadTxt(L.msg_migrate_packing); try { let sourceUid = ""; for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); if (k && k.startsWith('credentials')) { try { const v = JSON.parse(localStorage.getItem(k)); if (v && v.sub) { sourceUid = v.sub; break; } } catch {} } } if (!sourceUid) sourceUid = "UNKNOWN_UID_" + Date.now(); const randPass = Math.random().toString(36).slice(-5) + Math.random().toString(36).slice(-5).toUpperCase(); const payload = { file_ids: selectedIds, share_to: 'encryptedlink', expiration_days: 1, pass_code_option: 'REQUIRED', custom_pass_code: randPass, limit_count: 1 }; const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/share`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(payload) }); if (!res.ok) { const err = await res.json().catch(() => ({})); if (res.status === 403 || res.status === 400) { throw new Error(L.err_migrate_ban); } throw new Error(err.error_description || `API Error ${res.status}`); } const data = await res.json(); const stub = { source_uid: sourceUid, share_id: data.share_id, pass_code: randPass, file_count: selectedCount, file_ids: selectedIds, timestamp: Date.now() }; gmSet('pk_migration_stub', JSON.stringify(stub)); try { const store = JSON.parse(gmGet('pk_share_limits', '{}')); store[data.share_id] = 1; gmSet('pk_share_limits', JSON.stringify(store)); } catch(e) {} setLoad(false); await showAlert(L.msg_migrate_ready, L.btn_migrate); const keysToRemove = []; for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); if (k && (k.startsWith('credentials') || k.startsWith('captcha') || k === 'pk_captured_captcha')) { keysToRemove.push(k); } } keysToRemove.forEach(k => localStorage.removeItem(k)); if (typeof purgeAllCachesOnLogout === 'function') purgeAllCachesOnLogout(); if (!location.href.includes('/login')) window.location.href = 'https://mypikpak.com/drive/login'; } catch (e) { setLoad(false); showAlert(`${L.str_error}: ${e.message}`); } }; UI.btnUnzip.onclick = async () => { ensureItemMap(); const isArchive = (it) => { if (!it || it.kind === 'drive#folder') return false; const n = (it.name || '').toLowerCase(); const m = (it.mime_type || '').toLowerCase(); return m.includes('zip') || m.includes('rar') || m.includes('7z') || m.includes('compressed') || m.includes('archive') || n.endsWith('.zip') || n.endsWith('.rar') || n.endsWith('.7z') || n.endsWith('.tar') || n.endsWith('.gz'); }; const rawTargets = S.getSelectedIds().map(id => S.itemMap.get(id)).filter(i => isArchive(i)); const existingFolderNames = new Set( S.items.filter(i => i.kind === 'drive#folder').map(i => i.name) ); const targets = rawTargets.filter(item => { if (rawTargets.length === 1) return true; const isMarkedUnzipped = item.params && (item.params.global_file_kind === '1' || item.params.global_file_root); if (!isMarkedUnzipped) return true; let targetFolderName = item.name; const lastDot = targetFolderName.lastIndexOf('.'); if (lastDot > 0) targetFolderName = targetFolderName.substring(0, lastDot); return !existingFolderNames.has(targetFolderName); }); const skippedItems = rawTargets.filter(item => !targets.includes(item)); const skippedCount = skippedItems.length; if (skippedCount > 0) { const delRes = await showDeleteConfirm(L.msg_unzip_skip_del_confirm.replace('{n}', skippedCount)); if (delRes.confirm) { const idsToDelete = skippedItems.map(i => i.id); await executeBatchDelete(idsToDelete, { silent: true, forceRefresh: false, hardDelete: delRes.hardDelete }); showToast(L.msg_del_items_done.replace('{n}', skippedCount)); } else { showToast(L.msg_skip_unzipped.replace('{n}', skippedCount)); } } if (targets.length === 1) { if (targets[0]._isShareItem) { if (isArchiveLike(targets[0])) handleOpenShareArchive(targets[0]); else if (isAudioLikeItem(targets[0]) || isVideoLikeItem(targets[0]) || isImageLikeItem(targets[0])) handleOpenShareFile(targets[0]); else showToast(L.msg_share_parse_select_first, 'warning'); } else handleOpenArchive(targets[0]); } else if (targets.length > 1) { if (targets.some(t => t && t._isShareItem)) { showToast(L.msg_share_parse_select_first, 'warning'); return; } handleUnzip(targets); } }; ctx.querySelector('#ctx-share').onclick = async () => { ctx.style.display = 'none'; const ids = S.getSelectedIds(); if (ids.length === 0) return; if (ids.length > 100) { showToast(L.err_share_limit, 'error'); return; } const item = S.itemMap.get(ids[0]); if (!item) return; const m = showModal(`

${L.share_title}

${L.share_mode}
${L.share_public}
${L.share_encrypted}
${L.share_expiry}
${L.share_custom}
${L.share_pass}
${L.share_count}
`); const tabs = m.querySelectorAll('.pk-s-tab'); const passSec = m.querySelector('#sh_pass_sec'); const passVal = m.querySelector('#sh_pass_val'); const passRadios = m.querySelectorAll('input[name="sh_pass_type"]'); const btnGo = m.querySelector('#sh_go'); const validate = () => { const mode = m.querySelector('.pk-s-tab.act').dataset.val; const passType = m.querySelector('input[name="sh_pass_type"]:checked').value; const pass = passVal.value.trim(); let isValid = true; if (mode === 'encrypted' && passType === 'custom') { const reg = /^[a-zA-Z0-9]{4,10}$/; isValid = reg.test(pass); } btnGo.disabled = !isValid; btnGo.style.opacity = isValid ? '1' : '0.4'; btnGo.style.cursor = isValid ? 'pointer' : 'not-allowed'; }; tabs.forEach(t => t.onclick = () => { tabs.forEach(x => x.classList.remove('act')); t.classList.add('act'); const isEnc = t.dataset.val === 'encrypted'; passSec.style.opacity = isEnc ? '1' : '0.3'; passSec.style.pointerEvents = isEnc ? 'auto' : 'none'; validate(); }); passRadios.forEach(r => r.onchange = () => { passVal.disabled = r.value !== 'custom'; if (!passVal.disabled) passVal.focus(); validate(); }); const cntRadios = m.querySelectorAll('input[name="sh_cnt"]'); const cntVal = m.querySelector('#sh_cnt_val'); cntRadios.forEach(r => r.onchange = () => { const isCustom = r.value === 'custom'; cntVal.disabled = !isCustom; if (isCustom) { setTimeout(() => cntVal.focus(), 10); } else { cntVal.value = ''; } validate(); }); cntVal.onfocus = () => { if(!cntVal.disabled) cntBox.style.borderColor = 'var(--pk-pri)'; }; cntVal.onblur = () => { if(cntVal.value === '') cntBox.style.borderColor = 'var(--pk-bd)'; }; cntVal.oninput = () => { cntVal.value = cntVal.value.replace(/[^\d]/g, ''); validate(); }; const modalContainer = m.querySelector('.pk-modal'); if (modalContainer) { modalContainer.style.padding = '0'; modalContainer.style.width = 'fit-content'; } const btnCustomExp = m.querySelector('#sh_exp_custom_btn'); const txtCustom = m.querySelector('#sh_custom_txt'); let customDays = null; m.querySelectorAll('input[name="sh_exp"]').forEach(r => { r.addEventListener('change', (e) => { if (e.target.value !== 'custom') { btnCustomExp.style.borderColor = 'var(--pk-bd)'; btnCustomExp.style.color = 'var(--pk-fg)'; txtCustom.textContent = L.share_custom; customDays = null; } }); }); btnCustomExp.onclick = (e) => { e.stopPropagation(); e.preventDefault(); const existing = document.querySelector('.pk-cal-pop'); if (existing) { existing.remove(); return; } renderCalendar(btnCustomExp, (res) => { const presetRadio = m.querySelector(`input[name="sh_exp"][value="${res.days}"]`); if (presetRadio && res.ts === null) { presetRadio.checked = true; presetRadio.dispatchEvent(new Event('change')); } else { customDays = res.days; const radio = btnCustomExp.querySelector('input'); radio.checked = true; btnCustomExp.style.borderColor = 'var(--pk-pri)'; btnCustomExp.style.color = 'var(--pk-pri)'; if (res.ts) { const d = new Date(res.ts); const dateStr = `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; txtCustom.textContent = dateStr; } else if (res.days === -1) { txtCustom.textContent = L.share_perm; } else { txtCustom.textContent = `${res.days} ${L.share_days}`; } } }); }; passVal.oninput = validate; m.querySelector('#sh_cancel').onclick = () => m.remove(); btnGo.onclick = async () => { if (btnGo.disabled) return; const mode = m.querySelector('.pk-s-tab.act').dataset.val; const cntRadio = m.querySelector('input[name="sh_cnt"]:checked'); let cnt = parseInt(cntRadio.value); if (cntRadio.value === 'custom') { const customInput = m.querySelector('#sh_cnt_val').value.trim(); cnt = (customInput === "") ? -1 : (parseInt(customInput) || 1); } let exp = -1; const expRadio = m.querySelector('input[name="sh_exp"]:checked'); if (expRadio.value === 'custom') { if (customDays === null) { exp = 7; } else { exp = customDays; } } else { exp = parseInt(expRadio.value); } const isCustomPass = m.querySelector('input[name="sh_pass_type"]:checked').value === 'custom'; const pass = isCustomPass ? passVal.value.trim() : ""; if (mode === 'encrypted' && isCustomPass) { if (pass.length < 4 || pass.length > 10) { showAlert(L.err_share_pass); return; } } m.remove(); setLoad(true); updateLoadTxt(L.msg_creating_share); try { const payload = { file_ids: ids, share_to: mode === 'encrypted' ? 'encryptedlink' : 'publiclink', expiration_days: parseInt(exp), pass_code_option: mode === 'encrypted' ? 'REQUIRED' : 'NOT_REQUIRED' }; if (expRadio.value === 'custom' && customDays !== null) { payload.expiration_days = customDays; } if (cnt > 0) payload.limit_count = cnt; const normalizeShareData = raw => (raw && raw.data && typeof raw.data === 'object') ? raw.data : (raw || {}); const getShareErrorMessage = (raw, fallback) => { const src = normalizeShareData(raw); return src.error_description || raw?.error_description || src.error || raw?.error || fallback; }; const ensureShareUrl = (shareData, raw) => { if (!shareData.share_url) throw new Error(getShareErrorMessage(raw, L.err_unknown)); }; const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/share`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(payload) }); if (!res.ok) { const err = await res.json().catch(() => ({})); if ((err.error === 'share_status_prohibited' || err.error_code === 9) && err.error_description) { throw new Error(err.error_description); } throw new Error(getShareErrorMessage(err, `Create API Error ${res.status}`)); } const rawData = await res.json().catch(() => ({})); let data = normalizeShareData(rawData); let finalPassCode = data.pass_code || ''; if (!data.share_id) throw new Error(getShareErrorMessage(rawData, L.err_unknown)); if (cnt > 0) { const store = JSON.parse(gmGet('pk_share_limits', '{}')); store[data.share_id] = cnt; gmSet('pk_share_limits', JSON.stringify(store)); } if (mode === 'encrypted' && isCustomPass && pass) { updateLoadTxt(L.str_saving); const patchPayload = { share_id: data.share_id, pass_code_option: 'REQUIRED', custom_pass_code: pass }; const patchRes = await fetch(`https://api-drive.mypikpak.com/drive/v1/share`, { method: 'PATCH', headers: getHeaders(), body: JSON.stringify(patchPayload) }); if (patchRes.ok) { const patchRaw = await patchRes.json().catch(() => ({})); const patchData = normalizeShareData(patchRaw); data = { ...data, ...patchData }; finalPassCode = data.pass_code || pass; } else { const patchErr = await patchRes.json().catch(() => ({})); console.warn("[Share] Custom password patch failed, using fallback."); const patchMsg = getShareErrorMessage(patchErr, ''); if (!finalPassCode && patchMsg) throw new Error(patchMsg); } } ensureShareUrl(data, rawData); if (mode === 'encrypted' && !finalPassCode) { throw new Error(getShareErrorMessage(data, L.err_unknown)); } const shareUrl = data.share_url; const fullText = shareUrl + (finalPassCode ? ` ${L.lbl_share_code}: ${finalPassCode}` : ''); const resM = showModal(`
${L.title_share_result}
${finalPassCode ? `
${L.lbl_share_code}: ${esc(finalPassCode)}
` : ''}
`); const modalBox = resM.querySelector('.pk-modal'); if (modalBox) { modalBox.style.width = 'auto'; modalBox.style.minWidth = '460px'; modalBox.style.overflow = 'visible'; modalBox.style.padding = '30px'; } resM.querySelector('#res_copy').onclick = () => { GM_setClipboard(fullText); const b = resM.querySelector('#res_copy'); b.textContent = L.msg_copy_success; setTimeout(() => resM.remove(), 1000); }; } catch (e) { showAlert(e.message); } finally { setLoad(false); } }; }; ctx.querySelector('#ctx-copy-name').onclick = () => { ctx.style.display = 'none'; const names = []; const ids = S.getSelectedIds(); ids.forEach(id => { const item = S.itemMap.get(id); if (item && item.name) { const name = item.name; if (item.kind !== 'drive#folder' && name.includes('.') && name.lastIndexOf('.') > 0) { names.push(name.substring(0, name.lastIndexOf('.'))); } else { names.push(name); } } }); if (names.length > 0) { GM_setClipboard(names.join('\n')); showToast(L.msg_copy_success); } }; ctx.querySelector('#ctx-down').onclick = () => { ctx.style.display = 'none'; UI.win.querySelector('#pk-down').click(); }; ctx.querySelector('#ctx-add-bl').onclick = (e) => { ctx.style.display = 'none'; const action = e.currentTarget.getAttribute('data-action'); processBlacklistAction(action); }; ctx.querySelector('#ctx-copy').onclick = () => { ctx.style.display = 'none'; UI.btnCopy.click(); }; ctx.querySelector('#ctx-cut').onclick = () => { ctx.style.display = 'none'; UI.btnCut.click(); }; ctx.querySelector('#ctx-prune').onclick = () => { ctx.style.display = 'none'; UI.btnPrune.click(); }; ctx.querySelector('#ctx-rename').onclick = () => { ctx.style.display = 'none'; if (S.getSelectedCount() > 1) { UI.btnBulkRename.click(); } else { UI.btnRename.click(); } }; const ctxDel = ctx.querySelector('#ctx-del'); ctxDel.onclick = () => { ctx.style.display = 'none'; UI.btnDel.click(); }; if (S.historyMode) { const ctxDelTxt = ctxDel.childNodes[1]; if (ctxDelTxt) ctxDelTxt.textContent = " " + L.btn_clear_history; } const ctxShCancel = ctx.querySelector('#ctx-sh-cancel'); if (ctxShCancel) { ctxShCancel.onclick = () => { ctx.style.display = 'none'; if (UI.btnCancelShare) UI.btnCancelShare.click(); }; } const ctxShDetail = ctx.querySelector('#ctx-sh-detail'); if (ctxShDetail) { ctxShDetail.onclick = () => { ctx.style.display = 'none'; const id = S.getSelectedIds()[0]; const item = S.itemMap.get(id); if (item) showShareDetail(item); }; } const ctxShCopy = ctx.querySelector('#ctx-sh-copy'); if (ctxShCopy) { ctxShCopy.onclick = () => { ctx.style.display = 'none'; const id = S.getSelectedIds()[0]; const item = S.itemMap.get(id); if (!item) return; const url = item.share_url || ""; const pwd = item.pass_code || ""; const title = item.name || item.title || ""; let text = url + '\n'; if (pwd) text += `${L.share_copy_pwd}: ${pwd}\n`; text += `${title}\n${L.share_copy_suffix}`; GM_setClipboard(text); showToast(L.msg_copy_success); }; } const ctxRestore = ctx.querySelector('#ctx-restore'); if (ctxRestore) { ctxRestore.onclick = () => { ctx.style.display = 'none'; UI.btnRestore.click(); }; } const ctxDelForever = ctx.querySelector('#ctx-del-forever'); if (ctxDelForever) { ctxDelForever.onclick = () => { ctx.style.display = 'none'; UI.btnDelForever.click(); }; } updateStat(); const restoreUIState = () => { const navs =[UI.btnNavHome, UI.btnNavTrash, UI.btnNavShare, UI.btnNavShareParse, UI.btnNavStarred, UI.btnNavRecent, UI.btnNavHistory, UI.btnNavOffline, UI.btnNavUpload]; navs.forEach(n => { if(n) n.classList.remove('act'); }); const stdBtns =[UI.btnNewFolder, UI.btnDel, UI.btnCopy, UI.btnCut, UI.btnPaste, UI.btnRename, UI.btnBulkRename, UI.btnPrune, UI.btnUnzip, UI.btnMigrate, UI.btnBlacklistManager]; const shareBtns =[UI.btnCancelShare]; const upBtns =[UI.btnUpPause, UI.btnUpStart, UI.btnUpDel, UI.btnUpClearAll]; const upSep = el.querySelector('#pk-up-sep'); const downBtns = [UI.btnAria2, UI.btnDown, UI.btnExt, UI.btnExportM3U, UI.btnImgSearch]; upBtns.forEach(b => { if(b) b.style.display = 'none'; }); if(upSep) upSep.style.display = 'none'; if (UI.upTip) UI.upTip.style.display = 'none'; if (UI.btnClearHistoryAll) UI.btnClearHistoryAll.style.display = 'none'; if (UI.btnShareParseSave) UI.btnShareParseSave.style.display = 'none'; if (UI.btnShareParseInsight) UI.btnShareParseInsight.style.display = 'none'; if (UI.btnShareParseStopScan) UI.btnShareParseStopScan.style.display = 'none'; if (UI.btnShareParseBackList) UI.btnShareParseBackList.style.display = 'none'; if (UI.actionBar) UI.actionBar.querySelectorAll('.pk-sep').forEach(sep => { sep.style.display = ''; }); if (UI.btnRefresh) { UI.btnRefresh.style.display = 'inline-flex'; UI.btnRefresh.disabled = false; } if (UI.btnBlacklistManager) UI.btnBlacklistManager.disabled = false; const mainHeader = UI.win ? UI.win.querySelector('.pk-grid-hd') : null; if (mainHeader) { mainHeader.style.display = ''; mainHeader.style.visibility = ''; } if (S.trashMode) { UI.win.classList.add('pk-mode-trash'); if(UI.btnNavTrash) UI.btnNavTrash.classList.add('act'); if(UI.actionBar) UI.actionBar.style.display = 'none'; if(UI.trashBar) UI.trashBar.style.display = 'flex'; if(UI.bottomGrp) UI.bottomGrp.style.display = 'none'; if (S.path[0]) S.path[0].name = L.trash_title; } else if (S.shareMode) { if(UI.btnNavShare) UI.btnNavShare.classList.add('act'); if(UI.bottomGrp) UI.bottomGrp.style.display = 'none'; if (S.path[0]) S.path[0].name = L.btn_nav_share; stdBtns.forEach(b => { if(b) b.style.display = 'none'; }); shareBtns.forEach(b => { if(b) b.style.display = 'inline-flex'; }); } else if (S.shareParseMode) { if(UI.btnNavShareParse) UI.btnNavShareParse.classList.add('act'); S.path = [{ id: 'share_parse_root', name: L.title_share_parse }]; if(UI.bottomGrp) UI.bottomGrp.style.display = 'none'; if(UI.actionBar) { UI.actionBar.style.display = S.shareParseListActive ? 'flex' : 'none'; UI.actionBar.querySelectorAll('.pk-sep').forEach(sep => { sep.style.display = 'none'; }); } if(UI.trashBar) UI.trashBar.style.display = 'none'; stdBtns.forEach(b => { if(b) b.style.display = 'none'; }); shareBtns.forEach(b => { if(b) b.style.display = 'none'; }); upBtns.forEach(b => { if(b) b.style.display = 'none'; }); [UI.btnRetryTask, UI.btnCopyLinkOffline].forEach(b => { if(b) b.style.display = 'none'; }); downBtns.forEach(b => { if(b) b.style.display = 'none'; }); if(UI.btnRefresh) UI.btnRefresh.style.display = 'none'; if(UI.uploadWrap) UI.uploadWrap.style.display = 'none'; if(UI.lblGlobal) UI.lblGlobal.style.display = 'none'; if(UI.chkGlobal) UI.chkGlobal.checked = false; if(UI.btnAnalyze) UI.btnAnalyze.style.display = 'none'; if(UI.scan) UI.scan.style.display = 'none'; if(UI.cntFolderFirst) UI.cntFolderFirst.style.display = 'none'; if(UI.dupTools) UI.dupTools.style.display = 'none'; if(UI.dupFilters) UI.dupFilters.style.display = 'none'; if(UI.offTools) UI.offTools.style.display = 'none'; if(UI.upTools) UI.upTools.style.display = 'none'; if(UI.filterBar) UI.filterBar.style.display = 'none'; setSearchWrapVisible(!!S.shareParseListActive); if(UI.searchClear) UI.searchClear.style.display = (S.shareParseListActive && S.search) ? 'flex' : 'none'; if(UI.lblSearchPath) UI.lblSearchPath.style.display = (S.shareParseListActive && S.shareParseInsightMode) ? 'flex' : 'none'; if(UI.stat) UI.stat.style.display = S.shareParseListActive ? '' : 'none'; updateShareParseSaveButton(); updateShareParseInsightButtons(); } else if (S.offlineMode) { if(UI.btnNavOffline) UI.btnNavOffline.classList.add('act'); if (S.path[0]) S.path[0].name = L.title_offline; if(UI.bottomGrp) UI.bottomGrp.style.display = 'flex';[UI.btnNewFolder, UI.btnCopy, UI.btnCut, UI.btnPaste, UI.btnRename, UI.btnBulkRename, UI.btnPrune, UI.btnUnzip, UI.btnMigrate].forEach(b => { if(b) b.style.display = 'none'; }); [UI.btnDel, UI.btnRefresh, UI.btnBlacklistManager].forEach(b => { if(b) b.style.display = 'inline-flex'; }); shareBtns.forEach(b => { if(b) b.style.display = 'none'; }); [UI.btnAria2, UI.btnDown].forEach(b => { if(b) b.style.display = 'none'; }); if(UI.btnExt) UI.btnExt.style.display = 'inline-flex'; if(UI.btnExportM3U) UI.btnExportM3U.style.display = 'inline-flex'; if(UI.btnImgSearch) UI.btnImgSearch.style.display = 'inline-flex'; } else if (S.uploadMode) { if(UI.btnNavUpload) UI.btnNavUpload.classList.add('act'); if(UI.bottomGrp) UI.bottomGrp.style.display = 'flex'; if (S.path[0]) S.path[0].name = L.btn_nav_upload; [UI.btnAria2, UI.btnDown].forEach(b => { if(b) b.style.display = 'none'; }); if(UI.btnExt) UI.btnExt.style.display = 'inline-flex'; if(UI.btnExportM3U) UI.btnExportM3U.style.display = 'inline-flex'; if(UI.btnImgSearch) UI.btnImgSearch.style.display = 'inline-flex'; stdBtns.forEach(b => { if(b) b.style.display = 'none'; }); if (UI.btnRefresh) UI.btnRefresh.style.display = 'none'; shareBtns.forEach(b => { if(b) b.style.display = 'none'; }); upBtns.forEach(b => { if(b) b.style.display = 'inline-flex'; }); if(upSep) upSep.style.display = 'block'; if(UI.uploadWrap) UI.uploadWrap.style.display = 'none'; if(UI.upTip) UI.upTip.style.display = 'flex'; } else if (S.historyMode) { if(UI.btnNavHistory) UI.btnNavHistory.classList.add('act'); if (S.path[0]) S.path[0].name = L.btn_nav_history; if(UI.bottomGrp) UI.bottomGrp.style.display = 'flex'; stdBtns.forEach(b => { if(b && b !== UI.btnBlacklistManager && b !== UI.btnMigrate) b.style.display = 'none'; }); if (UI.btnClearHistoryAll) UI.btnClearHistoryAll.style.display = 'inline-flex'; if (UI.btnBlacklistManager) UI.btnBlacklistManager.style.display = 'inline-flex'; if (UI.btnMigrate) UI.btnMigrate.style.display = 'inline-flex'; shareBtns.forEach(b => { if(b) b.style.display = 'none'; }); if(UI.uploadWrap) UI.uploadWrap.style.display = 'none'; downBtns.forEach(b => { if(b) b.style.display = 'inline-flex'; }); } else if (S.recentMode || S.starredMode) { if (S.recentMode && UI.btnNavRecent) UI.btnNavRecent.classList.add('act'); if (S.starredMode && UI.btnNavStarred) UI.btnNavStarred.classList.add('act'); if (S.recentMode && S.path[0]) S.path[0].name = L.btn_nav_recent; if (S.starredMode && S.path[0]) S.path[0].name = L.btn_nav_starred; if(UI.bottomGrp) UI.bottomGrp.style.display = 'flex'; stdBtns.forEach(b => { if(b) b.style.display = 'inline-flex'; }); shareBtns.forEach(b => { if(b) b.style.display = 'none'; }); if(UI.uploadWrap) UI.uploadWrap.style.display = 'none'; downBtns.forEach(b => { if(b) b.style.display = 'inline-flex'; }); } else { if(UI.btnNavHome) UI.btnNavHome.classList.add('act'); if (S.path.length > 0 && S.path[0].id === '') S.path[0].name = L.btn_nav_home; if(UI.bottomGrp) UI.bottomGrp.style.display = 'flex'; stdBtns.forEach(b => { if(b) b.style.display = 'inline-flex'; }); shareBtns.forEach(b => { if(b) b.style.display = 'none'; }); if(UI.uploadWrap) UI.uploadWrap.style.display = 'inline-flex'; downBtns.forEach(b => { if(b) b.style.display = 'inline-flex'; }); } if(UI.topBar) UI.topBar.style.display = 'flex'; if(UI.crumb) { UI.crumb.style.opacity = '1'; UI.crumb.style.display = 'flex'; } syncLocalUploadVisibility(); refresh(); }; restoreUIState(); load(); window.pkForceManagerReloadAfterAuth = (() => { let timer = null; let reloadSeq = 0; let authReloading = false; let pendingRelayout = false; const flushDeferredRelayout = () => { if (!pendingRelayout) return; pendingRelayout = false; if (!el || el.style.display === 'none') return; requestAnimationFrame(() => { if (!el || el.style.display === 'none') return; if (typeof syncLayoutMetrics === 'function') syncLayoutMetrics(); if (isGridView() && typeof scheduleGridRelayout === 'function') { scheduleGridRelayout(true); } else if (typeof renderList === 'function') { renderList(); } else if (typeof renderVisible === 'function') { if (UI.in) UI.in.style.height = `${S.display.length * CONF.rowHeight}px`; renderVisible(); } }); }; const finishAuthReload = (seq) => { if (seq !== reloadSeq) return; authReloading = false; requestAnimationFrame(flushDeferredRelayout); }; window.pkIsAuthManagerReloading = () => authReloading; window.pkDeferAuthManagerRelayout = () => { pendingRelayout = true; }; return (reason = 'auth-recovered') => { if (timer) clearTimeout(timer); timer = setTimeout(() => { timer = null; try { const ov = document.querySelector('.pk-ov'); if (!ov || ov.style.display === 'none') return; if (!S || !Array.isArray(S.path) || S.path.length === 0) return; if (S.shareParseMode) return; const curNode = S.path[S.path.length - 1] || { id: '' }; const folderId = curNode.id || 'root'; const cacheKey = S.getRealCacheKey ? S.getRealCacheKey(folderId) : folderId; const authReloadKey = (!curNode.id || curNode.id === 'root') ? 'root' : String(curNode.id); const lastOk = window.__pkLastManagerLoadOk; if (lastOk && lastOk.key === authReloadKey && Date.now() - lastOk.at < 3500) { pendingRelayout = true; requestAnimationFrame(flushDeferredRelayout); return; } if (typeof globalNeedsSync !== 'undefined') globalNeedsSync = true; if (typeof globalCache !== 'undefined') { globalCache.delete(cacheKey); if (folderId === 'root') { globalCache.delete(''); globalCache.delete('root'); } } if (S.cache) { S.cache.delete(cacheKey); if (folderId === 'root') { S.cache.delete(''); S.cache.delete('root'); } } if (typeof globalDirtyFolders !== 'undefined') { globalDirtyFolders.add(folderId === 'root' ? '' : folderId); if (folderId === 'root') globalDirtyFolders.add('root'); } if (typeof scannedFolderIds !== 'undefined') { scannedFolderIds.delete(folderId === 'root' ? '' : folderId); } const seq = ++reloadSeq; authReloading = true; pendingRelayout = false; let reloadTask = Promise.resolve(); if (typeof load === 'function') { reloadTask = Promise.resolve(load(false, true)).catch(e => console.warn('[Auth Sync] Forced reload failed:', e)); } else if (typeof window.pkSmartRefreshTrigger === 'function') { window.pkSmartRefreshTrigger(true); } else if (typeof refresh === 'function') { refresh(); } if (typeof runBackgroundCrawler === 'function' && typeof isBackgroundRunning !== 'undefined' && !isBackgroundRunning) { runBackgroundCrawler(); } reloadTask.finally(() => finishAuthReload(seq)); } catch (e) { authReloading = false; requestAnimationFrame(flushDeferredRelayout); console.warn('[Auth Sync] Force reload error:', e); } }, 260); }; })(); const startSmartRefresh = () => { if (UI.win.dataset.autoRefresh) return null; UI.win.dataset.autoRefresh = "true"; let silentAbortController = null; let retryTimer = null; let lastUserInteractionTime = 0; const updateInteractionTime = () => { lastUserInteractionTime = Date.now(); }; el.addEventListener('mousedown', updateInteractionTime, true); el.addEventListener('keydown', updateInteractionTime, true); const diffWorkerBlob = new Blob([` self.onmessage = function(e) { const { oldList, newList } = e.data; if (oldList.length !== newList.length) { self.postMessage(true); return; } for (let i = 0; i < newList.length; i++) { const a = oldList[i]; const b = newList[i]; if (a.id !== b.id || a.modified_time !== b.modified_time || a.size !== b.size || a.hash !== b.hash || a.starred !== b.starred) { self.postMessage(true); return; } } self.postMessage(false); }; `], { type: 'application/javascript' }); const diffWorker = new Worker(URL.createObjectURL(diffWorkerBlob)); const runWatchdogAudit = async () => { if (document.hidden) return; if (S.shareParseMode) return; try { const list = await apiShareList(); const toAutoCancel = list.filter(it => it.kind === 'pikpak#share' && it.limit_count > 0 && it.save_count >= it.limit_count && it.share_status === 'OK'); if (toAutoCancel.length > 0) { const cancelIds = toAutoCancel.map(x => x.id); const expiredGraveyard = JSON.parse(gmGet('pk_expired_shares', '[]')); toAutoCancel.forEach(it => { it.share_status = 'EXPIRED'; it._is_local_phantom = true; expiredGraveyard.push(it); }); if (expiredGraveyard.length > 50) expiredGraveyard.splice(0, expiredGraveyard.length - 50); gmSet('pk_expired_shares', JSON.stringify(expiredGraveyard)); await apiCancelShare(cancelIds).catch(() => {}); const store = JSON.parse(gmGet('pk_share_limits', '{}')); cancelIds.forEach(id => delete store[id]); gmSet('pk_share_limits', JSON.stringify(store)); if (S.shareMode && window.pkSmartRefreshTrigger) window.pkSmartRefreshTrigger(true); } } catch (e) { console.warn("[Watchdog] Audit failed", e); } }; const checkAndRefresh = async (isRetry = false, bypassLock = false) => { if (document.hidden) return; const recentSession = globalCache.get('recent_session'); const recentCached = globalCache.get('recent_root'); const recentPaginationPending = !!(S.recentMode && ((!recentSession || !recentSession.completed) || (recentCached && !Array.isArray(recentCached) && recentCached.nextToken))); const historySession = globalCache.get('history_session'); const historyCached = globalCache.get('history_root'); const historyPaginationPending = !!(S.historyMode && ((!historySession || !historySession.completed) || (historyCached && !Array.isArray(historyCached) && historyCached.nextToken))); if (recentPaginationPending) { return; } if (historyPaginationPending) { return; } const isTyping = ['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName); const hasUIState = S.getSelectedCount() > 0 || (UI.ctx && UI.ctx.style.display === 'block'); const isInteracting = !bypassLock && (Date.now() - lastUserInteractionTime < 1500); const isBusy = S._isEmptyingTrash || S.loading || S.scanning || S.dupMode || S.isFlattened || hasUIState || isTyping || isInteracting; if (isBusy) { if (retryTimer) clearTimeout(retryTimer); retryTimer = setTimeout(() => checkAndRefresh(true, bypassLock), 2500); return; } if (S.historyMode || S.uploadMode || S.shareParseMode) return; const cur = S.path[S.path.length - 1]; const isStarredRoot = S.starredMode && S.path.length === 1; const isRecentRoot = S.recentMode && S.path.length === 1 && cur && cur.id === 'recent_root'; if (isRecentRoot) { return; } const cacheKey = S.shareMode ? 'share_root' : (isStarredRoot ? 'starred_root' : (S.getRealCacheKey ? S.getRealCacheKey(cur.id || 'root') : (cur.id || 'root'))); if (cur.id === 'virtual_search_root' || cur.id === 'analyze_root' || cur.id === 'upload_root' || cur.id === 'share_parse_root') return; if (silentAbortController) silentAbortController.abort(); silentAbortController = new AbortController(); const signal = silentAbortController.signal; try { const runWatchdogAudit = async (targetList) => { const toAutoCancel = targetList.filter(it => it.kind === 'pikpak#share' && it.limit_count > 0 && it.save_count >= it.limit_count && it.share_status === 'OK'); if (toAutoCancel.length > 0) { const cancelIds = toAutoCancel.map(x => x.id); const expiredGraveyard = JSON.parse(gmGet('pk_expired_shares', '[]')); toAutoCancel.forEach(it => { it.share_status = 'EXPIRED'; it._is_local_phantom = true; expiredGraveyard.push(it); }); if (expiredGraveyard.length > 50) expiredGraveyard.splice(0, expiredGraveyard.length - 50); gmSet('pk_expired_shares', JSON.stringify(expiredGraveyard)); await apiCancelShare(cancelIds).catch(() => {}); const store = JSON.parse(gmGet('pk_share_limits', '{}')); cancelIds.forEach(id => delete store[id]); gmSet('pk_share_limits', JSON.stringify(store)); return true; } return false; }; let allFetchedItems = []; if (!S.shareMode) { apiShareList().then(list => runWatchdogAudit(list)); } if (S.shareMode) { allFetchedItems = await apiShareList(); await runWatchdogAudit(allFetchedItems); } else if (S.offlineMode) { const rawTasks =[]; await apiTaskList(1000, (batch) => { if (batch && batch.length) rawTasks.push(...batch); }); allFetchedItems = rawTasks.map(t => { const ref = t.reference_resource || {}; return { id: t.id, kind: 'drive#task', name: ref.name || t.name || t.file_name || 'Untitled Task', size: t.file_size, phase: t.phase, progress: parseInt(t.progress || 0), message: t.message, icon_link: t.icon_link, thumbnail_link: ref.thumbnail_link ? ref.thumbnail_link : t.icon_link, created_time: t.created_time, modified_time: t.updated_time || ref.modified_time || '', file_id: t.file_id || '', source_url: (t.params && t.params.url) ? t.params.url : '', params: Object.assign({}, t.params || {}, ref.params || {}), mime_type: ref.mime_type || '', starred: !!(ref.starred || (ref.tags && ref.tags.some(tg => tg.name === 'STAR'))) }; }); } else if (S.historyMode) { return; } else if (isRecentRoot) { let nextToken = null; const limit = 500; do { if (document.hidden || signal.aborted || S.loading || S.scanning || S.dupMode || S.isFlattened || S.getSelectedCount() > 0) return; const filters = encodeURIComponent('{"phase":{"in":"PHASE_TYPE_COMPLETE"}}'); const url = `https://api-drive.mypikpak.com/drive/v1/tasks?limit=${limit}&filters=${filters}&thumbnail_size=SIZE_MEDIUM&with_reference_resource=true&_t=${Date.now()}${nextToken ? `&page_token=${nextToken}` : ''}`; const netPriority = bypassLock ? 'high' : 'low'; const res = await fetch(url, { headers: getHeaders(), signal: signal, priority: netPriority }); if (!res.ok) { if (res.status === 401 || res.status === 403) { console.warn("[Recent] SWR fetch auth rejected."); const didLogout = await confirmedLogout('recent-swr-fetch-auth', 5000, 5200); if (didLogout) return; return; } throw new Error("Recent SWR fetch error"); } const json = await res.json(); const validTasks = (json.tasks ||[]).filter(t => t.phase === 'PHASE_TYPE_COMPLETE' && (t.type === 'offline' || t.type === 'upload') && t.file_id !== "" ); const mapped = validTasks.map(t => { const ref = t.reference_resource || {}; const mime = ref.mime_type || ''; const isFolder = (ref.kind === 'drive#folder') || (mime === 'application/x-directory') || (t.icon_link && t.icon_link.includes('folder')); return { id: t.file_id || t.id, kind: isFolder ? 'drive#folder' : 'drive#file', name: ref.name || t.file_name || t.name, size: t.file_size, thumbnail_link: ref.thumbnail_link || t.icon_link || '', icon_link: t.icon_link || '', web_content_link: t.file_id ? null : null, created_time: t.created_time, modified_time: t.updated_time || ref.modified_time || t.created_time, mime_type: mime, parent_id: '', starred: !!(ref.starred || (ref.tags && ref.tags.some(tg => tg.name === 'STAR'))), trashed: false, params: Object.assign({}, t.params || {}, ref.params || {}), _sourceTaskId: t.id }; }); allFetchedItems.push(...mapped); nextToken = json.next_page_token; if (allFetchedItems.length >= 2000) break; } while (nextToken); const seen = new Set(); allFetchedItems = allFetchedItems.filter(f => { if (seen.has(f.id)) return false; seen.add(f.id); return true; }); } else { let nextToken = null; const limit = 500; const now = Date.now(); do { if (document.hidden || signal.aborted || S.loading || S.scanning || S.dupMode || S.isFlattened || S.getSelectedCount() > 0) return; const currentIsStarredRoot = S.starredMode && S.path.length === 1; const targetParentId = (S.trashMode || currentIsStarredRoot) ? '*' : (cur.id || ''); const filterObj = { "trashed": { "eq": S.trashMode } }; if (currentIsStarredRoot) { filterObj.trashed = { "eq": false }; filterObj.system_tag = { "in": "STAR" }; } else if (!S.trashMode) { filterObj.phase = { "eq": "PHASE_TYPE_COMPLETE" }; } const filters = `&filters=${encodeURIComponent(JSON.stringify(filterObj))}`; const url = `https://api-drive.mypikpak.com/drive/v1/files?thumbnail_size=SIZE_MEDIUM&limit=${limit}${filters}&parent_id=${targetParentId}&_t=${now}${nextToken ? `&page_token=${nextToken}` : ''}`; const netPriority = bypassLock ? 'high' : 'low'; const res = await fetch(url, { headers: getHeaders(), signal: signal, priority: netPriority }); if (!res.ok) { if (res.status === 401 || res.status === 403) { console.warn("[Recent] Silent fetch auth rejected."); const didLogout = await confirmedLogout('recent-silent-fetch-auth', 5000, 5200); if (didLogout) return; return; } throw new Error("Silent fetch error"); } const data = await res.json(); if (data.files) { allFetchedItems.push(...data.files.map(f => minifyFile(f, true))); } nextToken = data.next_page_token; } while (nextToken); } if (S.analyzeMode && S.analyzeMap) { allFetchedItems.forEach(item => { if (item.kind === 'drive#folder' && S.analyzeMap.has(item.id)) { item.size = S.analyzeMap.get(item.id).size.toString(); } }); } if (document.hidden || signal.aborted || S.loading || S.scanning || S.dupMode || S.isFlattened || S.getSelectedCount() > 0) return; const nowCur = S.path[S.path.length - 1]; const nowStarredRoot = S.starredMode && S.path.length === 1; let currentExpectedKey = 'root'; if (S.shareMode) currentExpectedKey = 'share_root'; else if (S.offlineMode) currentExpectedKey = 'offline_root'; else if (nowStarredRoot) currentExpectedKey = 'starred_root'; else currentExpectedKey = S.getRealCacheKey ? S.getRealCacheKey(nowCur.id || 'root') : (nowCur.id || 'root'); if (currentExpectedKey === cacheKey) { diffWorker.onmessage = (e) => { const hasChanges = e.data; if (!hasChanges) { return; } if ((!bypassLock && Date.now() - lastUserInteractionTime < 1500) || S.getSelectedCount() > 0) { if (retryTimer) clearTimeout(retryTimer); retryTimer = setTimeout(() => checkAndRefresh(true, bypassLock), 2000); return; } if (allFetchedItems.length > 0) { const sample = allFetchedItems[0]; if (!S.shareMode) { if (S.trashMode && !sample.trashed) { console.warn("[SmartRefresh] Dirty data blocked: Home data trying to enter Trash view."); return; } else if (!S.trashMode && sample.trashed) { console.warn("[SmartRefresh] Dirty data blocked: Trash data trying to enter Home view."); return; } } } S.cache.set(cacheKey, allFetchedItems); if (typeof globalCache !== 'undefined') globalCache.set(cacheKey, allFetchedItems); const newItemMap = new Map(); const newStarredSet = new Set(); for (let i = 0; i < allFetchedItems.length; i++) { const item = allFetchedItems[i]; newItemMap.set(item.id, item); if (item.starred || (item.tags && item.tags.some(t => t.name === 'STAR'))) { newStarredSet.add(item.id); } } requestAnimationFrame(() => { if (S.getSelectedCount() > 0 || S.loading || S.scanning) return; const scrollTop = UI.vp ? UI.vp.scrollTop : 0; const sameOrder = S.items.length === allFetchedItems.length && S.items.every((oldItem, idx) => oldItem && allFetchedItems[idx] && oldItem.id === allFetchedItems[idx].id); const canPatchVisible = sameOrder && !S.search && !S.dupMode && !S.isFlattened && !S.analyzeMode; S.items.splice(0, S.items.length, ...allFetchedItems); S.itemMap = newItemMap; S.starredSet = newStarredSet; if (canPatchVisible) { const updateDisplayItem = (item) => item && !item.isHeader && item.id ? (newItemMap.get(item.id) || item) : item; S.display = Array.isArray(S.display) ? S.display.map(updateDisplayItem) : []; if (typeof renderVisible === 'function') renderVisible(); if (typeof updateStat === 'function') updateStat(); if (UI.vp) UI.vp.scrollTop = scrollTop; return; } refresh(); if (UI.vp) UI.vp.scrollTop = scrollTop; }); }; const simplify = (list) => list.map(x => { let diffHash = x.hash; if (x.kind === 'drive#task') { diffHash = `${x.phase}_${x.progress}_${x.params?.global_file_kind || ''}_${x.hash || ''}`; } else if (x.params?.global_file_kind) { diffHash = `${x.params.global_file_kind}_${x.hash || ''}`; } return { id: x.id, modified_time: x.modified_time, size: x.size, hash: diffHash, starred: !!(x.starred || (x.tags && x.tags.some(t => t.name === 'STAR'))) }; }); if (S.shareMode || cacheKey === 'share_root') { const toAutoCancel = allFetchedItems.filter(it => it.kind === 'pikpak#share' && it.limit_count > 0 && it.save_count >= it.limit_count && it.share_status === 'OK'); if (toAutoCancel.length > 0) { const cancelIds = toAutoCancel.map(x => x.id); const expiredGraveyard = JSON.parse(gmGet('pk_expired_shares', '[]')); toAutoCancel.forEach(it => { it.share_status = 'EXPIRED'; it._is_local_phantom = true; expiredGraveyard.push(it); }); if (expiredGraveyard.length > 50) expiredGraveyard.splice(0, expiredGraveyard.length - 50); gmSet('pk_expired_shares', JSON.stringify(expiredGraveyard)); await apiCancelShare(cancelIds).catch(() => {}); const store = JSON.parse(gmGet('pk_share_limits', '{}')); cancelIds.forEach(id => delete store[id]); gmSet('pk_share_limits', JSON.stringify(store)); allFetchedItems = allFetchedItems.filter(it => !cancelIds.includes(it.id)); } } diffWorker.postMessage({ oldList: simplify(S.items), newList: simplify(allFetchedItems) }); } } catch (e) { } }; window.pkSmartRefreshTrigger = (isForce = false) => { if (S.shareParseMode) return; const session = globalCache.get('offline_session'); if (S.offlineMode && (!session || !session.completed) && !isForce) { return; } const cur = S.path[S.path.length - 1]; const isRecentRoot = S.recentMode && S.path.length === 1 && cur && cur.id === 'recent_root'; if (isRecentRoot) { return; } checkAndRefresh(false, isForce); }; const onVisibilityChange = () => { if (retryTimer) clearTimeout(retryTimer); checkAndRefresh(false, false); runWatchdogAudit(); }; const uiSyncTimer = setInterval(() => checkAndRefresh(false, true), 60000); const watchdogTimer = setInterval(runWatchdogAudit, 60000); document.addEventListener('visibilitychange', onVisibilityChange); return { handler: onVisibilityChange, abort: () => { if(silentAbortController) silentAbortController.abort(); if(retryTimer) clearTimeout(retryTimer); if(uiSyncTimer) clearInterval(uiSyncTimer); if(watchdogTimer) clearInterval(watchdogTimer); document.removeEventListener('visibilitychange', onVisibilityChange); el.removeEventListener('mousedown', updateInteractionTime, true); el.removeEventListener('keydown', updateInteractionTime, true); if (diffWorker) diffWorker.terminate(); delete window.pkSmartRefreshTrigger; } }; }; const visibilityListener = startSmartRefresh(); function handleClose() { let safePath = [...S.path]; if (safePath.some(n => n.id === 'virtual_search_root' || n.id === 'analyze_root')) { safePath = S.preSearchPath || [{ id: '', name: L.btn_nav_home }]; } globalSavedState = { path: safePath, trashMode: S.trashMode, shareParseMode: S.shareParseMode, isMaximized: UI.win.classList.contains('pk-maximized') }; if (S.offlineMode && S.items.length > 0) { const cacheKey = 'offline_root'; const cacheData = { items: [...S.items], nextToken: null }; if (typeof globalCache !== 'undefined') globalCache.set(cacheKey, cacheData); } pkState = null; delete window.pkUpdateCrawlerUI; if (S && S.broadcast) S.broadcast.close(); if (visibilityListener && visibilityListener.abort) { visibilityListener.abort(); } closeTransientDropdowns(true); if (typeof destroyTooltip === 'function') destroyTooltip(); if (pkWinHideObserver) pkWinHideObserver.disconnect(); el.remove(); document.removeEventListener('keydown', keyHandler); document.removeEventListener('mouseup', mouseHandler); if (window._pkMouseSideNavHandler) { window.removeEventListener('mousedown', window._pkMouseSideNavHandler, true); window.removeEventListener('mouseup', window._pkMouseSideNavHandler, true); window.removeEventListener('auxclick', window._pkMouseSideNavHandler, true); window.removeEventListener('click', window._pkMouseSideNavHandler, true); } document.body.style.overflow = ''; document.documentElement.style.overflow = ''; } UI.btnClose.addEventListener('click', () => { document.body.classList.remove('pk-body-max'); el.style.display = 'none'; }); updateCrawlerUI(); setTimeout(() => runScriptUpdateCheck(), 1800); S.updateBlCache(); if (S.items && S.items.length > 0) { S.itemMap.clear(); S.items.forEach(i => S.itemMap.set(i.id, i)); } async function syncGlobalStarredStatus() { let nextToken = null; try { const latestStarredIds = new Set(); do { const filter = encodeURIComponent('{"starred":{"eq":true},"trashed":{"eq":false}}'); const url = `https://api-drive.mypikpak.com/drive/v1/files?filters=${filter}&limit=1000${nextToken ? `&page_token=${nextToken}` : ''}`; const res = await fetch(url, { headers: getHeaders() }); if (!res.ok) break; const data = await res.json(); if (data.files) { data.files.forEach(f => latestStarredIds.add(f.id)); } nextToken = data.next_page_token; } while (nextToken); S.pendingMap.forEach((targetStatus, id) => { if (targetStatus) { latestStarredIds.add(id); } else { latestStarredIds.delete(id); } }); if (latestStarredIds.size > 0 || S.items.length > 0) { S.starredSet = latestStarredIds; if (typeof renderVisible === 'function') renderVisible(); } } catch (e) {} } const fmtTransferQuotaSize = (v) => { let n = Number(v || 0); if (!Number.isFinite(n) || n <= 0) return '0.00 KB'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let i = 0; while (n >= 1024 && i < units.length - 1) { n /= 1024; i++; } const truncated = Math.floor((n + Number.EPSILON) * 100) / 100; return `${truncated.toFixed(2)} ${units[i]}`; }; const buildTransferQuotaRowHtml = (label, quota, extraInfo = '') => { const total = Number((quota && quota.total_assets) || 0); const used = Number((quota && (quota.assets || quota.size)) || 0); const pct = total > 0 ? Math.max(0, Math.min(100, (used / total) * 100)) : 0; return `
${label}
${fmtTransferQuotaSize(total)}
${L.transfer_quota_used_total.replace('{u}', fmtTransferQuotaSize(used)).replace('{t}', fmtTransferQuotaSize(total))} ${extraInfo || ''}
`; }; const showTransferQuotaDetail = async () => { const existingModal = Array.from(document.querySelectorAll('.pk-modal-ov')).find(m => { const title = m.querySelector('h3'); return title && title.textContent === L.modal_transfer_quota_title; }); if (existingModal) return; const triggerBtn = UI && UI.btnTransferQuotaDetail; if (triggerBtn && triggerBtn.dataset.pkOpening === '1') return; if (triggerBtn) triggerBtn.dataset.pkOpening = '1'; try { const res = await fetch(`https://api-drive.mypikpak.com/vip/v1/quantity/list?type=transfer&limit=200&_t=${Date.now()}`, { headers: getHeaders() }); if (!res.ok) { if (res.status === 401 || res.status === 403) { const didLogout = await confirmedLogout('transfer-quota-auth-rejected', 5000, 5200); if (didLogout) return; } throw new Error(`HTTP ${res.status}`); } const data = await res.json(); const base = data && data.base; if (!base) throw new Error('base missing'); const isTransferQuotaNonVip = base.sub_status === false || String(base.vip_status || '').toLowerCase() !== 'valid'; const hasTransferQuotaDailyDownload = base.download_daily && Number(base.download_daily.total_assets || 0) > 0; const m = showModal(`

${L.modal_transfer_quota_title}

${L.transfer_quota_desc}
${L.transfer_quota_monthly}
${buildTransferQuotaRowHtml(L.transfer_quota_cloud_download, base.offline)} ${buildTransferQuotaRowHtml(L.transfer_quota_download, base.download, L.transfer_quota_download_note)} ${buildTransferQuotaRowHtml(L.transfer_quota_upload, base.upload)} ${isTransferQuotaNonVip && hasTransferQuotaDailyDownload ? buildTransferQuotaRowHtml(L.transfer_quota_daily_non_vip, base.download_daily) : ''}
`); const modalBox = m.querySelector('.pk-modal'); if (modalBox) Object.assign(modalBox.style, { width: '760px', maxWidth: '92vw', padding: '28px 30px 44px', boxSizing: 'border-box' }); const closeBtn = m.querySelector('.pk-modal-close'); if (closeBtn) Object.assign(closeBtn.style, { top: '24px', right: '24px' }); } catch (e) { showToast(L.err_transfer_quota_fetch, 'error'); } finally { const triggerBtn = UI && UI.btnTransferQuotaDetail; if (triggerBtn) delete triggerBtn.dataset.pkOpening; } }; const refreshQuotaText = () => { const txt = el.querySelector('#pk-quota-txt'); if (!txt || !S.quota) return; const isMaxNow = UI.win.classList.contains('pk-maximized'); const isCompactQuota = el.classList.contains('pk-hide-btn-text') || el.classList.contains('pk-auto-hide-btn-text'); txt.textContent = (isMaxNow && !isCompactQuota) ? `${S.quota.usedStr} / ${S.quota.limitStr}` : `${S.quota.pct}%`; }; const updateQuotaUI = async () => { try { const res = await fetch(`https://api-drive.mypikpak.com/drive/v1/about?_t=${Date.now()}`, { headers: getHeaders() }); if (!res.ok) { if (res.status === 401 || res.status === 403) { const didLogout = await confirmedLogout('quota-auth-rejected', 5000, 5200); if (didLogout) return; } return; } const data = await res.json(); const q = data.quota; if (!q) return; const used = parseInt(q.usage); const limit = parseInt(q.limit); const pct = Math.min(100, (used / limit) * 100).toFixed(1); const fQ = (v, d) => { let n = v, i = 0, units = ['B','KB','MB','GB','TB']; while(n >= 1024 && i < 4) { n /= 1024; i++; } return n.toFixed(d) + ' ' + units[i]; }; S.quota = { usedStr: fQ(used, 2), limitStr: fQ(limit, 0), pct: pct, usedRaw: used, limitRaw: limit }; const bar = el.querySelector('#pk-quota-bar'); const panel = el.querySelector('#pk-quota-panel'); if (bar) bar.style.width = pct + '%'; refreshQuotaText(); if (panel) panel.setAttribute('data-pk-tip', `${L.lbl_storage}: ${pct}% (${S.quota.usedStr} / ${S.quota.limitStr})`); if (bar) { if (parseFloat(pct) > 90) bar.style.background = '#d93025'; else if (parseFloat(pct) > 70) bar.style.background = '#faad14'; else bar.style.background = 'var(--pk-pri)'; } } catch (e) {} }; if (UI.btnTransferQuotaDetail) { UI.btnTransferQuotaDetail.onclick = (e) => { if (e) { e.preventDefault(); e.stopPropagation(); } showTransferQuotaDetail(); }; } updateQuotaUI(); const quotaTimer = setInterval(updateQuotaUI, 300000); const originalClose = UI.btnClose.onclick; UI.btnClose.onclick = (e) => { clearInterval(quotaTimer); if(originalClose) originalClose.call(UI.btnClose, e); }; await load(false, true); if (globalSavedState && globalSavedState.scrollTop !== undefined) { setTimeout(() => { if (typeof UI !== 'undefined' && UI.vp) { UI.vp.scrollTop = globalSavedState.scrollTop; delete globalSavedState.scrollTop; } }, 50); } syncGlobalStarredStatus(); if (gmGet('pk_turbo_mode', false)) { setTimeout(() => { if (location.href.includes('/login') || location.pathname.includes('login')) return; if (window.__pkTurboActivatedToastShown) return; window.__pkTurboActivatedToastShown = true; document.querySelectorAll('.pk-msg-toast').forEach(n => { if ((n.textContent || '').trim() === getStrings().msg_turbo_activated) n.remove(); }); if (typeof showToast === 'function') { showToast(getStrings().msg_turbo_activated, 'success', 5000); } }, 800); } setTimeout(async () => { const stubStr = gmGet('pk_migration_stub', ''); if (!stubStr) return; try { const stub = JSON.parse(stubStr); if (Date.now() - stub.timestamp > 86400000) { gmSet('pk_migration_stub', ''); return; } const isAuthReady = await waitForAuth(5000); if (!isAuthReady) { console.warn("[Migration] Auth not ready, aborting detection."); return; } const aboutRes = await fetch(`https://api-drive.mypikpak.com/drive/v1/about?_t=${Date.now()}`, { headers: getHeaders() }); if (!aboutRes.ok) throw new Error(`About API Error ${aboutRes.status}`); const aboutData = await aboutRes.json(); const currentUid = aboutData.sub; if (currentUid === stub.source_uid) { gmSet('pk_migration_stub', ''); showToast(L.msg_migrate_same_account, 'warning'); return; } if (await showConfirm(L.msg_migrate_detect.replace('{n}', stub.file_count), L.btn_migrate)) { setLoad(true); updateLoadTxt(L.msg_migrate_saving); const infoRes = await fetch(`https://api-drive.mypikpak.com/drive/v1/share?share_id=${stub.share_id}&pass_code=${stub.pass_code}`, { method: 'GET', headers: getHeaders() }); if (!infoRes.ok) throw new Error(`Share Info Fetch Error ${infoRes.status}`); const infoData = await infoRes.json(); const passCodeToken = infoData.pass_code_token || ""; const savePayload = { share_id: stub.share_id, pass_code_token: passCodeToken, params: { trace_file_ids: stub.file_ids.join(',') } }; const saveRes = await fetch(`https://api-drive.mypikpak.com/drive/v1/share/restore`, { method: 'POST', headers: getHeaders(), body: JSON.stringify(savePayload) }); if (!saveRes.ok) { const errData = await saveRes.json().catch(() => ({})); if (errData.error === 'file_restore_own' || errData.error_code === 9) { gmSet('pk_migration_stub', ''); setLoad(false); showToast(L.msg_migrate_same_account, 'warning'); return; } if (errData.error === 'file_space_not_enough' || errData.error_code === 8) { setLoad(false); const keep = await showConfirm(L.msg_migrate_quota_err.replace('{d}', errData.error_description), L.title_migrate_fail); if (!keep) gmSet('pk_migration_stub', ''); return; } throw new Error(errData.error_description || `Save API Error ${saveRes.status}`); } const saveData = await saveRes.json(); const migrateTaskRaw = saveData.task_id || saveData.taskId || saveData.restore_task_id || saveData.restoreTaskId || saveData.task_ids || saveData.taskIds || saveData.restore_task_ids || saveData.restoreTaskIds; const migrateTaskId = Array.isArray(migrateTaskRaw) ? migrateTaskRaw[0] : String(migrateTaskRaw || '').split(',')[0].trim(); if (migrateTaskId) { let isDone = false; let pollCount = 0; while(!isDone && pollCount < 300) { await sleep(2000); pollCount++; const tRes = await fetch(`https://api-drive.mypikpak.com/drive/v1/tasks/${encodeURIComponent(migrateTaskId)}`, { headers: getHeaders() }); if (!tRes.ok) continue; const tData = await tRes.json(); if (tData.phase === 'PHASE_TYPE_COMPLETE') isDone = true; else if (tData.phase === 'PHASE_TYPE_ERROR') throw new Error("Transfer task failed on server."); } if (!isDone) console.warn("[Migration] Task polling timed out, but might still be running."); } gmSet('pk_migration_stub', ''); setLoad(false); await showAlert(L.msg_migrate_success, "🎉 " + L.title_alert); UI.btnRefresh.click(); } else { gmSet('pk_migration_stub', ''); } } catch(e) { setLoad(false); const keep = await showConfirm(L.msg_migrate_err_keep.replace('{e}', e.message), L.title_alert); if (!keep) gmSet('pk_migration_stub', ''); } }, 1500); } let backgroundQueue = []; let isBackgroundRunning = false; let scannedFolderIds = new Set(); let globalPreloadPromise = null; let globalCache = new Map(); let globalLineageMap = new Map(); let globalParentIndex = new Map(); let globalTombstoneCache = new Map(); let globalDirtyFolders = new Set(); let globalNeedsSync = false; let isGlobalIndexReady = false; let hasShownGlobalWarnSession = false; let serverClockOffset = 0; let hasSyncedTime = false; let globalSavedState = null; const syncTime = (headers) => { if (!headers) return; const serverDate = headers.get('Date') || headers.get('date'); if (serverDate) { const remoteTime = new Date(serverDate).getTime(); const localTime = Date.now(); serverClockOffset = remoteTime - localTime; hasSyncedTime = true; } }; const getServerNow = () => Date.now() + serverClockOffset; let isGUISensitive = false; let pkState = null; const indexParents = (parentId, parentName, files) => { if (!files || !Array.isArray(files)) return; const pId = parentId || 'root'; const pName = parentName || 'Root'; for (const f of files) { if (f.kind === 'drive#folder') { globalParentIndex.set(f.id, { id: pId, name: pName }); } } }; const DurationProber = (() => { let queue = []; let queuedIds = new Set(); let failedDurationProbeSet = new Set(); let isRunning = false; let probeVideo = null; let probeAudio = null; let loopTimer = null; let runToken = 0; let activeId = ''; const parseDuration = (value) => { const n = Number(value || 0); return Number.isFinite(n) && n > 0 ? Math.round(n) : 0; }; const getProbeId = (item) => String((item && item.id) || ''); const getKnownDuration = (item) => { const id = getProbeId(item); return parseDuration((item && item.params && item.params.duration) || 0) || parseDuration(id && S.durationMap && S.durationMap.get(id)) || parseDuration(id && gmGet('pk_duration_' + id, 0)); }; const isProbeAudio = (item) => typeof isAudioLikeItem === 'function' && isAudioLikeItem(item); const isProbeVideo = (item) => typeof isVideoLikeItem === 'function' && isVideoLikeItem(item); const isProbeMedia = (item) => isProbeAudio(item) || isProbeVideo(item); const shouldProbe = (item, isBackground = false) => { const id = getProbeId(item); if (!item || !id || item._pkSkipDurationProbe || item.isHeader || item.kind === 'drive#folder') return false; if (isBackground || failedDurationProbeSet.has(id) || getKnownDuration(item) > 0) return false; return isProbeMedia(item); }; const getProbeElement = (audio) => { if (audio) { if (!probeAudio) { probeAudio = document.createElement('audio'); probeAudio.preload = 'metadata'; probeAudio.muted = true; probeAudio.crossOrigin = 'anonymous'; probeAudio.referrerPolicy = 'no-referrer'; probeAudio.style.display = 'none'; } return probeAudio; } if (!probeVideo) { probeVideo = document.createElement('video'); probeVideo.muted = true; probeVideo.preload = 'metadata'; probeVideo.style.display = 'none'; } return probeVideo; }; const clearProbeElement = (el) => { if (!el) return; el.removeAttribute('src'); try { el.load(); } catch (e) {} }; const markFailed = (item) => { const id = getProbeId(item); if (id) failedDurationProbeSet.add(id); }; const syncDurationToItem = (target, id, dur) => { if (!target || target.id !== id) return; if (!target.params) target.params = {}; target.params.duration = dur; }; const syncDurationEverywhere = (id, dur) => { if (!id || !(dur > 0)) return; if (S.durationMap) S.durationMap.set(id, dur); if (S.itemMap && S.itemMap.get(id)) syncDurationToItem(S.itemMap.get(id), id, dur); [S.items, S.display].forEach(list => Array.isArray(list) && list.forEach(i => syncDurationToItem(i, id, dur))); if (typeof pkState !== 'undefined' && pkState) { if (pkState.itemMap && pkState.itemMap.get(id)) syncDurationToItem(pkState.itemMap.get(id), id, dur); [pkState.items, pkState.display].forEach(list => Array.isArray(list) && list.forEach(i => syncDurationToItem(i, id, dur))); } }; const notifyDurationSaved = (id, dur) => { try { document.dispatchEvent(new CustomEvent('pk-duration-saved', { detail: { id, duration: dur } })); } catch (e) {} }; const startNext = async () => { if (!isRunning || queue.length === 0) { isRunning = false; return; } const currentToken = runToken; const isUserWatching = !!document.getElementById('pk-player-ov'); const isSystemScanning = typeof pkState !== 'undefined' && pkState && pkState.scanning; if (isUserWatching || isSystemScanning) { loopTimer = setTimeout(startNext, 2000); return; } const item = queue.shift(); const itemId = getProbeId(item); if (itemId) queuedIds.delete(itemId); if (!shouldProbe(item, false)) { loopTimer = setTimeout(startNext, 0); return; } activeId = itemId; const isAudioProbe = isProbeAudio(item); const probeEl = getProbeElement(isAudioProbe); let watchdog = null; let cleaned = false; const currentMedia = probeEl; const cleanup = (failed = false) => { if (cleaned) return; cleaned = true; if (watchdog) clearTimeout(watchdog); if (currentMedia) { currentMedia.removeEventListener('loadedmetadata', onLoaded); currentMedia.removeEventListener('error', onError); clearProbeElement(currentMedia); } if (failed) markFailed(item); activeId = ''; if (isRunning && currentToken === runToken) { loopTimer = setTimeout(startNext, 1500); } }; const saveAndNotify = (targetItem, dur) => { if (targetItem && targetItem._pkSkipDurationProbe) return; const id = getProbeId(targetItem); const seconds = parseDuration(dur); if (!id || !(seconds > 0)) return; gmSet('pk_duration_' + id, seconds); syncDurationEverywhere(id, seconds); notifyDurationSaved(id, seconds); if (typeof pkState !== 'undefined' && pkState) { if (pkState.historyMode) return; const safeId = window.CSS && CSS.escape ? CSS.escape(id) : id.replace(/"/g, '\\"'); const row = document.querySelector(`.pk-row[data-id="${safeId}"]`); if (row) { const cols = row.children; const durCol = cols.length > 1 ? cols[cols.length - 2] : null; if (durCol) { durCol.style.color = 'var(--pk-pri)'; durCol.textContent = fmtDur(seconds); setTimeout(() => { if(durCol) durCol.style.color = ''; }, 2000); } } if (typeof renderVisible === 'function') renderVisible(); } }; const onLoaded = () => { if (currentToken === runToken) { const dur = parseDuration(currentMedia && currentMedia.duration); if (dur > 0) { saveAndNotify(item, dur); cleanup(false); return; } } cleanup(true); }; const onError = () => { cleanup(true); }; currentMedia.addEventListener('loadedmetadata', onLoaded); currentMedia.addEventListener('error', onError); try { const res = await apiGet(item.id); if (currentToken !== runToken) { cleanup(); return; } const url = isAudioProbe && typeof getAudioDirectUrl === 'function' ? getAudioDirectUrl(res) : (res && res.web_content_link); if (!res || !url) { cleanup(true); return; } const nameLower = (item.name || '').toLowerCase(); const urlLower = url.toLowerCase(); const isM3u8 = nameLower.endsWith('.m3u8') || urlLower.includes('.m3u8'); const isWmv = nameLower.endsWith('.wmv') || urlLower.includes('.wmv') || nameLower.endsWith('.asf') || urlLower.includes('.asf'); const isAvi = nameLower.endsWith('.avi') || urlLower.includes('.avi') || nameLower.endsWith('.divx') || urlLower.includes('.divx'); const isFlv = nameLower.endsWith('.flv') || urlLower.includes('.flv'); const isMkv = nameLower.endsWith('.mkv') || urlLower.includes('.mkv'); const isRmvb = nameLower.endsWith('.rmvb') || urlLower.includes('.rmvb') || nameLower.endsWith('.rm') || urlLower.includes('.rm'); if (!isAudioProbe && isM3u8) { const fetchOptions = window.AbortSignal ? { signal: AbortSignal.timeout(15000) } : {}; const text = await fetch(url, fetchOptions).then(r => r.text()); let saved = false; if (currentToken === runToken) { const matches = text.matchAll(/#EXTINF:([\d.]+)/g); let total = 0; for (const m of matches) total += parseFloat(m[1]); if (total > 0) { saveAndNotify(item, Math.round(total)); saved = true; } } cleanup(!saved); } else if (!isAudioProbe && (isWmv || isAvi || isFlv || isMkv || isRmvb)) { const rangeEnd = isMkv ? 65535 : 8191; const fetchOptions = { headers: { 'Range': `bytes=0-${rangeEnd}` }, ...(window.AbortSignal ? { signal: AbortSignal.timeout(15000) } : {}) }; let saved = false; try { const response = await fetch(url, fetchOptions); if (currentToken === runToken && (response.ok || response.status === 206)) { const buffer = await response.arrayBuffer(); const view = new DataView(buffer); const bytes = new Uint8Array(buffer); let seconds = 0; let formatName = ''; if (isWmv) { formatName = nameLower.endsWith('.asf') ? 'ASF' : 'WMV'; const guid = [0xA1, 0xDC, 0xAB, 0x8C, 0x47, 0xA9, 0xCF, 0x11, 0x8E, 0xE4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65]; let foundIdx = -1; for (let i = 0; i < bytes.length - 88; i++) { let match = true; for (let j = 0; j < 16; j++) { if (bytes[i + j] !== guid[j]) { match = false; break; } } if (match) { foundIdx = i; break; } } if (foundIdx !== -1) { const playDur = view.getBigUint64(foundIdx + 64, true); const preroll = view.getBigUint64(foundIdx + 80, true); seconds = Number(playDur - preroll) / 10000000; } } else if (isAvi) { formatName = nameLower.endsWith('.divx') ? 'DIVX' : 'AVI'; const avih = [0x61, 0x76, 0x69, 0x68]; let foundIdx = -1; for (let i = 0; i < bytes.length - 28; i++) { if (bytes[i] === avih[0] && bytes[i+1] === avih[1] && bytes[i+2] === avih[2] && bytes[i+3] === avih[3]) { foundIdx = i; break; } } if (foundIdx !== -1) { const microSecPerFrame = view.getUint32(foundIdx + 8, true); const totalFrames = view.getUint32(foundIdx + 24, true); seconds = (microSecPerFrame * totalFrames) / 1000000; } } else if (isFlv) { formatName = 'FLV'; const durKey = [0x00, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00]; let foundIdx = -1; for (let i = 0; i < bytes.length - 19; i++) { let match = true; for (let j = 0; j < 11; j++) { if (bytes[i + j] !== durKey[j]) { match = false; break; } } if (match) { foundIdx = i; break; } } if (foundIdx !== -1) seconds = view.getFloat64(foundIdx + 11, false); } else if (isRmvb) { formatName = 'RM/RMVB'; const prop = [0x50, 0x52, 0x4F, 0x50]; let foundIdx = -1; for (let i = 0; i < bytes.length - 36; i++) { if (bytes[i] === prop[0] && bytes[i+1] === prop[1] && bytes[i+2] === prop[2] && bytes[i+3] === prop[3]) { foundIdx = i; break; } } if (foundIdx !== -1) { const ms = view.getUint32(foundIdx + 32, false); seconds = ms / 1000; } } else if (isMkv) { formatName = 'MKV'; let timecodeScale = 1000000; let durationVal = 0; for (let i = 0; i < bytes.length - 10; i++) { if (bytes[i] === 0x2A && bytes[i+1] === 0xD7 && bytes[i+2] === 0xB1) { let len = bytes[i+3] & 0x7F; if (len === 3) timecodeScale = (bytes[i+4]<<16) | (bytes[i+5]<<8) | bytes[i+6]; if (len === 4) timecodeScale = (bytes[i+4]<<24) | (bytes[i+5]<<16) | (bytes[i+6]<<8) | bytes[i+7]; break; } } for (let i = 0; i < bytes.length - 10; i++) { if (bytes[i] === 0x44 && bytes[i+1] === 0x89) { let lenByte = bytes[i+2]; if (lenByte === 0x84) { durationVal = view.getFloat32(i+3, false); break; } else if (lenByte === 0x88) { durationVal = view.getFloat64(i+3, false); break; } } } if (durationVal > 0) { seconds = (durationVal * timecodeScale) / 1000000000; } } if (seconds > 0) { saveAndNotify(item, Math.round(seconds)); saved = true; } } } catch (e) {} cleanup(!saved); } else { if (document.hidden) { queue.unshift(item); if (itemId) queuedIds.add(itemId); isRunning = false; cleanup(); return; } watchdog = setTimeout(() => { cleanup(true); }, 15000); currentMedia.src = url; currentMedia.load(); } } catch(e) { cleanup(true); } }; return { add: (item, isBackground = false) => { if (!shouldProbe(item, isBackground)) return; const id = getProbeId(item); if (queuedIds.has(id) || activeId === id) return; if (isBackground) { queue.push(item); } else { queue.unshift(item); } queuedIds.add(id); if (!isRunning) { isRunning = true; startNext(); } }, checkAndRun: () => { if (!isRunning && queue.length > 0) { isRunning = true; startNext(); } }, reset: () => { runToken++; queue = []; queuedIds = new Set(); failedDurationProbeSet = new Set(); activeId = ''; isRunning = false; if (loopTimer) clearTimeout(loopTimer); if (probeVideo) { clearProbeElement(probeVideo); probeVideo = null; } if (probeAudio) { clearProbeElement(probeAudio); probeAudio = null; } } }; })(); const minifyFile = (f, isBackground = false) => { if (f._minified) return f; const { id, kind, name, parent_id, size, mime_type, thumbnail_link, icon_link, web_content_link, hash, gcid, md5_checksum } = f; const trashed = !!f.trashed; const tags = f.tags ? [...f.tags] : []; const lineage = f._lineage ? [...f._lineage] : undefined; const isStarred = !!(f.starred || f.star || f.is_star || (tags.some(t => t.name === 'STAR'))); let duration = 0; const parse = (v) => { if (!v) return 0; const n = parseInt(v, 10); return isNaN(n) ? 0 : n; }; if (f.video_media_metadata?.duration) duration = parse(f.video_media_metadata.duration); if (!duration && f.audio_media_metadata?.duration) duration = parse(f.audio_media_metadata.duration); if (!duration && f.medias && Array.isArray(f.medias)) { for (const m of f.medias) { const d = m.duration ? parse(m.duration) : (m.video?.duration ? parse(m.video.duration) : 0); if (d > 0) { duration = d; break; } } } if (!duration && f.params?.duration) duration = parse(f.params.duration); let final_modified_time = f.modified_time; const skipDurationProbe = !!f._pkSkipDurationProbe; const skipNewDurationProbe = skipDurationProbe || isBackground || trashed; if (!duration && id && !skipDurationProbe) duration = gmGet('pk_duration_' + id, 0); if (kind === 'drive#folder' && id) { const localFMod = gmGet('pk_fmod_' + id); if (localFMod) final_modified_time = localFMod; } if (!duration && kind === 'drive#file' && !skipNewDurationProbe) { const probeItem = { id, name, kind, mime_type, parent_id, size }; if ((typeof isVideoLikeItem === 'function' && isVideoLikeItem(probeItem)) || (typeof isAudioLikeItem === 'function' && isAudioLikeItem(probeItem))) { setTimeout(() => DurationProber.add(probeItem, isBackground), 3000); } } const pickTrashMeta = (keys) => { const sources = [f, f.params, f.audit, f.delete_info, f.trash_info, f.recycle_info].filter(Boolean); for (const src of sources) { for (const key of keys) { if (src && src[key] !== undefined && src[key] !== null && src[key] !== '') return src[key]; } } return undefined; }; const trashRemainingDays = pickTrashMeta(['remaining_days', 'remain_days', 'days_left', 'left_days', 'expiration_days_left', 'expire_days_left', 'expires_in_days', 'trash_remaining_days', 'purge_remaining_days']); const trashExpireTime = pickTrashMeta(['expires_at', 'expire_time', 'expired_time', 'expiration_at', 'delete_at', 'delete_time', 'purge_at', 'purge_time', 'trashed_expire_time', 'trash_expire_time']); const trashDeletedTime = pickTrashMeta(['deleted_time', 'delete_time', 'deleted_at', 'trashed_time', 'trashed_at', 'trash_time', 'recycled_time', 'recycle_time']); const trashRetentionDays = pickTrashMeta(['retention_days', 'reserve_days', 'keep_days']); return { id, kind, name, parent_id, size, file_count: (function() { if (f.file_count !== undefined) return f.file_count; if (f.usage && f.usage.file_count !== undefined) return f.usage.file_count; if (f.params && f.params.file_count !== undefined) return f.params.file_count; if (f.audit && f.audit.file_count !== undefined) return f.audit.file_count; return undefined; })(), modified_time: final_modified_time, thumbnail_link, icon_link, mime_type, trashed, web_content_link, tags, starred: isStarred, params: { duration, width: f.video_media_metadata?.width || f.params?.width, height: f.video_media_metadata?.height || f.params?.height, global_file_kind: f.params?.global_file_kind, global_file_root: f.params?.global_file_root }, hash: hash || md5_checksum || gcid, _lineage: lineage, _recent_event_id: f._recent_event_id, _recent_event_type: f._recent_event_type, _recent_event_time: f._recent_event_time, _trash_remaining_days: trashRemainingDays, _trash_expire_time: trashExpireTime, _trash_deleted_time: trashDeletedTime, _trash_retention_days: trashRetentionDays, _minified: true }; }; async function runBackgroundCrawler() { if (isBackgroundRunning) return; isBackgroundRunning = true; if (window.pkUpdateCrawlerUI) window.pkUpdateCrawlerUI(); const homeBtn = document.querySelector('#pk-nav-home'); if (homeBtn) homeBtn.classList.add('pk-status-dot'); const userSetLimit = parseInt(localStorage.getItem('pk_user_limit') || "50"); const BACKGROUND_MAX_CONCURRENCY = Math.min(userSetLimit, 32); let currentConcurrencyLimit = 5; const MIN_CONCURRENCY = 2; let activeRequests = 0; let pendingRetries = 0; const fetchFolderContents = async (folder) => { activeRequests++; try { let files; if (globalCache.has(folder.id)) { files = globalCache.get(folder.id); } else { files = await apiList(folder.id, 1000, null, null, false, true); if (!isGUISensitive) { globalCache.set(folder.id, files); } } if (files && Array.isArray(files)) { for (let i = 0; i < files.length; i++) { const f = files[i]; if (f.kind === 'drive#folder') { if (!scannedFolderIds.has(f.id)) { backgroundQueue.push({ id: f.id, name: f.name, retryCount: 0 }); scannedFolderIds.add(f.id); } } } } if (currentConcurrencyLimit < BACKGROUND_MAX_CONCURRENCY) { currentConcurrencyLimit += 0.2; } } catch (err) { currentConcurrencyLimit = MIN_CONCURRENCY; folder.retryCount = (folder.retryCount || 0) + 1; const backoffTime = Math.min(folder.retryCount * 5000, 30000); pendingRetries++; try { await sleep(backoffTime); if (!isGUISensitive) backgroundQueue.unshift(folder); } finally { pendingRetries--; } } finally { activeRequests--; } }; while (backgroundQueue.length > 0 || activeRequests > 0 || pendingRetries > 0 || (typeof globalDirtyFolders !== 'undefined' && globalDirtyFolders.size > 0)) { const isUserBusy = pkState && (pkState.scanning || pkState.loading || document.getElementById('pk-player-ov')); if (isUserBusy) { if (homeBtn) homeBtn.classList.remove('pk-status-dot'); await sleep(2000); continue; } if (homeBtn) homeBtn.classList.add('pk-status-dot'); if (backgroundQueue.length > 0 && activeRequests < Math.floor(currentConcurrencyLimit)) { const folder = backgroundQueue.pop(); fetchFolderContents(folder); await sleep(50); } else if (activeRequests > 0 || pendingRetries > 0) { await sleep(500); } else if (typeof globalDirtyFolders !== 'undefined' && globalDirtyFolders.size > 0) { const dirtyId = Array.from(globalDirtyFolders)[0]; globalDirtyFolders.delete(dirtyId); if (typeof globalCache !== 'undefined') { for (const k of globalCache.keys()) { if (k && k.startsWith('__analyze_nodeMap_')) { globalCache.delete(k); } } } const normalizedId = dirtyId === 'root' ? '' : dirtyId; backgroundQueue.unshift({ id: normalizedId, name: "Dirty_Reval", retryCount: 0 }); continue; } else { let discovered = 0; if (typeof globalCache !== 'undefined') { for (const [parentFid, files] of globalCache) { if (!files) continue; for (let i = 0; i < files.length; i++) { const f = files[i]; if (f.kind === 'drive#folder' && !scannedFolderIds.has(f.id)) { backgroundQueue.push({ id: f.id, name: f.name, retryCount: 0 }); scannedFolderIds.add(f.id); discovered++; } } if (discovered > 0) break; } } if (discovered === 0) break; } } isBackgroundRunning = false; if (window.pkUpdateCrawlerUI) window.pkUpdateCrawlerUI(); if (homeBtn) homeBtn.classList.remove('pk-status-dot'); } async function preLoadRootFiles(onProgress) { if (globalPreloadPromise) return globalPreloadPromise; globalPreloadPromise = new Promise(async (resolve) => { try { const isAuthReady = await waitForAuth(3500); if (!isAuthReady) { console.warn("Background Crawler: Auth not ready. Halting preload."); globalPreloadPromise = null; resolve(false); return; } if (typeof window.pkCleanupGhostFiles === 'function') window.pkCleanupGhostFiles(); const rootFiles = await apiList('', 1000, onProgress, null, false, true); globalCache.set('root', rootFiles); const rootFolders = rootFiles.filter(f => f.kind === 'drive#folder'); rootFolders.forEach(f => { if (!scannedFolderIds.has(f.id)) { backgroundQueue.push({ id: f.id, name: f.name }); scannedFolderIds.add(f.id); } }); runBackgroundCrawler(); } catch (e) { console.error("Background pre-load failed:", e); const msg = String((e && e.message) || e || ''); if (/400|401|403|CAPTCHA|AUTH_RETRY/i.test(msg) && typeof window.pkEnterAuthRecoveryWindow === 'function') { window.pkEnterAuthRecoveryWindow('background-preload-auth-error', 5000); } } finally { const ok = globalCache.has('root'); if (!ok) globalPreloadPromise = null; resolve(ok); } }); return globalPreloadPromise; } async function tryInject() { if (location.href.includes('/login') || location.pathname.includes('login')) return; if (document.getElementById('pk-launch')) { return; } if (!document.body) { setTimeout(tryInject, 500); return; } inject(); window.pkScheduleResumeTasks = (reason = 'visibility') => { if (window.__pkResumeTaskTimer) clearTimeout(window.__pkResumeTaskTimer); const waitMs = (typeof window.pkIsAuthRecoveryActive === 'function' && window.pkIsAuthRecoveryActive()) ? 900 : 120; window.__pkResumeTaskTimer = setTimeout(() => { if (typeof window.pkIsAuthRecoveryActive === 'function' && window.pkIsAuthRecoveryActive()) { window.pkScheduleResumeTasks(`auth-wait:${reason}`); return; } if (location.href.includes('/login') || location.pathname.includes('login')) return; if (typeof DurationProber !== 'undefined') DurationProber.checkAndRun(); if (typeof isBackgroundRunning !== 'undefined' && !isBackgroundRunning) runBackgroundCrawler(); if (typeof pkState !== 'undefined' && pkState && pkState.uploadMode) { if (typeof refresh === 'function') refresh(); } }, waitMs); }; const isTurbo = typeof GM_getValue !== 'undefined' ? GM_getValue('pk_turbo_mode', false) : false; if (isTurbo) { const startTurbo = async () => { if (typeof window.pkIsAuthRecoveryActive === 'function' && window.pkIsAuthRecoveryActive()) { setTimeout(startTurbo, 900); return; } const preload = preLoadRootFiles(); if (!document.querySelector('.pk-ov')) { await ensureI18nReadyBeforeOpen(); await openManager(globalCache, preload); } }; setTimeout(startTurbo, 100); } else { const startPreload = () => { if (typeof window.pkIsAuthRecoveryActive === 'function' && window.pkIsAuthRecoveryActive()) { setTimeout(startPreload, 900); return; } preLoadRootFiles(); }; setTimeout(startPreload, 1500); } if (!window.__pkResumeHooksBound) { window.__pkResumeHooksBound = true; document.addEventListener('visibilitychange', () => { if (!document.hidden && typeof window.pkScheduleResumeTasks === 'function') { window.pkScheduleResumeTasks('visibility'); } }); window.addEventListener('focus', () => { if (!document.hidden && typeof window.pkScheduleResumeTasks === 'function') { window.pkScheduleResumeTasks('focus'); } }); window.addEventListener('pageshow', () => { if (!document.hidden && typeof window.pkScheduleResumeTasks === 'function') { window.pkScheduleResumeTasks('pageshow'); } }); } } function inject() { if (document.getElementById('pk-launch')) return; const b = document.createElement('button'); b.id = 'pk-launch'; const isTurbo = typeof GM_getValue !== 'undefined' ? GM_getValue('pk_turbo_mode', false) : false; const displayStyle = isTurbo ? 'none!important' : 'flex!important'; b.style.cssText = `position:fixed;bottom:20px;right:20px;width:50px;height:50px;border-radius:50%;background:#1a5eff;color:#FDFDFD;border:none;cursor:pointer;z-index:2147483647;box-shadow:0 4px 12px rgba(0,0,0,0.3);padding:0;overflow:hidden;transition:transform 0.1s;display:${displayStyle};align-items:center!important;justify-content:center!important;`; b.innerHTML = CONF.logoSVG.replace('width:24px;height:24px;', 'width:60%;height:60%;'); const savedLeft = gmGet('pk_pos_left', null); const savedTop = gmGet('pk_pos_top', null); if (savedLeft !== null && savedTop !== null) { b.style.bottom = 'auto'; b.style.right = 'auto'; b.style.left = savedLeft; b.style.top = savedTop; } else { b.style.bottom = 'auto'; b.style.right = 'auto'; b.style.left = '10px'; b.style.top = '430px'; } let isDragging = false; let dragStartX, dragStartY; const constrainBall = () => { const rect = b.getBoundingClientRect(); let newLeft = rect.left; let newTop = rect.top; const maxL = window.innerWidth - rect.width; const maxT = window.innerHeight - rect.height; if (newLeft > maxL) newLeft = maxL; if (newTop > maxT) newTop = maxT; if (newLeft < 0) newLeft = 0; if (newTop < 0) newTop = 0; b.style.left = newLeft + 'px'; b.style.top = newTop + 'px'; }; window.addEventListener('resize', constrainBall); const blockNativeDrag = (e) => { e.preventDefault(); e.stopPropagation(); e.dataTransfer.dropEffect = 'copy'; }; b.addEventListener('dragenter', blockNativeDrag); b.addEventListener('dragover', blockNativeDrag); b.addEventListener('drop', (e) => { blockNativeDrag(e); if (!document.querySelector('.pk-ov') || document.querySelector('.pk-ov').style.display === 'none') { const clickEvt = new MouseEvent('mousedown', { clientX: e.clientX, clientY: e.clientY }); b.dispatchEvent(clickEvt); const upEvt = new MouseEvent('mouseup', { clientX: e.clientX, clientY: e.clientY }); document.dispatchEvent(upEvt); } }); b.onmousedown = (e) => { isDragging = false; dragStartX = e.clientX; dragStartY = e.clientY; const rect = b.getBoundingClientRect(); b.style.bottom = 'auto'; b.style.right = 'auto'; b.style.left = rect.left + 'px'; b.style.top = rect.top + 'px'; b.style.transition = 'none'; const offsetX = e.clientX - rect.left; const offsetY = e.clientY - rect.top; const onMove = (em) => { if (!isDragging && (Math.abs(em.clientX - dragStartX) > 3 || Math.abs(em.clientY - dragStartY) > 3)) { isDragging = true; } if (isDragging) { b.style.left = (em.clientX - offsetX) + 'px'; b.style.top = (em.clientY - offsetY) + 'px'; } }; const onUp = async () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); b.style.transition = 'transform 0.1s'; if (!isDragging) { if (window.innerWidth < 720 || window.innerHeight < 340) { return; } let currentHeaders = getHeaders(); if (!currentHeaders.Authorization || currentHeaders.Authorization.length < 10) { console.warn("PikPak Master: No auth token on button click. Entering recovery recheck."); if (typeof window.pkEnterAuthRecoveryWindow === 'function') window.pkEnterAuthRecoveryWindow('button-click-missing-token', 4000); let isAuthReady = await waitForAuth(4500); if (!isAuthReady) { await sleep(800); isAuthReady = await waitForAuth(4200); } if (!isAuthReady) { console.warn("PikPak Master: Button click auth wait timeout."); const didLogout = await confirmedLogout('button-click-missing-token-final', 4000, 4500); if (didLogout) return; return; } currentHeaders = getHeaders(); } if (typeof window.pkMarkAuthRecovered === 'function') window.pkMarkAuthRecovered(); const existingWin = document.querySelector('.pk-ov'); if (existingWin) { if (existingWin.style.display === 'none') { const needGridReopenRelayout = !!existingWin.querySelector('.pk-win.pk-grid-view'); if (existingWin.querySelector('.pk-win.pk-maximized')) { document.body.classList.add('pk-body-max'); } existingWin.style.display = 'flex'; existingWin.focus(); if (needGridReopenRelayout) { requestAnimationFrame(() => { requestAnimationFrame(() => { window.dispatchEvent(new Event('resize')); }); }); } } else { existingWin.style.display = 'none'; } } else { await ensureI18nReadyBeforeOpen(); openManager(globalCache, globalPreloadPromise); } } else { constrainBall(); gmSet('pk_pos_left', b.style.left); gmSet('pk_pos_top', b.style.top); } }; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); }; document.body.appendChild(b); setTimeout(constrainBall, 0); } const startObserver = () => { if (!document.body) return; const obs = new MutationObserver(() => { if (!document.getElementById('pk-launch')) { tryInject(); } }); obs.observe(document.body, { childList: true, subtree: true }); }; if (document.readyState === 'loading') { window.addEventListener('DOMContentLoaded', () => { tryInject(); startObserver(); }); } else { tryInject(); startObserver(); } })() ;