feat: 하위 폴더에 "목록 뽑기" 추가 (영상 ID + 실제 주소 txt 다운로드)

플레이리스트 추가 버튼 옆에 "목록 뽑기"를 두어, 누르면
`<영상ID> <실제 영상 주소>` 형식의 txt 를 내려받는다. YouTube 는
watch?v=/embed/shorts/live/youtu.be 등 어떤 형식이든 11자 영상 ID 를
뽑아 https://youtu.be/ID 로 간결화하고, 그 외(네이버 등) source_url 은
원본 그대로 출력한다. 업로드 영상처럼 주소가 없으면 ID 만 남긴다.

GET /op/folder/:topName/:subName/list.txt (3세그먼트라 폴더 라우트와
충돌 없음). 파일명은 폴더 경로 기반, RFC5987 UTF-8 + ASCII 폴백.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Claude (owner)
2026-06-14 01:50:54 +09:00
parent 0abd21533b
commit 745b1db1cc
2 changed files with 47 additions and 0 deletions

View File

@@ -306,6 +306,51 @@ opRouter.get(
}
)
// ─── 목록 뽑기 (영상 ID + 실제 영상 주소 txt) ──────────────────────────
/** YouTube URL 의 11자 영상 ID 추출. watch?v= / youtu.be / embed / shorts / live 형식 지원. */
function extractYoutubeId(url: string): string | null {
const m = url.match(
/(?:youtu\.be\/|youtube\.com\/(?:watch\?(?:[^#]*&)?v=|embed\/|shorts\/|live\/|v\/))([\w-]{11})/
)
return m ? m[1] : null
}
/** 영상의 "실제 주소". YouTube 는 https://youtu.be/ID 로 간결화, 그 외(네이버 등)는 원본 그대로. */
function videoSourceUrl(sourceUrl: string | null): string {
if (!sourceUrl) return ''
const ytId = extractYoutubeId(sourceUrl)
return ytId ? `https://youtu.be/${ytId}` : sourceUrl
}
opRouter.get(
'/op/folder/:topName/:subName/list.txt',
requireAuth,
(req, res, next) => {
try {
const folder = getFolderByPath(collectFolderSegments(req.params))
if (!folder) {
res.status(404).send('폴더를 찾을 수 없습니다.')
return
}
const lines = listVideosInFolder(folder.id).map((v) =>
`${v.id} ${videoSourceUrl(v.sourceUrl)}`.trimEnd()
)
const body = lines.join('\n') + (lines.length ? '\n' : '')
const baseName = folderPathNames(folder.id).join('_') || 'list'
const asciiName = baseName.replace(/[^\x20-\x7e]+/g, '_').replace(/["\\]/g, '_')
res.setHeader('Content-Type', 'text/plain; charset=utf-8')
res.setHeader(
'Content-Disposition',
`attachment; filename="${asciiName}.txt"; filename*=UTF-8''${encodeURIComponent(baseName)}.txt`
)
res.send(body)
} catch (err) {
next(err)
}
}
)
// ─── 플레이리스트 임시저장(draft) ──────────────────────────────────────
// 다운로드 시작 전 수정해둔 값(URL + 항목별 제목/ID/구간)을 폴더당 1개 저장/복원.