feat: add youtube floating quick-download action
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "gomdown-helper",
|
"name": "gomdown-helper",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.10",
|
"version": "0.0.11",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"../../../../../@crx/manifest": {
|
"../../../../../@crx/manifest": {
|
||||||
"file": "assets/crx-manifest.js--9ZsUvq0.js",
|
"file": "assets/crx-manifest.js-B0cyAIB1.js",
|
||||||
"name": "crx-manifest.js",
|
"name": "crx-manifest.js",
|
||||||
"src": "../../../../../@crx/manifest",
|
"src": "../../../../../@crx/manifest",
|
||||||
"isEntry": true
|
"isEntry": true
|
||||||
@@ -20,9 +20,9 @@
|
|||||||
"file": "assets/downloadIntent-Dv31jC2S.js",
|
"file": "assets/downloadIntent-Dv31jC2S.js",
|
||||||
"name": "downloadIntent"
|
"name": "downloadIntent"
|
||||||
},
|
},
|
||||||
"_index.ts-loader-DMyyuf2n.js": {
|
"_index.ts-loader-BHtfStLc.js": {
|
||||||
"file": "assets/index.ts-loader-DMyyuf2n.js",
|
"file": "assets/index.ts-loader-BHtfStLc.js",
|
||||||
"src": "_index.ts-loader-DMyyuf2n.js"
|
"src": "_index.ts-loader-BHtfStLc.js"
|
||||||
},
|
},
|
||||||
"_settings-Bo6W9Drl.js": {
|
"_settings-Bo6W9Drl.js": {
|
||||||
"file": "assets/settings-Bo6W9Drl.js",
|
"file": "assets/settings-Bo6W9Drl.js",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/background/index.ts": {
|
"src/background/index.ts": {
|
||||||
"file": "assets/index.ts-U2ACoZ75.js",
|
"file": "assets/index.ts-BAxKsZ8F.js",
|
||||||
"name": "index.ts",
|
"name": "index.ts",
|
||||||
"src": "src/background/index.ts",
|
"src": "src/background/index.ts",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"src/content/index.ts": {
|
"src/content/index.ts": {
|
||||||
"file": "assets/index.ts-BGLNJwsP.js",
|
"file": "assets/index.ts-CMnPQ13j.js",
|
||||||
"name": "index.ts",
|
"name": "index.ts",
|
||||||
"src": "src/content/index.ts",
|
"src": "src/content/index.ts",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
|
|||||||
1
packages/chrome/assets/index.ts-BAxKsZ8F.js
Normal file
1
packages/chrome/assets/index.ts-BAxKsZ8F.js
Normal file
File diff suppressed because one or more lines are too long
1
packages/chrome/assets/index.ts-CMnPQ13j.js
Normal file
1
packages/chrome/assets/index.ts-CMnPQ13j.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import{b as m}from"./browser-polyfill-CZ_dLIqp.js";import{i as a,n as k}from"./downloadIntent-Dv31jC2S.js";const E=8e3,i=new Map;function v(){const e=Date.now();for(const[r,t]of i.entries())t<=e&&i.delete(r)}async function u(e,r){const t=k(e,window.location.href);if(!t)return!1;if(v(),i.has(t))return!0;i.set(t,Date.now()+E);try{if((await m.runtime.sendMessage({type:"capture-link-download",url:t,referer:r||document.referrer||window.location.href}))?.ok)return!0}catch{}return i.delete(t),!1}function p(e){return e?e instanceof HTMLAnchorElement?e:e instanceof Element?e.closest("a[href]"):null:null}function h(e){return!!(e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)}async function g(e){if(e.defaultPrevented||h(e))return;const r=p(e.target);if(!r)return;const t=r.href||"";!t||!a(t,window.location.href)||(e.preventDefault(),e.stopImmediatePropagation(),e.stopPropagation(),await u(t,document.referrer||window.location.href))}function b(e){const r=p(e.target);if(!r)return;const t=r.href||"";!t||!a(t,window.location.href)||h(e)||(e.preventDefault(),e.stopImmediatePropagation(),e.stopPropagation(),u(t,document.referrer||window.location.href))}document.addEventListener("pointerdown",e=>{e.button===0&&b(e)},!0);document.addEventListener("mousedown",e=>{e.button===0&&b(e)},!0);document.addEventListener("click",e=>{e.button===0&&g(e)},!0);document.addEventListener("keydown",e=>{if(e.key!=="Enter"||e.defaultPrevented||h(e))return;const r=p(e.target);if(!r)return;const t=r.href||"";!t||!a(t,window.location.href)||(e.preventDefault(),e.stopImmediatePropagation(),e.stopPropagation(),u(t,document.referrer||window.location.href))},!0);document.addEventListener("auxclick",e=>{e.button===1&&g(e)},!0);function C(){try{const e=window.open.bind(window);window.open=function(t,n,x){const d=String(t||"").trim();return d&&a(d,window.location.href)?(u(d,window.location.href),null):e(t,n,x)}}catch{}try{const e=HTMLAnchorElement.prototype.click;HTMLAnchorElement.prototype.click=function(){const t=this.href||this.getAttribute("href")||"";if(t&&a(t,window.location.href)){u(t,document.referrer||window.location.href);return}e.call(this)}}catch{}}C();let l=null,o=null,w=!1,y=null,s=window.location.href;function L(e){try{const r=new URL(e);return r.hostname!=="www.youtube.com"&&r.hostname!=="youtube.com"?!1:r.pathname==="/watch"&&r.searchParams.has("v")}catch{return!1}}function c(e,r="idle"){o&&(o.textContent=e,r==="ok"?o.style.color="#8ff0a4":r==="error"?o.style.color="#ff9b9b":o.style.color="#aeb7d8")}async function P(){if(!w){w=!0,c("gdown으로 전송 중...");try{const e=await m.runtime.sendMessage({type:"page:enqueue-ytdlp-url",url:window.location.href,referer:window.location.href});e?.ok?c("다운로드 모달로 전송됨","ok"):c(`전송 실패: ${e?.error||"unknown error"}`,"error")}catch(e){c(`전송 실패: ${String(e)}`,"error")}finally{w=!1}}}function I(){l&&(l.remove(),l=null,o=null)}function f(){if(window.top!==window.self)return;if(!L(window.location.href)){I();return}if(l)return;const e=document.createElement("div");e.id="gomdown-youtube-overlay",e.style.position="fixed",e.style.right="20px",e.style.bottom="24px",e.style.zIndex="2147483647",e.style.background="rgba(17, 21, 32, 0.94)",e.style.border="1px solid rgba(133, 148, 195, 0.35)",e.style.borderRadius="12px",e.style.padding="10px",e.style.boxShadow="0 8px 24px rgba(0, 0, 0, 0.28)",e.style.backdropFilter="blur(6px)",e.style.width="220px",e.style.fontFamily="ui-sans-serif, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif",e.style.color="#e8edff";const r=document.createElement("div");r.textContent="Gdown Helper",r.style.fontSize="12px",r.style.fontWeight="700",r.style.marginBottom="8px";const t=document.createElement("button");t.type="button",t.textContent="이 영상 다운로드",t.style.width="100%",t.style.height="34px",t.style.border="1px solid #5a69f0",t.style.borderRadius="8px",t.style.background="#5a69f0",t.style.color="#ffffff",t.style.fontSize="12px",t.style.fontWeight="700",t.style.cursor="pointer",t.addEventListener("click",()=>{P()});const n=document.createElement("div");n.textContent="클릭 시 gdown 다운로드 모달로 연결",n.style.fontSize="11px",n.style.marginTop="8px",n.style.lineHeight="1.35",n.style.color="#aeb7d8",e.appendChild(r),e.appendChild(t),e.appendChild(n),document.documentElement.appendChild(e),l=e,o=n}function S(){y===null&&(y=window.setInterval(()=>{const e=window.location.href;e!==s&&(s=e,f())},800),window.addEventListener("popstate",()=>{s=window.location.href,f()}),document.addEventListener("yt-navigate-finish",()=>{s=window.location.href,f()}))}f();S();
|
||||||
13
packages/chrome/assets/index.ts-loader-BHtfStLc.js
Normal file
13
packages/chrome/assets/index.ts-loader-BHtfStLc.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const injectTime = performance.now();
|
||||||
|
(async () => {
|
||||||
|
const { onExecute } = await import(
|
||||||
|
/* @vite-ignore */
|
||||||
|
chrome.runtime.getURL("assets/index.ts-CMnPQ13j.js")
|
||||||
|
);
|
||||||
|
onExecute?.({ perf: { injectTime, loadTime: performance.now() - injectTime } });
|
||||||
|
})().catch(console.error);
|
||||||
|
|
||||||
|
})();
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Gomdown Helper",
|
"name": "Gomdown Helper",
|
||||||
"description": "Send browser downloads to gdown",
|
"description": "Send browser downloads to gdown",
|
||||||
"version": "0.0.10",
|
"version": "0.0.11",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "images/16.png",
|
"16": "images/16.png",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"js": [
|
"js": [
|
||||||
"assets/index.ts-loader-DMyyuf2n.js"
|
"assets/index.ts-loader-BHtfStLc.js"
|
||||||
],
|
],
|
||||||
"matches": [
|
"matches": [
|
||||||
"<all_urls>"
|
"<all_urls>"
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
"images/*",
|
"images/*",
|
||||||
"assets/browser-polyfill-CZ_dLIqp.js",
|
"assets/browser-polyfill-CZ_dLIqp.js",
|
||||||
"assets/downloadIntent-Dv31jC2S.js",
|
"assets/downloadIntent-Dv31jC2S.js",
|
||||||
"assets/index.ts-BGLNJwsP.js"
|
"assets/index.ts-CMnPQ13j.js"
|
||||||
],
|
],
|
||||||
"use_dynamic_url": false
|
"use_dynamic_url": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
import './assets/index.ts-U2ACoZ75.js';
|
import './assets/index.ts-BAxKsZ8F.js';
|
||||||
|
|||||||
BIN
packages/gomdown-helper.v0.0.11.chrome.zip
Normal file
BIN
packages/gomdown-helper.v0.0.11.chrome.zip
Normal file
Binary file not shown.
@@ -419,6 +419,13 @@ browser.runtime.onMessage.addListener((message: any, sender: any) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message?.type === 'page:enqueue-ytdlp-url') {
|
||||||
|
const url = String(message?.url || '').trim()
|
||||||
|
const referer = String(message?.referer || url).trim()
|
||||||
|
if (!url) return Promise.resolve({ ok: false, error: 'url is empty' })
|
||||||
|
return transferUrlToGdown(url, referer || url, 'yt-dlp')
|
||||||
|
}
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -140,3 +140,142 @@ function installProgrammaticInterceptors(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
installProgrammaticInterceptors()
|
installProgrammaticInterceptors()
|
||||||
|
|
||||||
|
let ytOverlayRoot: HTMLDivElement | null = null
|
||||||
|
let ytOverlayStatus: HTMLDivElement | null = null
|
||||||
|
let ytOverlayBusy = false
|
||||||
|
let ytUrlWatcherTimer: number | null = null
|
||||||
|
let lastObservedUrl = window.location.href
|
||||||
|
|
||||||
|
function isYoutubeWatchPage(url: string): boolean {
|
||||||
|
try {
|
||||||
|
const parsed = new URL(url)
|
||||||
|
if (parsed.hostname !== 'www.youtube.com' && parsed.hostname !== 'youtube.com') return false
|
||||||
|
return parsed.pathname === '/watch' && parsed.searchParams.has('v')
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setYtOverlayStatus(message: string, tone: 'ok' | 'error' | 'idle' = 'idle'): void {
|
||||||
|
if (!ytOverlayStatus) return
|
||||||
|
ytOverlayStatus.textContent = message
|
||||||
|
if (tone === 'ok') ytOverlayStatus.style.color = '#8ff0a4'
|
||||||
|
else if (tone === 'error') ytOverlayStatus.style.color = '#ff9b9b'
|
||||||
|
else ytOverlayStatus.style.color = '#aeb7d8'
|
||||||
|
}
|
||||||
|
|
||||||
|
async function enqueueCurrentYoutubePage(): Promise<void> {
|
||||||
|
if (ytOverlayBusy) return
|
||||||
|
ytOverlayBusy = true
|
||||||
|
setYtOverlayStatus('gdown으로 전송 중...')
|
||||||
|
try {
|
||||||
|
const result = (await browser.runtime.sendMessage({
|
||||||
|
type: 'page:enqueue-ytdlp-url',
|
||||||
|
url: window.location.href,
|
||||||
|
referer: window.location.href,
|
||||||
|
})) as { ok?: boolean; error?: string }
|
||||||
|
if (result?.ok) {
|
||||||
|
setYtOverlayStatus('다운로드 모달로 전송됨', 'ok')
|
||||||
|
} else {
|
||||||
|
setYtOverlayStatus(`전송 실패: ${result?.error || 'unknown error'}`, 'error')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setYtOverlayStatus(`전송 실패: ${String(error)}`, 'error')
|
||||||
|
} finally {
|
||||||
|
ytOverlayBusy = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeYoutubeOverlay(): void {
|
||||||
|
if (ytOverlayRoot) {
|
||||||
|
ytOverlayRoot.remove()
|
||||||
|
ytOverlayRoot = null
|
||||||
|
ytOverlayStatus = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureYoutubeOverlay(): void {
|
||||||
|
if (window.top !== window.self) return
|
||||||
|
if (!isYoutubeWatchPage(window.location.href)) {
|
||||||
|
removeYoutubeOverlay()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (ytOverlayRoot) return
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
root.id = 'gomdown-youtube-overlay'
|
||||||
|
root.style.position = 'fixed'
|
||||||
|
root.style.right = '20px'
|
||||||
|
root.style.bottom = '24px'
|
||||||
|
root.style.zIndex = '2147483647'
|
||||||
|
root.style.background = 'rgba(17, 21, 32, 0.94)'
|
||||||
|
root.style.border = '1px solid rgba(133, 148, 195, 0.35)'
|
||||||
|
root.style.borderRadius = '12px'
|
||||||
|
root.style.padding = '10px'
|
||||||
|
root.style.boxShadow = '0 8px 24px rgba(0, 0, 0, 0.28)'
|
||||||
|
root.style.backdropFilter = 'blur(6px)'
|
||||||
|
root.style.width = '220px'
|
||||||
|
root.style.fontFamily = 'ui-sans-serif, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif'
|
||||||
|
root.style.color = '#e8edff'
|
||||||
|
|
||||||
|
const title = document.createElement('div')
|
||||||
|
title.textContent = 'Gdown Helper'
|
||||||
|
title.style.fontSize = '12px'
|
||||||
|
title.style.fontWeight = '700'
|
||||||
|
title.style.marginBottom = '8px'
|
||||||
|
|
||||||
|
const action = document.createElement('button')
|
||||||
|
action.type = 'button'
|
||||||
|
action.textContent = '이 영상 다운로드'
|
||||||
|
action.style.width = '100%'
|
||||||
|
action.style.height = '34px'
|
||||||
|
action.style.border = '1px solid #5a69f0'
|
||||||
|
action.style.borderRadius = '8px'
|
||||||
|
action.style.background = '#5a69f0'
|
||||||
|
action.style.color = '#ffffff'
|
||||||
|
action.style.fontSize = '12px'
|
||||||
|
action.style.fontWeight = '700'
|
||||||
|
action.style.cursor = 'pointer'
|
||||||
|
action.addEventListener('click', () => {
|
||||||
|
void enqueueCurrentYoutubePage()
|
||||||
|
})
|
||||||
|
|
||||||
|
const status = document.createElement('div')
|
||||||
|
status.textContent = '클릭 시 gdown 다운로드 모달로 연결'
|
||||||
|
status.style.fontSize = '11px'
|
||||||
|
status.style.marginTop = '8px'
|
||||||
|
status.style.lineHeight = '1.35'
|
||||||
|
status.style.color = '#aeb7d8'
|
||||||
|
|
||||||
|
root.appendChild(title)
|
||||||
|
root.appendChild(action)
|
||||||
|
root.appendChild(status)
|
||||||
|
document.documentElement.appendChild(root)
|
||||||
|
|
||||||
|
ytOverlayRoot = root
|
||||||
|
ytOverlayStatus = status
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchYoutubeRouteChanges(): void {
|
||||||
|
if (ytUrlWatcherTimer !== null) return
|
||||||
|
ytUrlWatcherTimer = window.setInterval(() => {
|
||||||
|
const current = window.location.href
|
||||||
|
if (current === lastObservedUrl) return
|
||||||
|
lastObservedUrl = current
|
||||||
|
ensureYoutubeOverlay()
|
||||||
|
}, 800)
|
||||||
|
|
||||||
|
window.addEventListener('popstate', () => {
|
||||||
|
lastObservedUrl = window.location.href
|
||||||
|
ensureYoutubeOverlay()
|
||||||
|
})
|
||||||
|
|
||||||
|
document.addEventListener('yt-navigate-finish', () => {
|
||||||
|
lastObservedUrl = window.location.href
|
||||||
|
ensureYoutubeOverlay()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureYoutubeOverlay()
|
||||||
|
watchYoutubeRouteChanges()
|
||||||
|
|||||||
Reference in New Issue
Block a user