feat: context menu to copy clean/short Ali links
This commit is contained in:
parent
a24b476d06
commit
84304be7da
|
@ -0,0 +1,105 @@
|
||||||
|
/* global chrome, browser */
|
||||||
|
const API = typeof chrome !== "undefined" ? chrome : browser;
|
||||||
|
|
||||||
|
function canonicalizeAliExpress(urlString) {
|
||||||
|
const url = new URL(urlString);
|
||||||
|
const path = url.pathname;
|
||||||
|
const canonicalHost = "www.aliexpress.com";
|
||||||
|
let productId = null;
|
||||||
|
const itemMatch = path.match(/\/(item|i)\/(\d+)\.html/i);
|
||||||
|
if (itemMatch) productId = itemMatch[2];
|
||||||
|
if (!productId) {
|
||||||
|
const xObjectId = url.searchParams.get("x_object_id") || url.searchParams.get("item_id");
|
||||||
|
if (xObjectId && /^\d+$/.test(xObjectId)) productId = xObjectId;
|
||||||
|
}
|
||||||
|
if (productId) return `https://${canonicalHost}/item/${productId}.html`;
|
||||||
|
return `https://${canonicalHost}${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function canonicalizeAlibaba(urlString) {
|
||||||
|
const url = new URL(urlString);
|
||||||
|
return `${url.origin}${url.pathname}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function canonicalize(urlString) {
|
||||||
|
const url = new URL(urlString);
|
||||||
|
const hostname = url.hostname.toLowerCase();
|
||||||
|
if (hostname.includes("aliexpress.")) return { vendor: "AliExpress", url: canonicalizeAliExpress(urlString) };
|
||||||
|
if (hostname.includes("alibaba.")) return { vendor: "Alibaba", url: canonicalizeAlibaba(urlString) };
|
||||||
|
return { vendor: "Unknown", url: `${url.origin}${url.pathname}` };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shortenWithIsGd(longUrl) {
|
||||||
|
const endpoint = `https://is.gd/create.php?format=simple&url=${encodeURIComponent(longUrl)}`;
|
||||||
|
const res = await fetch(endpoint, { method: "GET" });
|
||||||
|
const text = await res.text();
|
||||||
|
if (!res.ok) throw new Error(text || `Shorten failed (${res.status})`);
|
||||||
|
if (!/^https?:\/\//i.test(text)) throw new Error(text || "Shorten failed");
|
||||||
|
return text.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMenus() {
|
||||||
|
try { API.contextMenus.removeAll(() => {}); } catch (e) {}
|
||||||
|
API.contextMenus.create({
|
||||||
|
id: "ali-clean",
|
||||||
|
title: "Copy clean Ali link",
|
||||||
|
contexts: ["page", "selection", "link"],
|
||||||
|
documentUrlPatterns: [
|
||||||
|
"*://*.aliexpress.*/*",
|
||||||
|
"*://*.alibaba.*/*"
|
||||||
|
]
|
||||||
|
});
|
||||||
|
API.contextMenus.create({
|
||||||
|
id: "ali-shorten",
|
||||||
|
title: "Copy short Ali link",
|
||||||
|
contexts: ["page", "selection", "link"],
|
||||||
|
documentUrlPatterns: [
|
||||||
|
"*://*.aliexpress.*/*",
|
||||||
|
"*://*.alibaba.*/*"
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getRelevantUrl(info, tab) {
|
||||||
|
if (info.linkUrl) return info.linkUrl;
|
||||||
|
return tab && tab.url ? tab.url : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyTextToClipboard(tabId, text) {
|
||||||
|
// Using scripting to execute in page context for broad compatibility
|
||||||
|
const code = `navigator.clipboard.writeText(${JSON.stringify(text)}).then(() => true).catch(() => false);`;
|
||||||
|
try {
|
||||||
|
const results = await API.scripting.executeScript({
|
||||||
|
target: { tabId },
|
||||||
|
func: (t) => navigator.clipboard.writeText(t).then(() => true).catch(() => false),
|
||||||
|
args: [text]
|
||||||
|
});
|
||||||
|
const ok = results && results[0] && results[0].result;
|
||||||
|
return !!ok;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
API.runtime.onInstalled && API.runtime.onInstalled.addListener(() => {
|
||||||
|
createMenus();
|
||||||
|
});
|
||||||
|
|
||||||
|
API.contextMenus.onClicked.addListener(async (info, tab) => {
|
||||||
|
if (!tab || !tab.id) return;
|
||||||
|
const rawUrl = await getRelevantUrl(info, tab);
|
||||||
|
if (!rawUrl) return;
|
||||||
|
const { url } = canonicalize(rawUrl);
|
||||||
|
if (info.menuItemId === "ali-clean") {
|
||||||
|
await copyTextToClipboard(tab.id, url);
|
||||||
|
} else if (info.menuItemId === "ali-shorten") {
|
||||||
|
try {
|
||||||
|
const shortUrl = await shortenWithIsGd(url);
|
||||||
|
await copyTextToClipboard(tab.id, shortUrl);
|
||||||
|
} catch (e) {
|
||||||
|
await copyTextToClipboard(tab.id, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
2
build.sh
2
build.sh
|
@ -19,7 +19,7 @@ cleanup() {
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
# Common files
|
# Common files
|
||||||
for f in manifest.json popup.html popup.js; do
|
for f in manifest.json popup.html popup.js background.js; do
|
||||||
cp "$ROOT_DIR/$f" "$WORK_CHROME/"
|
cp "$ROOT_DIR/$f" "$WORK_CHROME/"
|
||||||
cp "$ROOT_DIR/$f" "$WORK_FIREFOX/"
|
cp "$ROOT_DIR/$f" "$WORK_FIREFOX/"
|
||||||
done
|
done
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -3,6 +3,9 @@
|
||||||
"name": "Ali Sharelink",
|
"name": "Ali Sharelink",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Get a clean, canonical share link for AliExpress/Alibaba products, with optional shortlink.",
|
"description": "Get a clean, canonical share link for AliExpress/Alibaba products, with optional shortlink.",
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js"
|
||||||
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"default_title": "Ali Sharelink",
|
"default_title": "Ali Sharelink",
|
||||||
"default_popup": "popup.html"
|
"default_popup": "popup.html"
|
||||||
|
@ -10,7 +13,9 @@
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"activeTab",
|
"activeTab",
|
||||||
"tabs",
|
"tabs",
|
||||||
"clipboardWrite"
|
"clipboardWrite",
|
||||||
|
"contextMenus",
|
||||||
|
"scripting"
|
||||||
],
|
],
|
||||||
"host_permissions": [
|
"host_permissions": [
|
||||||
"https://is.gd/*"
|
"https://is.gd/*"
|
||||||
|
|
Loading…
Reference in New Issue