video_nowm/src/index.ts

161 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { writeFileSync } from 'fs'
import { join } from 'path'
const app = new Hono()
const DOWNLOAD_KEY = 'djl'
app.use('/api/*', cors())
app.get('/', (c) => {
return c.text('抖音视频解析API - 使用 /api/parse POST 提交 shareUrl 参数形如https://v.douyin.com/IqPTpKdCngY/')
})
// 获取重定向地址
async function getRedirectedUrl(url: string): Promise<string> {
try {
const response = await fetch(url, {
method: 'HEAD',
redirect: 'follow',
})
return response.url
} catch (e) {
console.error('Error fetching URL:', e)
throw new Error('Failed to fetch the URL')
}
}
app.post('/api/parse', async (c) => {
try {
let body
try {
body = await c.req.json()
} catch (e) {
return c.json({ error: 'Invalid request body' }, 400)
}
const shareUrl = body.shareUrl || undefined
if (!shareUrl) {
return c.json({ error: 'Missing shareUrl parameter' }, 400)
}
let videoId: string
const redirectedUrl = await getRedirectedUrl(shareUrl)
const idMatch = redirectedUrl.match(/(\d+)/)
if (!idMatch) {
return c.json({ error: 'Failed to extract video ID from URL' }, 400)
}
videoId = idMatch[1]
const headers = {
'User-Agent':
'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36',
Referer: 'https://www.douyin.com/?is_from_mobile_home=1&recommend=1',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
}
const url = `https://www.iesdouyin.com/share/video/${videoId}/`
const response = await fetch(url, { headers })
if (!response.ok) {
return c.json({ error: 'Failed to fetch video data' }, 500)
}
const html = await response.text()
const routerDataMatch = html.match(/_ROUTER_DATA\s*=\s*(\{.*?\});/)
if (!routerDataMatch) {
return c.json({ error: 'Failed to extract router data' }, 500)
}
const jsonData = JSON.parse(routerDataMatch[1])
const itemList = jsonData?.loaderData?.['video_(id)/page']?.videoInfoRes?.item_list?.[0]
if (!itemList) {
return c.json({ error: '视频数据格式异常' }, 500)
}
const nickname = itemList.author?.nickname
const title = itemList.desc
const awemeId = itemList.aweme_id
const video = itemList.video?.play_addr?.uri
const video2 = await getRedirectedUrl(
itemList.video?.play_addr?.url_list?.[0]
.replace(/playwm/, 'play')
.replace(/ratio=\d+p/, 'ratio=3840p'),
)
const videoUrl = video
? video.includes('mp3')
? video
: `https://www.douyin.com/aweme/v1/play/?video_id=${video}`
: null
const cover = itemList.video?.cover?.url_list?.[0]
// const images = itemList.images
if (!nickname || !videoUrl) {
return c.json({
success: false,
message: '解析失败,可能是视频已被删除或者接口变更',
})
}
return c.json({
success: true,
message: '解析成功',
data: {
name: nickname,
awemeId: awemeId,
title: title,
video: videoUrl,
video2: video2 || null,
cover: cover,
// images: images ? images.map((image: any) => image.url_list[0]) : [],
// type: images ? '图集' : '视频',
},
})
} catch (error) {
console.error('解析错误:', error)
return c.json({ error: '服务器内部错误' }, 500)
}
})
app.get('/api/download', async (c) => {
const videoUrl = c.req.query('url')
const providedKey = c.req.query('key')
if (!providedKey) {
return c.json({ error: 'Missing download key' }, 401)
}
if (!videoUrl) {
return c.json({ error: 'Missing video URL' }, 400)
}
try {
// 验证密钥
if (providedKey !== DOWNLOAD_KEY) {
return c.json({ error: 'Invalid download key' }, 403)
}
const headers = {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15',
Referer: 'https://www.douyin.com/',
Accept: '*/*',
}
const response = await fetch(decodeURIComponent(videoUrl), { headers })
if (!response.ok) {
return c.text('Video not found', 404)
}
// 设置下载响应头
c.header('Content-Type', 'video/mp4')
c.header('Content-Disposition', 'attachment; filename="douyin_video.mp4"')
c.header('Content-Length', response.headers.get('Content-Length') || '0')
return new Response(response.body, {
headers: c.res.headers,
})
} catch (error) {
console.error('下载错误:', error)
return c.text('Internal server error', 500)
}
})
export default {
port: 30001,
fetch: app.fetch,
}