Skip to content

Commit 5c8eb04

Browse files
nieaoclaude
andcommitted
feat(video): 视频节点 oEmbed 抓标题+封面 (套 absorber-youtube 思路)
参考 ~/.claude/skills/absorber-youtube/helpers.py 的 fetch_video_metadata, 浏览器版改用 oEmbed (noembed.com 包装器, 50+ 平台支持, CORS 友好), 等价 yt-dlp 在前端的替代: - src/utils/linkPreview.js 新增 fetchOembedMetadata(url) - fetchLinkMetadata 路由优化: Bilibili → 官方 API (已有, 不动) 其他视频平台 (YouTube/Vimeo/TikTok/抖音/Twitter/SoundCloud) → 先走 noembed → 失败 fallback Microlink 非视频 → 直接 Microlink (与原行为一致) - YouTube 缩略图自动升级 hqdefault → maxresdefault (高清) - VideoNode/BookmarkNode 已渲染 thumbnail, 不用改 效果: YouTube 节点导入立即拿到标题 + 高清封面, 不再看 Microlink 脸色 (限流/不准) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 21ee6f0 commit 5c8eb04

1 file changed

Lines changed: 53 additions & 1 deletion

File tree

src/utils/linkPreview.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ const MICROLINK_API = 'https://api.microlink.io'
1111
const BILIBILI_API = 'https://api.bilibili.com/x/web-interface/view'
1212
const CORS_PROXY = 'https://api.allorigins.win/raw?url='
1313

14+
// noembed — oEmbed 包装器,浏览器原生 fetch CORS 友好
15+
// 支持 YouTube / Vimeo / Twitter / SoundCloud / Vine / Flickr / Slideshare 等 50+ 平台
16+
// 参考 ~/.claude/skills/absorber-youtube/helpers.py 的 fetch_video_metadata 思路(浏览器版替代 yt-dlp)
17+
const NOEMBED_API = 'https://noembed.com/embed'
18+
1419
/**
1520
* 获取域名 favicon URL
1621
* @param {string} url - 页面 URL
@@ -82,6 +87,44 @@ async function fetchBilibiliMetadata(url) {
8287
}
8388
}
8489

90+
/**
91+
* 通过 noembed (oEmbed 协议) 拉视频元数据
92+
* 参考 absorber-youtube `fetch_video_metadata` 的等价浏览器版本
93+
* 支持 YouTube / Vimeo / TikTok / 抖音 / Twitter / SoundCloud 等
94+
* @param {string} url - 视频页面 URL
95+
* @returns {Promise<Object|null>} - 元数据或 null
96+
*/
97+
async function fetchOembedMetadata(url) {
98+
try {
99+
const resp = await fetch(`${NOEMBED_API}?url=${encodeURIComponent(url)}`)
100+
if (!resp.ok) return null
101+
const data = await resp.json()
102+
// noembed 解析失败时会回 { error: '...' },没有 title 也算失败
103+
if (!data || data.error || !data.title) return null
104+
105+
// 缩略图:noembed 给 thumbnail_url
106+
let thumb = data.thumbnail_url || ''
107+
// YouTube 的高清缩略图:把 hqdefault.jpg 升级到 maxresdefault.jpg(如果可用)
108+
if (thumb && thumb.includes('ytimg.com')) {
109+
thumb = thumb.replace(/\/(hqdefault|mqdefault|sddefault)\.jpg/, '/maxresdefault.jpg')
110+
}
111+
112+
// 描述:noembed 通常没 description,用 author_name 兜底
113+
const desc = data.author_name ? `作者:${data.author_name}` : ''
114+
115+
return {
116+
title: data.title,
117+
description: desc,
118+
image: thumb,
119+
favicon: getFaviconUrl(url),
120+
screenshot: thumb,
121+
}
122+
} catch (e) {
123+
console.warn('noembed 抓取失败:', e?.message || e)
124+
return null
125+
}
126+
}
127+
85128
/**
86129
* 检查图片是否满足最小宽度要求
87130
* @param {string} imageUrl - 图片 URL
@@ -117,7 +160,7 @@ export async function fetchLinkMetadata(url) {
117160
}
118161

119162
// 检测是否为视频 URL
120-
const { isVideo } = detectVideoUrl(url)
163+
const { isVideo, platform } = detectVideoUrl(url)
121164

122165
// Bilibili 视频优先使用官方 API
123166
if (url.includes('bilibili.com') && url.match(/BV[\w]+/)) {
@@ -127,6 +170,15 @@ export async function fetchLinkMetadata(url) {
127170
}
128171
}
129172

173+
// 视频平台:先走 noembed (oEmbed) — 比 Microlink 准很多 (拿到精确标题 + 高清缩略图)
174+
// 参考 absorber-youtube/helpers.py 的 fetch_video_metadata 思路 (yt-dlp 的浏览器替代)
175+
// 覆盖 YouTube / Vimeo / TikTok / 抖音 / Twitter / SoundCloud 等 noembed 支持的平台
176+
if (isVideo && platform && platform !== 'bilibili') {
177+
const oembed = await fetchOembedMetadata(url)
178+
if (oembed) return oembed
179+
// 失败继续往下兜底 Microlink
180+
}
181+
130182
// 使用 Microlink API 获取元数据
131183
try {
132184
const response = await fetch(`${MICROLINK_API}?url=${encodeURIComponent(url)}`)

0 commit comments

Comments
 (0)