Files
yaoyaoai/scripts/nodejs/douyin_content_parser.js
fengchuanhn@gmail.com 3db327d93b 22
2026-05-17 12:54:13 +08:00

148 lines
4.2 KiB
JavaScript
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.
"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 };