${L.title_share_detail}
`);
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 => `
`).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}
${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}
`);
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();
}
})()
;