22
This commit is contained in:
147
scripts/nodejs/douyin_content_parser.js
Normal file
147
scripts/nodejs/douyin_content_parser.js
Normal file
@@ -0,0 +1,147 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* 从混杂文本中提取抖音分享内容(链接、文案、话题)。
|
||||
* 自 zhenqianba DouyinContentParser 移植,CommonJS 导出供 require。
|
||||
*/
|
||||
|
||||
const DIRECT_VIDEO_PATTERN =
|
||||
/https?:\/\/[^\s"'<>]+?\.(mp4|mov|m4v|webm|avi|mkv)(\?[^\s"'<>]*)?/i;
|
||||
|
||||
class DouyinContentParser {
|
||||
/**
|
||||
* @param {string} input
|
||||
* @returns {{ success: true, content: object } | { success: false, error: string }}
|
||||
*/
|
||||
static parseContent(input) {
|
||||
try {
|
||||
const raw = String(input || "").trim();
|
||||
if (!raw) {
|
||||
return { success: false, error: "输入内容为空" };
|
||||
}
|
||||
const videoUrl = this.extractVideoUrl(raw);
|
||||
if (!videoUrl) {
|
||||
return { success: false, error: "未找到有效的抖音视频链接" };
|
||||
}
|
||||
const description = this.extractDescription(raw, videoUrl);
|
||||
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)}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static extractVideoUrl(text) {
|
||||
const directVideoMatch = text.match(DIRECT_VIDEO_PATTERN);
|
||||
if (directVideoMatch && directVideoMatch[0]) {
|
||||
return directVideoMatch[0];
|
||||
}
|
||||
const patterns = [
|
||||
/https:\/\/v\.douyin\.com\/[\w_\-@]+\/?/i,
|
||||
/https:\/\/(?:www\.)?douyin\.com\/(?:video|modal)\/\d+/i,
|
||||
/https:\/\/iesdouyin\.com\/(?:share\/video|video)\/\d+/i,
|
||||
/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];
|
||||
if (!url.startsWith("http")) {
|
||||
url = "https://" + url;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static isDirectVideoUrl(url) {
|
||||
return DIRECT_VIDEO_PATTERN.test(String(url || "").trim());
|
||||
}
|
||||
|
||||
static extractDescription(text, videoUrl) {
|
||||
const urlIndex = text.indexOf(videoUrl);
|
||||
let descriptionText = "";
|
||||
if (urlIndex > 0) {
|
||||
descriptionText = text.substring(0, urlIndex).trim();
|
||||
} else {
|
||||
const descriptionMatch = text.match(/^([^h]*?)(?:https?:\/\/|$)/);
|
||||
if (descriptionMatch && descriptionMatch[1]) {
|
||||
descriptionText = descriptionMatch[1].trim();
|
||||
}
|
||||
}
|
||||
return this.cleanDescription(descriptionText);
|
||||
}
|
||||
|
||||
static cleanDescription(text) {
|
||||
if (!text) return "";
|
||||
let cleaned = text.replace(/^[\d\s./]+/, "").trim();
|
||||
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;
|
||||
}
|
||||
|
||||
static extractHashtags(text) {
|
||||
const hashtagPattern = /#[\w\u4e00-\u9fff_]+/g;
|
||||
const matches = text.match(hashtagPattern);
|
||||
if (!matches) return [];
|
||||
return Array.from(new Set(matches));
|
||||
}
|
||||
|
||||
static extractVideoId(videoUrl) {
|
||||
const shortMatch = videoUrl.match(/v\.douyin\.com\/([\w_\-@]+)/i);
|
||||
if (shortMatch) return shortMatch[1];
|
||||
const standardMatch = videoUrl.match(/\/(?:video|modal)\/(\d+)/i);
|
||||
if (standardMatch) return standardMatch[1];
|
||||
return null;
|
||||
}
|
||||
|
||||
static generateTitleFromDescription(description, maxLength = 30) {
|
||||
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 || "精彩分享";
|
||||
}
|
||||
|
||||
static quickExtractUrl(text) {
|
||||
const r = this.parseContent(text);
|
||||
return r.success && r.content ? r.content.videoUrl : null;
|
||||
}
|
||||
|
||||
static quickExtractDescription(text) {
|
||||
const r = this.parseContent(text);
|
||||
return r.success && r.content ? r.content.description : null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { DouyinContentParser, DIRECT_VIDEO_PATTERN };
|
||||
Reference in New Issue
Block a user