feat: add youtube floating quick-download action

This commit is contained in:
tongki078
2026-02-26 12:17:36 +09:00
parent e8b7432594
commit 59226ed6fd
10 changed files with 172 additions and 11 deletions

View File

@@ -140,3 +140,142 @@ function installProgrammaticInterceptors(): void {
}
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()