feat: add youtube floating quick-download action
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user