171 lines
5.7 KiB
TypeScript
171 lines
5.7 KiB
TypeScript
import { Context } from 'hono';
|
||
import { Bindings } from '../../bindings';
|
||
|
||
export const importComments = async (c: Context<{ Bindings: Bindings }>) => {
|
||
try {
|
||
const body = await c.req.json();
|
||
const rawComments = Array.isArray(body) ? body : [body];
|
||
|
||
if (rawComments.length === 0) {
|
||
return c.json({ message: '导入数据为空' }, 400);
|
||
}
|
||
|
||
// 映射 Twikoo / Artalk 数据结构到 CWD 结构
|
||
const comments = rawComments.map((item: any) => {
|
||
// Twikoo 特征检测
|
||
const isTwikoo =
|
||
item.href !== undefined || item.nick !== undefined || item.comment !== undefined;
|
||
// Artalk 特征检测 (page_key 是 Artalk 特有的)
|
||
const isArtalk = item.page_key !== undefined && item.content !== undefined;
|
||
|
||
if (isArtalk) {
|
||
// Artalk 映射逻辑
|
||
// 处理 ID: Artalk ID 通常是数字
|
||
let id = undefined;
|
||
if (typeof item.id === 'number') {
|
||
id = item.id;
|
||
} else if (typeof item.id === 'string' && /^\d+$/.test(item.id)) {
|
||
id = parseInt(item.id, 10);
|
||
}
|
||
|
||
// 处理时间
|
||
let created = Date.now();
|
||
if (item.created_at) {
|
||
created = new Date(item.created_at).getTime();
|
||
}
|
||
|
||
return {
|
||
id, // >>> id
|
||
created, // >>> created_at 转为时间戳
|
||
post_slug: item.page_key || '', // >>> page_key
|
||
name: item.nick || 'Anonymous', // >>> nick
|
||
email: item.email || '', // >>> email
|
||
url: item.link || null, // >>> link
|
||
ip_address: item.ip || null, // >>> ip
|
||
device: null, // >>> 保持空
|
||
os: null, // >>> 保持空
|
||
browser: null, // >>> 保持空
|
||
ua: item.ua || null, // >>> ua
|
||
content_text: item.content || '', // >>> content
|
||
content_html: item.content || '', // >>> content
|
||
parent_id: null, // >>> 保持 null
|
||
status: 'approved' // >>> 保持 "approved"
|
||
};
|
||
}
|
||
|
||
if (isTwikoo) {
|
||
// 处理 ID: 如果 _id 是数字则保留,否则丢弃(让数据库自增)
|
||
// Twikoo 的 _id 通常是 ObjectId 字符串,无法直接存入 INTEGER PRIMARY KEY
|
||
// 除非 _id 恰好是数字
|
||
let id = undefined;
|
||
if (typeof item._id === 'number') {
|
||
id = item._id;
|
||
} else if (typeof item._id === 'string' && /^\d+$/.test(item._id)) {
|
||
id = parseInt(item._id, 10);
|
||
}
|
||
|
||
// 处理时间
|
||
let created = Date.now();
|
||
if (item.created) {
|
||
// 支持时间戳或 ISO 字符串
|
||
created = new Date(item.created).getTime();
|
||
}
|
||
|
||
return {
|
||
id, // 可能为 undefined
|
||
created, // >>> created
|
||
post_slug: item.href || "", // >>> href
|
||
name: item.nick || "Anonymous", // >>> nick
|
||
email: item.mail || "", // >>> mail
|
||
url: item.link || null, // >>> link
|
||
ip_address: item.ip || null, // >>> ip
|
||
device: null, // >>> 保持空
|
||
os: null, // >>> 保持空
|
||
browser: null, // >>> 保持空
|
||
ua: item.ua || null, // >>> ua
|
||
content_text: item.comment || "", // >>> comment
|
||
content_html: item.comment || "", // >>> comment
|
||
parent_id: null, // >>> 保持 null
|
||
status: "approved" // >>> approved
|
||
};
|
||
}
|
||
|
||
// 否则假设已经是 CWD 格式
|
||
return item;
|
||
});
|
||
|
||
// 按 ID 升序排序,防止因外键约束导致插入失败(子评论先于父评论插入)
|
||
// 对于 Twikoo 导入,id 可能不存在,或者被重置。
|
||
// 如果 parent_id 全部为 null,则排序其实不重要(没有依赖)。
|
||
comments.sort((a: any, b: any) => {
|
||
const idA = a.id || 0;
|
||
const idB = b.id || 0;
|
||
return idA - idB;
|
||
});
|
||
|
||
const stmts = comments.map((comment: any) => {
|
||
const {
|
||
id,
|
||
created,
|
||
post_slug,
|
||
name,
|
||
email,
|
||
url,
|
||
ip_address,
|
||
device,
|
||
os,
|
||
browser,
|
||
ua,
|
||
content_text,
|
||
content_html,
|
||
parent_id,
|
||
status
|
||
} = comment;
|
||
|
||
const fields = [
|
||
'created', 'post_slug', 'name', 'email', 'url',
|
||
'ip_address', 'device', 'os', 'browser', 'ua',
|
||
'content_text', 'content_html', 'parent_id', 'status'
|
||
];
|
||
const values = [
|
||
created || Date.now(),
|
||
post_slug || "",
|
||
name || "Anonymous",
|
||
email || "",
|
||
url || null,
|
||
ip_address || null,
|
||
device || null,
|
||
os || null,
|
||
browser || null,
|
||
ua || null,
|
||
content_text || "",
|
||
content_html || "",
|
||
parent_id || null,
|
||
status || "approved"
|
||
];
|
||
|
||
if (id !== undefined && id !== null) {
|
||
fields.unshift('id');
|
||
values.unshift(id);
|
||
}
|
||
|
||
const placeholders = fields.map(() => '?').join(', ');
|
||
const sql = `INSERT OR REPLACE INTO Comment (${fields.join(', ')}) VALUES (${placeholders})`;
|
||
|
||
return c.env.CWD_DB.prepare(sql).bind(...values);
|
||
});
|
||
|
||
// 批量执行,每批 50 条
|
||
const BATCH_SIZE = 50;
|
||
for (let i = 0; i < stmts.length; i += BATCH_SIZE) {
|
||
const batch = stmts.slice(i, i + BATCH_SIZE);
|
||
await c.env.CWD_DB.batch(batch);
|
||
}
|
||
|
||
return c.json({ message: `成功导入 ${comments.length} 条评论` });
|
||
} catch (e: any) {
|
||
console.error(e);
|
||
return c.json({ message: e.message || '导入失败' }, 500);
|
||
}
|
||
};
|