mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-13 09:28:14 +08:00
471 lines
14 KiB
TypeScript
471 lines
14 KiB
TypeScript
/**
|
||
* @file plugins Mys utils PostParser.ts
|
||
* @description 用于解析Mys数据的工具
|
||
* @author BTMuli<bt-muli@outlook.com>
|
||
* @since Alpha v0.1.2
|
||
*/
|
||
import { PostContent, PostData, PostStructuredContent } from "../interface/post";
|
||
|
||
/**
|
||
* @description 检测链接是否是米游社帖子
|
||
* @since Alpha v0.1.2
|
||
* @param {string} url 链接
|
||
* @returns {boolean} 是否是米游社帖子
|
||
*/
|
||
export function IsMysPost(url: string): boolean {
|
||
const regBBS = /^https:\/\/bbs\.mihoyo\.com\/\w+\/article\/\d+$/;
|
||
const regMYS = /^https:\/\/www\.miyoushe\.com\/\w+\/article\/\d+$/;
|
||
return regBBS.test(url) || regMYS.test(url);
|
||
}
|
||
|
||
/**
|
||
* @description 解析用户帖子,将其转换为 PostStructContent
|
||
* @since Alpha v0.1.1
|
||
* @see PostContent
|
||
* @param {string} content 帖子内容
|
||
* @returns {string} 解析后的内容
|
||
*/
|
||
export function contentParser(content: string): string {
|
||
const data = JSON.parse(content);
|
||
const result: PostStructuredContent[] = [];
|
||
// 遍历 data 属性,值
|
||
Object.keys(data).forEach(key => {
|
||
switch (key) {
|
||
case "describe":
|
||
result.push({
|
||
insert: data.describe,
|
||
});
|
||
break;
|
||
case "imgs":
|
||
for (const image of data.imgs) {
|
||
result.push({
|
||
insert: {
|
||
image: image,
|
||
},
|
||
});
|
||
}
|
||
break;
|
||
default:
|
||
// 如果是其他属性,就直接插入
|
||
result.push({
|
||
insert: JSON.stringify(data[key]),
|
||
});
|
||
}
|
||
});
|
||
return JSON.stringify(result);
|
||
}
|
||
|
||
/**
|
||
* @description 解析Mys数据
|
||
* @since Alpha v0.1.2
|
||
* @param {PostData} post Mys数据
|
||
* @description 为了安全考虑,不会解析所有的属性,只会解析几个常用的属性
|
||
* @returns {string} 解析后的HTML,可作为 v-html 使用
|
||
*/
|
||
export function PostParser(post: PostData): string {
|
||
const postContent = post.post.content;
|
||
let parserData;
|
||
if (postContent.startsWith("<")) {
|
||
parserData = post.post.structured_content;
|
||
} else {
|
||
try {
|
||
parserData = contentParser(post.post.content);
|
||
} catch (error) {
|
||
parserData = post.post.structured_content;
|
||
}
|
||
}
|
||
let jsonData: PostStructuredContent[] = JSON.parse(parserData);
|
||
const doc = document.createElement("div");
|
||
jsonData.forEach((item: any) => {
|
||
const parsed = ParserTransfer(item);
|
||
doc.appendChild(parsed);
|
||
});
|
||
return doc.innerHTML;
|
||
}
|
||
|
||
/**
|
||
* @description 解析中转
|
||
* @since Alpha v0.1.1
|
||
* @param {PostStructuredContent} data Mys数据
|
||
* @returns {HTMLDivElement | HTMLSpanElement} 解析后的中转
|
||
*/
|
||
function ParserTransfer(data: PostStructuredContent): HTMLDivElement | HTMLSpanElement {
|
||
if (typeof data.insert === "string") {
|
||
return TextParser(data);
|
||
} else if (data.insert.image) {
|
||
return ImageParser(data);
|
||
} else if (data.insert.vod) {
|
||
return VideoParser(data);
|
||
} else if (data.insert.video) {
|
||
return VideoParser(data);
|
||
} else if (data.insert.backup_text) {
|
||
return BackupTextParser(data);
|
||
} else if (data.insert.link_card) {
|
||
return LinkCardParser(data);
|
||
} else if (data.insert.divider) {
|
||
return DividerParser(data);
|
||
} else {
|
||
return UnknownParser(data);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @description 解析未知数据
|
||
* @since Alpha v0.1.1
|
||
* @param {PostStructuredContent} data Mys数据
|
||
* @returns {HTMLDivElement} 解析后的未知数据
|
||
*/
|
||
function UnknownParser(data: PostStructuredContent): HTMLDivElement {
|
||
// 创建 div
|
||
const div = document.createElement("div");
|
||
div.classList.add("mys-post-unknown");
|
||
// 创建 code,将数据放入 code
|
||
const code = document.createElement("code");
|
||
code.innerText = JSON.stringify(data);
|
||
// 插入 code
|
||
div.appendChild(code);
|
||
// 返回 div
|
||
return div;
|
||
}
|
||
|
||
/**
|
||
* @description 解析文本
|
||
* @since Alpha
|
||
* @param {PostStructuredContent} data Mys数据
|
||
* @returns {HTMLSpanElement} 解析后的文本
|
||
*/
|
||
function TextParser(data: PostStructuredContent): HTMLSpanElement {
|
||
// 检查数据
|
||
if (typeof data.insert !== "string") {
|
||
throw new Error("data.insert is not a string");
|
||
}
|
||
// 创建文本
|
||
const text = document.createElement("span");
|
||
// 设置文本属性
|
||
if (data.attributes) {
|
||
if (data.attributes.bold) text.style.fontWeight = "bold";
|
||
if (data.attributes.color) text.style.color = data.attributes.color;
|
||
if (data.attributes.link) {
|
||
return LinkTextParser(data);
|
||
}
|
||
}
|
||
// 添加 class
|
||
text.classList.add("mys-post-span");
|
||
// 设置 span 内容
|
||
text.innerText = data.insert;
|
||
// 返回文本
|
||
return text;
|
||
}
|
||
|
||
/**
|
||
* @description 解析链接
|
||
* @since Alpha v0.1.1
|
||
* @param {PostStructuredContent} data Mys数据
|
||
* @returns {HTMLSpanElement} 解析后的链接
|
||
*/
|
||
function LinkTextParser(data: PostStructuredContent): HTMLSpanElement {
|
||
// 检查数据
|
||
if (typeof data.insert !== "string") {
|
||
throw new Error("data.insert is not a string");
|
||
}
|
||
if (!data.attributes) {
|
||
throw new Error("data.attributes is not defined");
|
||
}
|
||
if (!data.attributes.link) {
|
||
throw new Error("data.attributes.link is not defined");
|
||
}
|
||
// 创建图标
|
||
const icon = document.createElement("i");
|
||
icon.classList.add("mdi", "mdi-link-variant");
|
||
// 创建链接
|
||
const link = document.createElement("a");
|
||
const link_url = data.attributes.link;
|
||
link.classList.add("mys-post-link");
|
||
if (IsMysPost(link_url)) {
|
||
link.href = "/post_detail/" + link_url.split("/").pop();
|
||
link.target = "_self";
|
||
} else {
|
||
link.href = link_url;
|
||
link.target = "view_window";
|
||
}
|
||
link.innerText = data.insert;
|
||
// 插入图标作为链接前缀
|
||
link.prepend(icon);
|
||
// 返回链接
|
||
return link;
|
||
}
|
||
|
||
/**
|
||
* @description 解析分割线
|
||
* @since Alpha v0.1.1
|
||
* @param {PostStructuredContent} data Mys数据
|
||
* @returns {HTMLDivElement} 解析后的分割线
|
||
*/
|
||
function DividerParser(data: PostStructuredContent): HTMLDivElement {
|
||
// 数据检查
|
||
if (typeof data.insert === "string") {
|
||
throw new Error("data.insert is a string");
|
||
}
|
||
if (!data.insert.divider) {
|
||
throw new Error("data.insert.divider is not defined");
|
||
}
|
||
// 创建分割线
|
||
const div = document.createElement("div");
|
||
div.classList.add("mys-post-divider");
|
||
// 创建 img
|
||
const img = document.createElement("img");
|
||
if (data.insert.divider === "line_1") {
|
||
img.src =
|
||
"https://mihoyo-community-web.oss-cn-shanghai.aliyuncs.com/upload/2021/01/05/40eb5281cb24042bf34a9f1bcc61eaf5.png";
|
||
} else if (data.insert.divider === "line_2") {
|
||
img.src =
|
||
"https://mihoyo-community-web.oss-cn-shanghai.aliyuncs.com/upload/2021/01/05/477d4c535e965bec1791203aecdfa8e6.png";
|
||
} else if (data.insert.divider === "line_3") {
|
||
img.src =
|
||
"https://mihoyo-community-web.oss-cn-shanghai.aliyuncs.com/upload/2021/01/05/e7047588e912d60ff87a975e037c7606.png";
|
||
} else if (data.insert.divider === "line_4") {
|
||
img.src =
|
||
"https://mihoyo-community-web.oss-cn-shanghai.aliyuncs.com/upload/2022/07/13/line_4.png";
|
||
} else {
|
||
console.error("Unknown divider type", data);
|
||
return UnknownParser(data);
|
||
}
|
||
// 插入 img
|
||
div.appendChild(img);
|
||
// 返回分割线
|
||
return div;
|
||
}
|
||
|
||
/**
|
||
* @description 解析图片
|
||
* @since Alpha v0.1.1
|
||
* @param {PostStructuredContent} data Mys数据
|
||
* @returns {HTMLDivElement} 解析后的图片
|
||
*/
|
||
function ImageParser(data: PostStructuredContent): HTMLDivElement {
|
||
// 检查数据
|
||
if (typeof data.insert === "string") {
|
||
throw new Error("data.insert is a string");
|
||
}
|
||
if (!data.insert.image) {
|
||
throw new Error("data.insert.image is not defined");
|
||
}
|
||
// if (!data.attributes) {
|
||
// throw new Error("data.attributes is not defined");
|
||
// }
|
||
// if (!data.attributes.width) {
|
||
// throw new Error("data.attributes.width is not defined");
|
||
// }
|
||
// if (!data.attributes.height) {
|
||
// throw new Error("data.attributes.height is not defined");
|
||
// }
|
||
const div = document.createElement("div");
|
||
// 创建图片
|
||
const img = document.createElement("img");
|
||
img.src = data.insert.image;
|
||
// 添加 class
|
||
img.classList.add("mys-post-img");
|
||
// 插入图片
|
||
div.appendChild(img);
|
||
// 添加 class
|
||
div.classList.add("mys-post-div");
|
||
// 返回 div
|
||
return div;
|
||
}
|
||
|
||
/**
|
||
* @description 解析视频
|
||
* @since Alpha
|
||
* @param {PostStructuredContent} data Mys数据
|
||
* @returns {HTMLDivElement} 解析后的视频
|
||
*/
|
||
function VideoParser(data: PostStructuredContent): HTMLDivElement {
|
||
// 检查数据
|
||
if (typeof data.insert === "string") {
|
||
throw new Error("data.insert is a string");
|
||
}
|
||
if (!data.insert.vod && !data.insert.video) {
|
||
throw new Error("data.insert.vod is not defined");
|
||
}
|
||
// 创建 div
|
||
const div = document.createElement("div");
|
||
div.classList.add("mys-post-div");
|
||
if (data.insert.vod) {
|
||
// 创建视频
|
||
const video = document.createElement("video");
|
||
video.classList.add("mys-post-vod");
|
||
// 获取 resolutions中size最大的视频
|
||
const resolution = data.insert.vod.resolutions.reduce((prev: any, curr: any) => {
|
||
if (prev.size > curr.size) return prev;
|
||
return curr;
|
||
});
|
||
video.poster = data.insert.vod.cover; // 设置封面
|
||
video.controls = true; // 设置 controls
|
||
// 添加 source
|
||
const source = document.createElement("source");
|
||
source.src = resolution.url;
|
||
source.type = resolution.format === ".mp4" ? "video/mp4" : "video/webm";
|
||
// 插入 source
|
||
video.appendChild(source);
|
||
// 插入 video
|
||
div.appendChild(video);
|
||
} else if (data.insert.video) {
|
||
// 创建 iframe
|
||
const video = document.createElement("iframe");
|
||
video.classList.add("mys-post-iframe");
|
||
// 设置 iframe 属性
|
||
video.src = data.insert.video;
|
||
video.allowFullscreen = true;
|
||
video.sandbox.add("allow-top-navigation", "allow-same-origin", "allow-forms", "allow-scripts");
|
||
// 插入 video
|
||
div.appendChild(video);
|
||
}
|
||
return div;
|
||
}
|
||
|
||
/**
|
||
* @description 解析折叠内容
|
||
* @since Alpha v0.1.1
|
||
* @param {PostStructuredContent} data Mys数据
|
||
* @returns {HTMLDivElement} 解析后的折叠内容
|
||
*/
|
||
function BackupTextParser(data: PostStructuredContent): HTMLDivElement {
|
||
// 检查数据
|
||
if (typeof data.insert === "string") {
|
||
throw new Error("data.insert is a string");
|
||
}
|
||
if (data.insert.backup_text === "[抽奖]") {
|
||
return LotteryParser(data);
|
||
}
|
||
if (!data.insert.fold) {
|
||
throw new Error("data.insert.fold is not defined");
|
||
}
|
||
// 转换
|
||
const titleJson: PostStructuredContent[] = JSON.parse(data.insert.fold.title);
|
||
const contentJson: PostStructuredContent[] = JSON.parse(data.insert.fold.content);
|
||
// 创建 div
|
||
const div = document.createElement("div");
|
||
div.classList.add("mys-post-div");
|
||
// 创建折叠内容
|
||
const details = document.createElement("details");
|
||
details.classList.add("mys-post-details");
|
||
// 创建标题
|
||
const title = document.createElement("summary");
|
||
// 解析标题
|
||
titleJson.forEach(item => {
|
||
const parsed = ParserTransfer(item);
|
||
title.appendChild(parsed);
|
||
});
|
||
// 创建内容
|
||
const content = document.createElement("div");
|
||
contentJson.forEach(item => {
|
||
const parsed = ParserTransfer(item);
|
||
content.appendChild(parsed);
|
||
});
|
||
details.appendChild(title);
|
||
details.appendChild(content);
|
||
div.appendChild(details);
|
||
// 返回 div
|
||
return div;
|
||
}
|
||
|
||
/**
|
||
* @description 解析抽奖
|
||
* @since Alpha v0.1.1
|
||
* @param {PostStructuredContent} data Mys数据
|
||
* @returns {HTMLDivElement} 解析后的抽奖
|
||
*/
|
||
function LotteryParser(data: PostStructuredContent): HTMLDivElement {
|
||
// 检查数据
|
||
if (typeof data.insert === "string") {
|
||
throw new Error("data.insert is a string");
|
||
}
|
||
if (!data.insert.backup_text) {
|
||
throw new Error("data.insert.backup_text is not defined");
|
||
}
|
||
if (data.insert.backup_text !== "[抽奖]") {
|
||
throw new Error("data.insert.backup_text is not [抽奖]");
|
||
}
|
||
if (!data.insert.lottery) {
|
||
throw new Error("data.insert.lottery is not defined");
|
||
}
|
||
// 创建 div
|
||
const div = document.createElement("div");
|
||
// 创建图标
|
||
const icon = document.createElement("i");
|
||
icon.classList.add("mdi", "mdi-gift");
|
||
// 创建标题
|
||
const title = document.createElement("a");
|
||
title.classList.add("mys-post-link");
|
||
title.href = `/lottery/${data.insert.lottery.id}`;
|
||
title.innerText = data.insert.lottery.toast;
|
||
// 插入图标
|
||
title.prepend(icon);
|
||
// 插入标题
|
||
div.appendChild(title);
|
||
// 返回 div
|
||
return div;
|
||
}
|
||
|
||
/**
|
||
* @description 解析链接卡片
|
||
* @since Alpha v0.1.1
|
||
* @param {PostStructuredContent} data Mys数据
|
||
* @returns {HTMLDivElement} 解析后的链接卡片
|
||
*/
|
||
function LinkCardParser(data: PostStructuredContent): HTMLDivElement {
|
||
// 检查数据
|
||
if (typeof data.insert === "string") {
|
||
throw new Error("data.insert is a string");
|
||
}
|
||
if (!data.insert.link_card) {
|
||
throw new Error("data.insert.link_card is not defined");
|
||
}
|
||
// 创建 div
|
||
const div = document.createElement("div");
|
||
// 创建 cover
|
||
const cover = document.createElement("div");
|
||
cover.classList.add("mys-post-link-card-cover");
|
||
// 创建 img
|
||
const img = document.createElement("img");
|
||
img.src = data.insert.link_card.cover;
|
||
// 插入 img
|
||
cover.appendChild(img);
|
||
// 插入 cover
|
||
div.appendChild(cover);
|
||
// 创建 content
|
||
const content = document.createElement("div");
|
||
content.classList.add("mys-post-link-card-content");
|
||
// 创建标题
|
||
const title = document.createElement("div");
|
||
title.classList.add("mys-post-link-card-title");
|
||
title.innerHTML = data.insert.link_card.title;
|
||
// 插入 title
|
||
content.appendChild(title);
|
||
if (data.insert.link_card.price) {
|
||
const price = document.createElement("div");
|
||
price.classList.add("mys-post-link-card-price");
|
||
price.innerHTML = data.insert.link_card.price;
|
||
content.appendChild(price);
|
||
}
|
||
// 创建 button
|
||
const button = document.createElement("a");
|
||
button.classList.add("mys-post-link-card-btn");
|
||
button.innerHTML = (data.insert.link_card.button_text || "详情") + " >";
|
||
const link_url = data.insert.link_card.origin_url;
|
||
if (IsMysPost(link_url)) {
|
||
button.href = "/post_detail/" + link_url.split("/").pop();
|
||
button.target = "_self";
|
||
} else {
|
||
button.href = link_url;
|
||
button.target = "view_window";
|
||
}
|
||
// 插入 button
|
||
content.appendChild(button);
|
||
// 插入 content
|
||
div.appendChild(content);
|
||
// 添加 class
|
||
div.classList.add("mys-post-link-card");
|
||
return div;
|
||
}
|