thread-master/apps/extension/haixun-threads-sync/storage-state.js

132 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** @typedef {{ name: string; value: string; domain: string; path: string; expires: number; httpOnly: boolean; secure: boolean; sameSite: string }} PlaywrightCookie */
const COOKIE_DOMAINS = [
"threads.com",
"threads.net",
"instagram.com",
"facebook.com",
];
const THREADS_TAB_URLS = [
"https://www.threads.com/*",
"https://threads.com/*",
"https://www.threads.net/*",
"https://threads.net/*",
];
function mapSameSite(sameSite) {
if (sameSite === "no_restriction") return "None";
if (sameSite === "lax") return "Lax";
if (sameSite === "strict") return "Strict";
return "Lax";
}
/** @param {chrome.cookies.Cookie} cookie */
function toPlaywrightCookie(cookie) {
return {
name: cookie.name,
value: cookie.value,
domain: cookie.domain,
path: cookie.path || "/",
expires: cookie.expirationDate ?? -1,
httpOnly: cookie.httpOnly ?? false,
secure: cookie.secure ?? false,
sameSite: mapSameSite(cookie.sameSite),
};
}
export async function collectThreadsCookies() {
/** @type {PlaywrightCookie[]} */
const merged = [];
const seen = new Set();
for (const domain of COOKIE_DOMAINS) {
const batch = await chrome.cookies.getAll({ domain });
for (const cookie of batch) {
const key = `${cookie.domain}|${cookie.path}|${cookie.name}`;
if (seen.has(key)) continue;
seen.add(key);
merged.push(toPlaywrightCookie(cookie));
}
}
// Some Chrome builds store Meta login cookies only on exact host URLs.
const urls = [
"https://www.threads.com/",
"https://threads.com/",
"https://www.threads.net/",
"https://threads.net/",
"https://www.instagram.com/",
];
for (const url of urls) {
try {
const batch = await chrome.cookies.getAll({ url });
for (const cookie of batch) {
const key = `${cookie.domain}|${cookie.path}|${cookie.name}`;
if (seen.has(key)) continue;
seen.add(key);
merged.push(toPlaywrightCookie(cookie));
}
} catch {
// host permission missing for that URL — skip
}
}
return merged;
}
/**
* localStorage 非必要cookies 已可組成 storageState。
* 缺 host 權限時不能 executeScript必須 soft-fail否則設定頁同步會整段炸掉。
*/
export async function collectThreadsLocalStorage() {
let tabs = [];
try {
tabs = await chrome.tabs.query({ url: THREADS_TAB_URLS });
} catch {
return [];
}
if (!tabs.length || tabs[0].id == null) {
return [];
}
try {
const [injection] = await chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: () => {
const localStorageItems = [];
for (let i = 0; i < localStorage.length; i += 1) {
const name = localStorage.key(i);
if (!name) continue;
localStorageItems.push({ name, value: localStorage.getItem(name) ?? "" });
}
return { origin: location.origin, localStorage: localStorageItems };
},
});
const payload = injection?.result;
if (!payload || !Array.isArray(payload.localStorage)) {
return [];
}
return [payload];
} catch (error) {
// Chrome: "Cannot access contents of the page. Extension manifest must request permission..."
console.warn("[haixun] skip threads localStorage (no host access)", error);
return [];
}
}
export async function buildStorageState() {
const cookies = await collectThreadsCookies();
const origins = await collectThreadsLocalStorage();
if (!cookies.length) {
throw new Error(
"找不到 Threads / Instagram cookies。請先在 Chrome 開啟 threads.com 或 threads.net 並完成登入",
);
}
return JSON.stringify({ cookies, origins });
}