Initial clean project import
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
/**
|
||||
* 抖音内容解析器 - 优化版
|
||||
* 特点:
|
||||
* 1. 不需要打开浏览器
|
||||
* 2. 支持抖音分享链接格式解析
|
||||
* 3. 智能提取视频文案、链接、话题标签
|
||||
*/
|
||||
|
||||
interface DouyinContent {
|
||||
videoUrl: string; // 提取的视频链接
|
||||
description: string; // 视频文案
|
||||
hashtags: string[]; // 话题标签
|
||||
rawText: string; // 原始文本
|
||||
}
|
||||
|
||||
interface ParseResult {
|
||||
success: boolean;
|
||||
content?: DouyinContent;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
class DouyinContentParser {
|
||||
private static readonly DIRECT_VIDEO_PATTERN = /https?:\/\/[^\s"'<>]+?\.(mp4|mov|m4v|webm|avi|mkv)(\?[^\s"'<>]*)?/i;
|
||||
/**
|
||||
* 从混杂文本中提取抖音内容
|
||||
* 支持格式:
|
||||
* 1. 纯链接:https://v.douyin.com/HjUnkVdzqU/
|
||||
* 2. 分享格式:文案 + 链接 + 说明文字
|
||||
* 3. 富文本:包含话题标签、emoji等
|
||||
*/
|
||||
static parseContent(input: string): ParseResult {
|
||||
try {
|
||||
const raw = input.trim();
|
||||
|
||||
if (!raw) {
|
||||
return {
|
||||
success: false,
|
||||
error: '输入内容为空'
|
||||
};
|
||||
}
|
||||
|
||||
// 1. 提取视频URL
|
||||
const videoUrl = this.extractVideoUrl(raw);
|
||||
if (!videoUrl) {
|
||||
return {
|
||||
success: false,
|
||||
error: '未找到有效的抖音视频链接'
|
||||
};
|
||||
}
|
||||
|
||||
// 2. 提取视频文案(从文本中去掉URL和说明文字后的内容)
|
||||
const description = this.extractDescription(raw, videoUrl);
|
||||
|
||||
// 3. 提取话题标签
|
||||
const hashtags = this.extractHashtags(raw);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
content: {
|
||||
videoUrl,
|
||||
description,
|
||||
hashtags,
|
||||
rawText: raw
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: `解析失败: ${error instanceof Error ? error.message : String(error)}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取视频URL
|
||||
* 支持多种格式:
|
||||
* - https://v.douyin.com/HjUnkVdzqU/
|
||||
* - https://www.douyin.com/video/7123456789
|
||||
* - https://iesdouyin.com/...
|
||||
*/
|
||||
private static extractVideoUrl(text: string): string | null {
|
||||
const directVideoMatch = text.match(this.DIRECT_VIDEO_PATTERN);
|
||||
if (directVideoMatch?.[0]) {
|
||||
return directVideoMatch[0];
|
||||
}
|
||||
|
||||
// 多种URL模式
|
||||
const patterns = [
|
||||
// 短链接格式:https://v.douyin.com/HjUnkVdzqU/
|
||||
/https:\/\/v\.douyin\.com\/[\w_\-@]+\/?/i,
|
||||
// www格式:https://www.douyin.com/video/7123456789
|
||||
/https:\/\/(?:www\.)?douyin\.com\/(?:video|modal)\/\d+/i,
|
||||
// iesdouyin格式
|
||||
/https:\/\/iesdouyin\.com\/(?:share\/video|video)\/\d+/i,
|
||||
// 简化版本(不带https)
|
||||
/v\.douyin\.com\/[\w_\-@]+/i,
|
||||
/douyin\.com\/(?:video|modal)\/\d+/i
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = text.match(pattern);
|
||||
if (match) {
|
||||
let url = match[0];
|
||||
// 补充 https:// 前缀(如果缺失)
|
||||
if (!url.startsWith('http')) {
|
||||
url = 'https://' + url;
|
||||
}
|
||||
// 确保以斜杠结尾(短链接)或不重复(标准链接)
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static isDirectVideoUrl(url: string): boolean {
|
||||
return this.DIRECT_VIDEO_PATTERN.test(url.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取视频文案
|
||||
* 规则:
|
||||
* 1. 从第一个非whitespace字符开始
|
||||
* 2. 到URL出现前的所有文本
|
||||
* 3. 去掉尾部的"复制此链接"等说明文字
|
||||
*/
|
||||
private static extractDescription(text: string, videoUrl: string): string {
|
||||
// 找到URL在文本中的位置
|
||||
const urlIndex = text.indexOf(videoUrl);
|
||||
|
||||
let descriptionText = '';
|
||||
if (urlIndex > 0) {
|
||||
// URL前面的文本可能是文案
|
||||
descriptionText = text.substring(0, urlIndex).trim();
|
||||
} else {
|
||||
// 如果找不到完整URL,尝试提取URL后的内容
|
||||
// 这种情况很少,但作为后备方案
|
||||
const descriptionMatch = text.match(/^([^h]*?)(?:https?:\/\/|$)/);
|
||||
if (descriptionMatch && descriptionMatch[1]) {
|
||||
descriptionText = descriptionMatch[1].trim();
|
||||
}
|
||||
}
|
||||
|
||||
// 清理文案中可能存在的噪音
|
||||
descriptionText = this.cleanDescription(descriptionText);
|
||||
|
||||
return descriptionText;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理文案中的噪音
|
||||
* 1. 去掉行号标记(2.05 03/10 等)
|
||||
* 2. 去掉乱码和特殊前缀
|
||||
* 3. 保留正常内容
|
||||
*/
|
||||
private static cleanDescription(text: string): string {
|
||||
if (!text) return '';
|
||||
|
||||
// 去掉开头的时间戳或版本号(如 "2.05 03/10")
|
||||
let cleaned = text.replace(/^[\d\s\.\/]+/, '').trim();
|
||||
|
||||
// 去掉开头的乱码或特殊字符(如 "i@p.Qx rRX:/" 这样的)
|
||||
// 如果开头包含太多特殊字符和数字的混合,说明是乱码
|
||||
const startMatch = cleaned.match(/^[^\u4e00-\u9fff\w\s]*(.*)$/);
|
||||
if (startMatch && startMatch[1]) {
|
||||
// 如果清理后还有内容,就用清理后的
|
||||
const potential = startMatch[1].trim();
|
||||
if (potential.length > 0) {
|
||||
cleaned = potential;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果清理后发现全是特殊字符或数字,返回空
|
||||
if (/^[\W_]+$/.test(cleaned)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取话题标签
|
||||
* 支持:#话题、#话题名、@用户 等格式
|
||||
*/
|
||||
private static extractHashtags(text: string): string[] {
|
||||
const hashtags: string[] = [];
|
||||
|
||||
// 匹配 #话题 格式(包括中英文)
|
||||
const hashtagPattern = /#[\w\u4e00-\u9fff_]+/g;
|
||||
const matches = text.match(hashtagPattern);
|
||||
|
||||
if (matches) {
|
||||
// 去重并返回
|
||||
hashtags.push(...Array.from(new Set(matches)));
|
||||
}
|
||||
|
||||
return hashtags;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取视频ID(用于后续处理)
|
||||
*/
|
||||
static extractVideoId(videoUrl: string): string | null {
|
||||
// 短链接格式:v.douyin.com/HjUnkVdzqU/ -> HjUnkVdzqU
|
||||
const shortMatch = videoUrl.match(/v\.douyin\.com\/([\w_\-@]+)/i);
|
||||
if (shortMatch) {
|
||||
return shortMatch[1];
|
||||
}
|
||||
|
||||
// 标准链接格式:/video/7123456789 -> 7123456789
|
||||
const standardMatch = videoUrl.match(/\/(?:video|modal)\/(\d+)/i);
|
||||
if (standardMatch) {
|
||||
return standardMatch[1];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为文案生成一个合理的标题
|
||||
* 规则:从文案中截取第一句话,或前N个字符
|
||||
*/
|
||||
static generateTitleFromDescription(description: string, maxLength: number = 30): string {
|
||||
if (!description) {
|
||||
return '分享精彩内容';
|
||||
}
|
||||
|
||||
// 尝试找到第一个句号、问号或感叹号
|
||||
const sentenceEnd = description.match(/[。!?\n]/);
|
||||
let title = '';
|
||||
|
||||
if (sentenceEnd) {
|
||||
title = description.substring(0, sentenceEnd.index).trim();
|
||||
} else {
|
||||
title = description;
|
||||
}
|
||||
|
||||
// 限制长度
|
||||
if (title.length > maxLength) {
|
||||
title = title.substring(0, maxLength) + '...';
|
||||
}
|
||||
|
||||
return title || '精彩分享';
|
||||
}
|
||||
}
|
||||
|
||||
export { DouyinContentParser, DouyinContent, ParseResult };
|
||||
Reference in New Issue
Block a user