From 84304be7da6ddc07a35a45879bf3f1fd955aa366 Mon Sep 17 00:00:00 2001 From: automation Date: Wed, 10 Sep 2025 10:12:27 -0400 Subject: [PATCH] feat: context menu to copy clean/short Ali links --- background.js | 105 +++++++++++++++++++++++++++++++++ build.sh | 2 +- dist/ali-sharelink-chrome.zip | Bin 2962 -> 4425 bytes dist/ali-sharelink-firefox.zip | Bin 2962 -> 4425 bytes manifest.json | 7 ++- 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 background.js diff --git a/background.js b/background.js new file mode 100644 index 0000000..de48edb --- /dev/null +++ b/background.js @@ -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); + } + } +}); + + diff --git a/build.sh b/build.sh index b22cc5a..cb672f1 100644 --- a/build.sh +++ b/build.sh @@ -19,7 +19,7 @@ cleanup() { trap cleanup EXIT # 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_FIREFOX/" done diff --git a/dist/ali-sharelink-chrome.zip b/dist/ali-sharelink-chrome.zip index 07c180319bb036b43368e8d450fba14d7cecfbbf..c5255cdbf6e79948c72516e02e590ca8e3979c8d 100644 GIT binary patch delta 2000 zcmZ{lhgZ|*8i0SSXu{r0K*F8|WmqXIK05AeqfCmna9xnbqLEb;gy9ZcWfB~BS03_F-5PE=$ zc8>}GexFCaJ7qGj!-9HeUyr=}QHofl4k}-cdapEM%S>e^6t1egFZ$8B%>zFgrFe+i z82Ealti-11YxvyMQQ;AJjfL?e9JR`uEo;YD@J1?+w{4JA=b#u%?2|k8RdR52R^eTK zMD9-Raa`n-j%YSb4Ox37oXB_7NV;!VF$TdS2Qu$K7tK%Tgvy%21{r>Bw&!`|m}Hs| z?sX~AlCsH$mqIqK?7a}|zt@lUYejI0M(hywb#}|35 zzUkMJY&Nf)a2d*3Rv@{+zqXHRH*gbrXBs3-y%0b zVS6ptovv-EiQOieKgRQk`D90B+&2qpH&gI1Out5~FFs{?_+77tc>A!sf>MJ$KOxJl z#8sRS!agRc&%r;-^~MuZ@Qz3_7FvRmMe~anwzjw|s@ZewOymP|-RSy$UhV&-rXk0| zf;?)2RKAqUmDGJ3#h^^rIt&IKZ*5Z?otRm<3cX}v4d5-yFki&o$DFW)ouKu)Ah$&2 z_Vi8)>Q&_fmjHQD(E*L=G(GQTx$WyAYl{D}x1C(VhUPn>E5-|lX=8|?t>bM^Hdstz z%Z+*`3UvIy-xzRjzu{;po-puX>g(gcTogpxEfWiD1-xPhgYsAtFs_{Lqd$Byn z2lqub^1Zy)h}r`qDy?O!vUW@WM%#DM8SO!smXBDFE?XC8kH9lZR%4s}xRqv2FR36) zS(U2|Au@U9x2bgpUJkBKpvQubCM9(uqg3MeNw%4Ledcr}cb8G@U`osBtK$`i1CBdI znJ8Dr6s$;_f1V)7_;mgbtbdGDxMdZfLrs z-EZ1&cW3jlw(CIKyJj6*xd!Iv8*z13*nS9mWCzt>rJ8;ZB>Z2iHim4dRN?a0WcR2Wz(41qyiU9@a8aP+)Qnn<$Ok-S$q?o2>c zpC2?f7`!omvKAm7btR)46x&Zj3wK$toAmkc8b>v!rOwOYQvkXDe&3%DZ`w=_K6?0t zqIs)$$!VKfHsd`Al`(Z0U)@-d$(m7y_`sG#55;K|g#o3|tV~?SCTRrgJRU3JFELqm zQ^9r?G2qf+u+q-xO$Z{W{$jkw8b#wSnIgZ-@_|oA`b?bHEIY1xw_yohU1Em)l!hj2 zjGjhs52mP>Jcn+Cz^Vu_u&d?al5lkw=WW&3t+*QVYH3spWxTjHAF`*b`OJFs-ZS=& zrvg52#T+KYTWfo30=BkJch_>%))T0ud_K>4mlqfMEj{`q-Q>tnBg)LjKP*Ni!E80} zrgiY;;SX$5ft3s&l(4$?zUi8;I{_N2^wJ&^{4Gz$cbIO)IKEDqYwIIzO z*-OVj2Dzd%71Sj&v_gH;QUrVS%eV?`2PaZT@>&Fy>WqFR#&TurUrm*IskwG ze??>H0l?SCH^^7sHPFk`(&A6<_b31S59ci7|8S;E0N~}|?dI$h5O|(kAMZa<4|-lE PT7VWnoUgXQe6izigK>}= delta 572 zcmX@9G)bH%z?+#xgn@y9gTXssB9A=V+m?eFZ(BARswljK+3=C>a3=F&sG7Pzid6{Xc#U*-K#rb)mA)E}%5Hk*DlvZ#vFtU6Fss&q7 zI`N?2VFRAF_qAOb?j~DjTWl1Txgf`&!p9Ms_iEj>ODbJ^c{6YAp1ef*He+7dpWnZ8 zpND8JRWC46@UA{^;>p~&)%C|+^(sQx-SsY+ULk?Qu#OG_rdO5M9^`seKa93|GB zA!Zs}Q#NwsE#$Fyf1)9DPN3H2D-RZXeYyT#VB${CM0VK~0=4_i4{?{ONKVy1a_Qyb zC&#M3F&A&NTDs;3|Ay_CxH|u)f3gn!kSM=t70XwR$yYrK^t zHzzcN6%_m+1+kcMB>{|Q1_oesFf3^l<77aOIj~lYSPSrG1zN$tzzl>Rfpj+)hz9_E C%icx+ diff --git a/dist/ali-sharelink-firefox.zip b/dist/ali-sharelink-firefox.zip index 07c180319bb036b43368e8d450fba14d7cecfbbf..c5255cdbf6e79948c72516e02e590ca8e3979c8d 100644 GIT binary patch delta 2000 zcmZ{lhgZ|*8i0SSXu{r0K*F8|WmqXIK05AeqfCmna9xnbqLEb;gy9ZcWfB~BS03_F-5PE=$ zc8>}GexFCaJ7qGj!-9HeUyr=}QHofl4k}-cdapEM%S>e^6t1egFZ$8B%>zFgrFe+i z82Ealti-11YxvyMQQ;AJjfL?e9JR`uEo;YD@J1?+w{4JA=b#u%?2|k8RdR52R^eTK zMD9-Raa`n-j%YSb4Ox37oXB_7NV;!VF$TdS2Qu$K7tK%Tgvy%21{r>Bw&!`|m}Hs| z?sX~AlCsH$mqIqK?7a}|zt@lUYejI0M(hywb#}|35 zzUkMJY&Nf)a2d*3Rv@{+zqXHRH*gbrXBs3-y%0b zVS6ptovv-EiQOieKgRQk`D90B+&2qpH&gI1Out5~FFs{?_+77tc>A!sf>MJ$KOxJl z#8sRS!agRc&%r;-^~MuZ@Qz3_7FvRmMe~anwzjw|s@ZewOymP|-RSy$UhV&-rXk0| zf;?)2RKAqUmDGJ3#h^^rIt&IKZ*5Z?otRm<3cX}v4d5-yFki&o$DFW)ouKu)Ah$&2 z_Vi8)>Q&_fmjHQD(E*L=G(GQTx$WyAYl{D}x1C(VhUPn>E5-|lX=8|?t>bM^Hdstz z%Z+*`3UvIy-xzRjzu{;po-puX>g(gcTogpxEfWiD1-xPhgYsAtFs_{Lqd$Byn z2lqub^1Zy)h}r`qDy?O!vUW@WM%#DM8SO!smXBDFE?XC8kH9lZR%4s}xRqv2FR36) zS(U2|Au@U9x2bgpUJkBKpvQubCM9(uqg3MeNw%4Ledcr}cb8G@U`osBtK$`i1CBdI znJ8Dr6s$;_f1V)7_;mgbtbdGDxMdZfLrs z-EZ1&cW3jlw(CIKyJj6*xd!Iv8*z13*nS9mWCzt>rJ8;ZB>Z2iHim4dRN?a0WcR2Wz(41qyiU9@a8aP+)Qnn<$Ok-S$q?o2>c zpC2?f7`!omvKAm7btR)46x&Zj3wK$toAmkc8b>v!rOwOYQvkXDe&3%DZ`w=_K6?0t zqIs)$$!VKfHsd`Al`(Z0U)@-d$(m7y_`sG#55;K|g#o3|tV~?SCTRrgJRU3JFELqm zQ^9r?G2qf+u+q-xO$Z{W{$jkw8b#wSnIgZ-@_|oA`b?bHEIY1xw_yohU1Em)l!hj2 zjGjhs52mP>Jcn+Cz^Vu_u&d?al5lkw=WW&3t+*QVYH3spWxTjHAF`*b`OJFs-ZS=& zrvg52#T+KYTWfo30=BkJch_>%))T0ud_K>4mlqfMEj{`q-Q>tnBg)LjKP*Ni!E80} zrgiY;;SX$5ft3s&l(4$?zUi8;I{_N2^wJ&^{4Gz$cbIO)IKEDqYwIIzO z*-OVj2Dzd%71Sj&v_gH;QUrVS%eV?`2PaZT@>&Fy>WqFR#&TurUrm*IskwG ze??>H0l?SCH^^7sHPFk`(&A6<_b31S59ci7|8S;E0N~}|?dI$h5O|(kAMZa<4|-lE PT7VWnoUgXQe6izigK>}= delta 572 zcmX@9G)bH%z?+#xgn@y9gTXssB9A=V+m?eFZ(BARswljK+3=C>a3=F&sG7Pzid6{Xc#U*-K#rb)mA)E}%5Hk*DlvZ#vFtU6Fss&q7 zI`N?2VFRAF_qAOb?j~DjTWl1Txgf`&!p9Ms_iEj>ODbJ^c{6YAp1ef*He+7dpWnZ8 zpND8JRWC46@UA{^;>p~&)%C|+^(sQx-SsY+ULk?Qu#OG_rdO5M9^`seKa93|GB zA!Zs}Q#NwsE#$Fyf1)9DPN3H2D-RZXeYyT#VB${CM0VK~0=4_i4{?{ONKVy1a_Qyb zC&#M3F&A&NTDs;3|Ay_CxH|u)f3gn!kSM=t70XwR$yYrK^t zHzzcN6%_m+1+kcMB>{|Q1_oesFf3^l<77aOIj~lYSPSrG1zN$tzzl>Rfpj+)hz9_E C%icx+ diff --git a/manifest.json b/manifest.json index 488c0ed..b2b248b 100644 --- a/manifest.json +++ b/manifest.json @@ -3,6 +3,9 @@ "name": "Ali Sharelink", "version": "0.1.0", "description": "Get a clean, canonical share link for AliExpress/Alibaba products, with optional shortlink.", + "background": { + "service_worker": "background.js" + }, "action": { "default_title": "Ali Sharelink", "default_popup": "popup.html" @@ -10,7 +13,9 @@ "permissions": [ "activeTab", "tabs", - "clipboardWrite" + "clipboardWrite", + "contextMenus", + "scripting" ], "host_permissions": [ "https://is.gd/*"