koishi-plugin-fimtale-api 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +4 -12
- package/lib/index.js +449 -222
- package/package.json +5 -3
- package/readme.md +3 -1
package/lib/index.d.ts
CHANGED
|
@@ -17,20 +17,12 @@ export interface Config {
|
|
|
17
17
|
apiUrl: string;
|
|
18
18
|
apiKey: string;
|
|
19
19
|
apiPass: string;
|
|
20
|
+
cookies: string;
|
|
20
21
|
pollInterval: number;
|
|
21
22
|
autoParseLink: boolean;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
messages: {
|
|
26
|
-
subSuccess: string;
|
|
27
|
-
subExists: string;
|
|
28
|
-
unsubSuccess: string;
|
|
29
|
-
notFound: string;
|
|
30
|
-
updateAlert: string;
|
|
31
|
-
infoTemplate: string;
|
|
32
|
-
errorApi: string;
|
|
33
|
-
};
|
|
23
|
+
deviceWidth: number;
|
|
24
|
+
deviceHeight: number;
|
|
25
|
+
fontSize: number;
|
|
34
26
|
}
|
|
35
27
|
export declare const Config: Schema<Config>;
|
|
36
28
|
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
1
2
|
var __defProp = Object.defineProperty;
|
|
2
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
7
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
8
|
var __export = (target, all) => {
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -27,30 +37,19 @@ __export(src_exports, {
|
|
|
27
37
|
});
|
|
28
38
|
module.exports = __toCommonJS(src_exports);
|
|
29
39
|
var import_koishi = require("koishi");
|
|
40
|
+
var import_crypto = __toESM(require("crypto"));
|
|
30
41
|
var name = "fimtale-watcher";
|
|
31
42
|
var inject = ["puppeteer", "database", "http"];
|
|
32
43
|
var Config = import_koishi.Schema.object({
|
|
33
44
|
apiUrl: import_koishi.Schema.string().default("https://fimtale.com/api/v1").description("Fimtale API 基础路径"),
|
|
34
45
|
apiKey: import_koishi.Schema.string().role("secret").required().description("API Key (必填)"),
|
|
35
46
|
apiPass: import_koishi.Schema.string().role("secret").required().description("API Pass (必填)"),
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
import_koishi.Schema.const("dark").description("暗黑"),
|
|
43
|
-
import_koishi.Schema.const("sepia").description("羊皮纸(护眼)")
|
|
44
|
-
]).default("sepia").description("阅读器图片生成的背景主题"),
|
|
45
|
-
messages: import_koishi.Schema.object({
|
|
46
|
-
subSuccess: import_koishi.Schema.string().default("✅ 订阅成功!\n📖 标题:{title}\n✍️ 作者:{author}\n当前 {count} 个回复"),
|
|
47
|
-
subExists: import_koishi.Schema.string().default("⚠️ 你已经订阅过这个帖子了。"),
|
|
48
|
-
unsubSuccess: import_koishi.Schema.string().default("❎ 已取消订阅帖子:{id}"),
|
|
49
|
-
notFound: import_koishi.Schema.string().default("❌ 帖子不存在或 API 请求失败。"),
|
|
50
|
-
updateAlert: import_koishi.Schema.string().default("📢 <b>{title}</b> 有新动态!\n\n最新回复:{lastUser}\n回复总数:{count}\n\n传送门:{url}"),
|
|
51
|
-
infoTemplate: import_koishi.Schema.string().default("📄 <b>{title}</b>\n作者:{author}\n热度:{views} | 回复:{count}\n最后更新:{time}\n\n{preview}"),
|
|
52
|
-
errorApi: import_koishi.Schema.string().default("❌ API 连接失败,请检查配置。")
|
|
53
|
-
}).description("自定义提示文案 (支持 {变量} 替换)")
|
|
47
|
+
cookies: import_koishi.Schema.string().role("secret").description("浏览器 Cookie (用于解除安全模式,必填)"),
|
|
48
|
+
pollInterval: import_koishi.Schema.number().default(60 * 1e3).description("追更轮询间隔(ms)"),
|
|
49
|
+
autoParseLink: import_koishi.Schema.boolean().default(true).description("自动解析链接为预览卡片"),
|
|
50
|
+
deviceWidth: import_koishi.Schema.number().default(390).description("阅读器渲染宽度(px)"),
|
|
51
|
+
deviceHeight: import_koishi.Schema.number().default(844).description("阅读器渲染高度(px)"),
|
|
52
|
+
fontSize: import_koishi.Schema.number().default(20).description("正文字号(px)")
|
|
54
53
|
});
|
|
55
54
|
function apply(ctx, config) {
|
|
56
55
|
ctx.model.extend("fimtale_subs", {
|
|
@@ -59,248 +58,476 @@ function apply(ctx, config) {
|
|
|
59
58
|
threadId: "string",
|
|
60
59
|
lastCount: "integer",
|
|
61
60
|
lastCheck: "integer"
|
|
62
|
-
}, {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
}, { primary: "id", autoInc: true });
|
|
62
|
+
const sleep = /* @__PURE__ */ __name((ms) => new Promise((resolve) => setTimeout(resolve, ms)), "sleep");
|
|
63
|
+
const formatDate = /* @__PURE__ */ __name((timestamp) => {
|
|
64
|
+
if (!timestamp) return "未知日期";
|
|
65
|
+
const date = new Date(timestamp * 1e3);
|
|
66
|
+
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`;
|
|
67
|
+
}, "formatDate");
|
|
66
68
|
const stripHtml = /* @__PURE__ */ __name((html) => {
|
|
67
69
|
if (!html) return "";
|
|
68
70
|
return html.replace(/<[^>]+>/g, "").replace(/ /g, " ").replace(/\s+/g, " ").trim();
|
|
69
71
|
}, "stripHtml");
|
|
70
|
-
const
|
|
71
|
-
if (!
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
const extractImage = /* @__PURE__ */ __name((html) => {
|
|
73
|
+
if (!html) return null;
|
|
74
|
+
const match = html.match(/<img[^>]+src="([^">]+)"/);
|
|
75
|
+
return match ? match[1] : null;
|
|
76
|
+
}, "extractImage");
|
|
77
|
+
const generateGradient = /* @__PURE__ */ __name((str) => {
|
|
78
|
+
const hash = import_crypto.default.createHash("md5").update(str || "default").digest("hex");
|
|
79
|
+
const c1 = "#" + hash.substring(0, 6);
|
|
80
|
+
const c2 = "#" + hash.substring(6, 12);
|
|
81
|
+
return `linear-gradient(135deg, ${c1} 0%, ${c2} 100%)`;
|
|
82
|
+
}, "generateGradient");
|
|
83
|
+
const cleanContent = /* @__PURE__ */ __name((html) => {
|
|
84
|
+
if (!html) return "";
|
|
85
|
+
return html.replace(/<p>\s* \s*<\/p>/gi, "").replace(/<p>\s*<br\s*\/?>\s*<\/p>/gi, "").replace(/<p>\s*<\/p>/gi, "").replace(/(<br\s*\/?>){2,}/gi, "<br>").replace(/margin-bottom:\s*\d+px/gi, "");
|
|
86
|
+
}, "cleanContent");
|
|
87
|
+
const fontStack = '"Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", "WenQuanYi Micro Hei", Arial, sans-serif';
|
|
88
|
+
const injectCookies = /* @__PURE__ */ __name(async (page) => {
|
|
89
|
+
if (!config.cookies) return;
|
|
90
|
+
const cookies = config.cookies.split(";").map((pair) => {
|
|
91
|
+
const parts = pair.trim().split("=");
|
|
92
|
+
if (parts.length < 2) return null;
|
|
93
|
+
return { name: parts[0].trim(), value: parts.slice(1).join("=").trim(), domain: "fimtale.com", path: "/" };
|
|
94
|
+
}).filter((c) => c !== null);
|
|
95
|
+
if (cookies.length) await page.setCookie(...cookies);
|
|
96
|
+
}, "injectCookies");
|
|
77
97
|
const fetchThread = /* @__PURE__ */ __name(async (threadId) => {
|
|
78
98
|
try {
|
|
79
|
-
const
|
|
99
|
+
const url = `${config.apiUrl}/t/${threadId}`;
|
|
80
100
|
const params = { APIKey: config.apiKey, APIPass: config.apiPass };
|
|
81
|
-
const res = await ctx.http.get(
|
|
82
|
-
if (
|
|
83
|
-
return { valid: false };
|
|
84
|
-
}
|
|
85
|
-
const info = res.TopicInfo;
|
|
101
|
+
const res = await ctx.http.get(url, { params });
|
|
102
|
+
if (res.Status !== 1 || !res.TopicInfo) return { valid: false, msg: res.ErrorMessage || "API 返回错误" };
|
|
86
103
|
return {
|
|
87
104
|
valid: true,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
menu: res.Menu || []
|
|
91
|
-
// 目录数组
|
|
92
|
-
// 提取常用字段方便使用
|
|
93
|
-
id: info.ID,
|
|
94
|
-
title: info.Title,
|
|
95
|
-
author: info.UserName,
|
|
96
|
-
views: info.Views,
|
|
97
|
-
comments: info.Comments,
|
|
98
|
-
lastTime: formatTime(info.LastTime),
|
|
99
|
-
lastUser: info.LastUser,
|
|
100
|
-
content: info.Content,
|
|
101
|
-
dateCreated: info.DateCreated
|
|
105
|
+
data: res.TopicInfo,
|
|
106
|
+
parent: res.ParentInfo,
|
|
107
|
+
menu: res.Menu || []
|
|
102
108
|
};
|
|
103
109
|
} catch (e) {
|
|
104
|
-
|
|
105
|
-
return { valid: false };
|
|
110
|
+
return { valid: false, msg: "网络请求失败" };
|
|
106
111
|
}
|
|
107
112
|
}, "fetchThread");
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
threadId,
|
|
117
|
-
lastCount: data.comments,
|
|
118
|
-
lastCheck: Date.now()
|
|
119
|
-
});
|
|
120
|
-
return formatText(config.messages.subSuccess, {
|
|
121
|
-
id: threadId,
|
|
122
|
-
title: data.title,
|
|
123
|
-
author: data.author,
|
|
124
|
-
count: data.comments
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
ctx.command("ft.unsub <threadId:string>", "取消订阅").action(async ({ session }, threadId) => {
|
|
128
|
-
if (!threadId) return "请输入帖子ID。";
|
|
129
|
-
const res = await ctx.database.remove("fimtale_subs", { cid: session.cid, threadId });
|
|
130
|
-
return res.matched ? formatText(config.messages.unsubSuccess, { id: threadId }) : "未找到对应订阅。";
|
|
131
|
-
});
|
|
132
|
-
ctx.command("ft.info <threadId:string>", "查询帖子详情").action(async (_, threadId) => {
|
|
133
|
-
if (!threadId) return "请输入ID。";
|
|
134
|
-
const data = await fetchThread(threadId);
|
|
135
|
-
if (!data.valid) return config.messages.notFound;
|
|
136
|
-
let preview = "";
|
|
137
|
-
if (config.showPreview) {
|
|
138
|
-
const rawText = stripHtml(data.content);
|
|
139
|
-
preview = rawText.length > config.previewLength ? rawText.substring(0, config.previewLength) + "..." : rawText;
|
|
113
|
+
const fetchRandomId = /* @__PURE__ */ __name(async () => {
|
|
114
|
+
try {
|
|
115
|
+
const headers = config.cookies ? { Cookie: config.cookies } : {};
|
|
116
|
+
const html = await ctx.http.get("https://fimtale.com/rand", { responseType: "text", headers });
|
|
117
|
+
let match = html.match(/FimTale\.topic\.init\((\d+)/) || html.match(/data-clipboard-text=".*?\/t\/(\d+)"/);
|
|
118
|
+
return match ? match[1] : null;
|
|
119
|
+
} catch (e) {
|
|
120
|
+
return null;
|
|
140
121
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (!data.valid) return "❌ 读取失败,可能是网络问题或帖子不存在。";
|
|
154
|
-
const info = data.info;
|
|
155
|
-
const menu = data.menu;
|
|
156
|
-
let prevId = null;
|
|
157
|
-
let nextId = null;
|
|
158
|
-
if (menu && menu.length > 0) {
|
|
159
|
-
const currentIndex = menu.findIndex((item) => item.ID.toString() === threadId);
|
|
160
|
-
if (currentIndex !== -1) {
|
|
161
|
-
if (currentIndex > 0) prevId = menu[currentIndex - 1].ID;
|
|
162
|
-
if (currentIndex < menu.length - 1) nextId = menu[currentIndex + 1].ID;
|
|
122
|
+
}, "fetchRandomId");
|
|
123
|
+
const searchThreads = /* @__PURE__ */ __name(async (keyword) => {
|
|
124
|
+
let page;
|
|
125
|
+
try {
|
|
126
|
+
page = await ctx.puppeteer.page();
|
|
127
|
+
await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36");
|
|
128
|
+
await injectCookies(page);
|
|
129
|
+
const searchUrl = `https://fimtale.com/topics?q=${encodeURIComponent(keyword)}`;
|
|
130
|
+
await page.goto(searchUrl, { waitUntil: "networkidle2", timeout: 25e3 });
|
|
131
|
+
try {
|
|
132
|
+
await page.waitForSelector(".card", { timeout: 5e3 });
|
|
133
|
+
} catch {
|
|
163
134
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
135
|
+
const results = await page.evaluate(() => {
|
|
136
|
+
const items = [];
|
|
137
|
+
const cards = document.querySelectorAll(".card.topic-card");
|
|
138
|
+
cards.forEach((card) => {
|
|
139
|
+
if (items.length >= 6) return;
|
|
140
|
+
const link = card.querySelector('a[href^="/t/"]');
|
|
141
|
+
const href = link?.getAttribute("href");
|
|
142
|
+
const idMatch = href?.match(/^\/t\/(\d+)$/);
|
|
143
|
+
if (!idMatch) return;
|
|
144
|
+
const id = idMatch[1];
|
|
145
|
+
if (items.some((i) => i.id === id)) return;
|
|
146
|
+
let title = "";
|
|
147
|
+
const titleEl = card.querySelector(".card-title");
|
|
148
|
+
if (titleEl) title = titleEl.textContent?.trim() || "";
|
|
149
|
+
else title = link.textContent?.trim() || "";
|
|
150
|
+
let author = "未知";
|
|
151
|
+
const authorEl = card.querySelector('a[href^="/u/"] span.grey-text');
|
|
152
|
+
if (authorEl) author = authorEl.textContent?.trim() || "";
|
|
153
|
+
let cover = void 0;
|
|
154
|
+
const imgEl = card.querySelector(".card-image img");
|
|
155
|
+
if (imgEl) {
|
|
156
|
+
const src = imgEl.getAttribute("src");
|
|
157
|
+
if (src && (!src.includes("avatar") || src.includes("upload"))) cover = src;
|
|
158
|
+
}
|
|
159
|
+
const tags = [];
|
|
160
|
+
let status = "";
|
|
161
|
+
card.querySelectorAll(".main-tag-set div").forEach((b) => {
|
|
162
|
+
const t = b.textContent?.trim();
|
|
163
|
+
if (t) tags.push(t);
|
|
164
|
+
});
|
|
165
|
+
card.querySelectorAll(".chip").forEach((c) => {
|
|
166
|
+
const t = c.textContent?.trim();
|
|
167
|
+
if (!t) return;
|
|
168
|
+
if (["连载中", "已完结", "已弃坑"].includes(t)) status = t;
|
|
169
|
+
else if (!t.includes("展开其余")) tags.push(t);
|
|
170
|
+
});
|
|
171
|
+
const stats = { views: "0", comments: "0", likes: "0", words: "0" };
|
|
172
|
+
const actionDiv = card.querySelector(".card-action > div");
|
|
173
|
+
if (actionDiv) {
|
|
174
|
+
actionDiv.querySelectorAll("span[title]").forEach((s) => {
|
|
175
|
+
const t = s.getAttribute("title") || "";
|
|
176
|
+
const v = s.textContent?.trim().replace(/,/g, "") || "0";
|
|
177
|
+
if (t.includes("字")) stats.words = v;
|
|
178
|
+
if (t.includes("阅读")) stats.views = v;
|
|
179
|
+
if (t.includes("评论")) stats.comments = v;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const greenText = card.querySelector(".left.green-text");
|
|
183
|
+
if (greenText) stats.likes = greenText.textContent?.replace(/[^0-9]/g, "") || "0";
|
|
184
|
+
let updateTime = "";
|
|
185
|
+
const timeSpan = card.querySelector('div[style*="margin: 3px 0;"] span.grey-text');
|
|
186
|
+
if (timeSpan) {
|
|
187
|
+
const txt = timeSpan.textContent || "";
|
|
188
|
+
const dateMatch = txt.match(/(\d{4}\s*年\s*\d{1,2}\s*月\s*\d{1,2}\s*日)/) || txt.match(/(\d+\s*(?:小时|分钟|天)前)/) || txt.match(/(\d{1,2}\s*月\s*\d{1,2}\s*日)/);
|
|
189
|
+
if (dateMatch) updateTime = dateMatch[1].replace(/\s/g, "");
|
|
185
190
|
}
|
|
186
|
-
.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
191
|
+
items.push({ id, title, author, cover, tags: tags.slice(0, 8), status, stats, updateTime });
|
|
192
|
+
});
|
|
193
|
+
return items;
|
|
194
|
+
});
|
|
195
|
+
return results;
|
|
196
|
+
} catch (e) {
|
|
197
|
+
return [];
|
|
198
|
+
} finally {
|
|
199
|
+
if (page) await page.close();
|
|
200
|
+
}
|
|
201
|
+
}, "searchThreads");
|
|
202
|
+
const renderCard = /* @__PURE__ */ __name(async (info, parent) => {
|
|
203
|
+
const isChapter = info.IsChapter || !!parent && parent.ID !== info.ID;
|
|
204
|
+
const displayTitle = isChapter && parent ? parent.Title : info.Title;
|
|
205
|
+
const displayCover = isChapter && parent ? parent.Background || extractImage(parent.Content) : info.Background || extractImage(info.Content);
|
|
206
|
+
const displayTagsObj = isChapter && parent ? parent.Tags : info.Tags;
|
|
207
|
+
const subTitle = isChapter ? info.Title : null;
|
|
208
|
+
const views = info.Views || 0;
|
|
209
|
+
const comments = info.Comments || 0;
|
|
210
|
+
const words = info.WordCount || 0;
|
|
211
|
+
const likes = info.Upvotes || 0;
|
|
212
|
+
const bgStyle = displayCover ? `background-image: url('${displayCover}');` : `background: ${generateGradient(displayTitle)};`;
|
|
213
|
+
let summary = stripHtml(info.Content);
|
|
214
|
+
if (summary.length < 10 && parent && isChapter) summary = stripHtml(parent.Content);
|
|
215
|
+
if (summary.length > 100) summary = summary.substring(0, 100) + "...";
|
|
216
|
+
if (!summary) summary = "暂无简介";
|
|
217
|
+
const tagsArr = [];
|
|
218
|
+
if (displayTagsObj?.Type) tagsArr.push(displayTagsObj.Type);
|
|
219
|
+
if (displayTagsObj?.Rating && displayTagsObj.Rating !== "E") tagsArr.push(displayTagsObj.Rating);
|
|
220
|
+
if (displayTagsObj?.OtherTags && Array.isArray(displayTagsObj.OtherTags)) {
|
|
221
|
+
tagsArr.push(...displayTagsObj.OtherTags);
|
|
222
|
+
}
|
|
223
|
+
const displayTags = tagsArr.slice(0, 10);
|
|
224
|
+
const html = `
|
|
225
|
+
<!DOCTYPE html>
|
|
226
|
+
<html>
|
|
227
|
+
<head>
|
|
228
|
+
<style>
|
|
229
|
+
body { margin: 0; padding: 0; font-family: ${fontStack}; background: transparent; }
|
|
230
|
+
.card { width: 600px; height: 320px; background: #fff; border-radius: 16px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); display: flex; overflow: hidden; }
|
|
231
|
+
.cover { width: 220px; height: 100%; ${bgStyle} background-size: cover; background-position: center; position: relative; flex-shrink: 0; }
|
|
232
|
+
.id-tag { position: absolute; top: 12px; left: 12px; background: rgba(0,0,0,0.6); color: #fff; padding: 4px 10px; border-radius: 6px; font-size: 13px; font-weight: bold; backdrop-filter: blur(4px); font-family: monospace; }
|
|
233
|
+
.info { flex: 1; padding: 24px; display: flex; flex-direction: column; justify-content: space-between; overflow: hidden; }
|
|
234
|
+
|
|
235
|
+
.title {
|
|
236
|
+
font-size: 22px; font-weight: 700; color: #333; line-height: 1.35;
|
|
237
|
+
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
|
238
|
+
}
|
|
239
|
+
.subtitle {
|
|
240
|
+
font-size: 15px; color: #555; margin-top: 6px; font-weight: 500;
|
|
241
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
242
|
+
padding-left: 10px; border-left: 3px solid #e91e63;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.author { font-size: 13px; color: #888; margin-top: 8px; font-weight: 400; }
|
|
246
|
+
|
|
247
|
+
.tags { display: flex; flex-wrap: wrap; gap: 6px; margin: 12px 0; max-height: 56px; overflow: hidden; }
|
|
248
|
+
.tag { background: #eff2f5; color: #5c6b7f; padding: 3px 9px; border-radius: 4px; font-size: 11px; font-weight: 500; }
|
|
249
|
+
.tag-imp { background: #e3f2fd; color: #1565c0; }
|
|
250
|
+
|
|
251
|
+
.summary {
|
|
252
|
+
font-size: 13px; color: #666; line-height: 1.6;
|
|
253
|
+
display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden;
|
|
254
|
+
margin-top: auto;
|
|
255
|
+
}
|
|
256
|
+
.footer { border-top: 1px solid #eee; padding-top: 14px; display: flex; justify-content: space-between; font-size: 12px; color: #888; margin-top: 15px; }
|
|
257
|
+
.stat b { color: #555; font-weight: bold; margin-right: 2px;}
|
|
258
|
+
</style>
|
|
259
|
+
</head>
|
|
260
|
+
<body>
|
|
261
|
+
<div class="card">
|
|
262
|
+
<div class="cover"><div class="id-tag">ID: ${info.ID}</div></div>
|
|
263
|
+
<div class="info">
|
|
264
|
+
<div>
|
|
265
|
+
<div class="title">${displayTitle}</div>
|
|
266
|
+
${subTitle ? `<div class="subtitle">${subTitle}</div>` : ""}
|
|
267
|
+
<div class="author">@${info.UserName}</div>
|
|
268
|
+
<div class="tags">${displayTags.map((t) => `<span class="tag ${["文", "译", "R"].includes(t) ? "tag-imp" : ""}">${t}</span>`).join("")}</div>
|
|
269
|
+
</div>
|
|
270
|
+
<div class="summary">${summary}</div>
|
|
271
|
+
<div class="footer">
|
|
272
|
+
<span class="stat"><b style="color:#009688">热度</b>${views}</span>
|
|
273
|
+
<span class="stat"><b style="color:#673ab7">评论</b>${comments}</span>
|
|
274
|
+
<span class="stat"><b style="color:#4caf50">赞</b>${likes}</span>
|
|
275
|
+
<span class="stat"><b style="color:#795548">字数</b>${words}</span>
|
|
201
276
|
</div>
|
|
202
277
|
</div>
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
278
|
+
</div>
|
|
279
|
+
</body></html>`;
|
|
280
|
+
const page = await ctx.puppeteer.page();
|
|
281
|
+
await injectCookies(page);
|
|
282
|
+
await page.setContent(html);
|
|
283
|
+
await page.setViewport({ width: 640, height: 440, deviceScaleFactor: 2 });
|
|
284
|
+
const el = await page.$(".card");
|
|
285
|
+
const img = await el.screenshot({ type: "png" });
|
|
286
|
+
await page.close();
|
|
287
|
+
return img;
|
|
288
|
+
}, "renderCard");
|
|
289
|
+
const renderSearchResults = /* @__PURE__ */ __name(async (keyword, results) => {
|
|
290
|
+
const html = `
|
|
291
|
+
<!DOCTYPE html>
|
|
292
|
+
<html>
|
|
293
|
+
<head>
|
|
294
|
+
<style>
|
|
295
|
+
body { margin: 0; padding: 0; font-family: ${fontStack}; width: 500px; background: transparent; }
|
|
296
|
+
.container { background: #fff; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.15); margin: 10px; }
|
|
297
|
+
.header { background: #fafafa; padding: 15px 20px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; }
|
|
298
|
+
.header-title { font-size: 16px; font-weight: bold; color: #333; }
|
|
299
|
+
.list { padding: 0; }
|
|
300
|
+
.item { display: flex; padding: 15px; border-bottom: 1px solid #f5f5f5; height: 110px; align-items: flex-start; }
|
|
301
|
+
.cover-box { width: 75px; height: 100%; border-radius: 6px; overflow: hidden; flex-shrink: 0; margin-right: 15px; background: #eee; position: relative; }
|
|
302
|
+
.cover-img { width: 100%; height: 100%; object-fit: cover; }
|
|
303
|
+
.content { flex: 1; display: flex; flex-direction: column; justify-content: space-between; height: 100%; min-width: 0; }
|
|
304
|
+
.top-row { display: flex; justify-content: space-between; align-items: flex-start; }
|
|
305
|
+
.title { font-size: 16px; font-weight: bold; color: #222; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-bottom: 4px; flex:1; margin-right: 8px;}
|
|
306
|
+
.id-badge { background: #455a64; color: #fff; padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 11px; font-weight: bold; flex-shrink: 0; }
|
|
307
|
+
.author { font-size: 12px; color: #666; }
|
|
308
|
+
.tags { display: flex; gap: 4px; flex-wrap: wrap; height: 18px; overflow: hidden; margin-top: 4px; }
|
|
309
|
+
.tag { background: #f3f3f3; color: #666; padding: 0 5px; border-radius: 3px; font-size: 10px; white-space: nowrap; line-height: 1.6;}
|
|
310
|
+
.meta-row { display: flex; gap: 10px; font-size: 11px; color: #999; margin-top: auto; border-top: 1px dashed #eee; padding-top: 5px; }
|
|
311
|
+
.stat b { margin-right: 1px; font-weight: bold; }
|
|
312
|
+
</style>
|
|
313
|
+
</head>
|
|
314
|
+
<body>
|
|
315
|
+
<div class="container">
|
|
316
|
+
<div class="header"><div class="header-title">🔍 "${keyword}"</div><div>Top ${results.length}</div></div>
|
|
317
|
+
<div class="list">
|
|
318
|
+
${results.map((r) => {
|
|
319
|
+
const bg = r.cover ? `<img class="cover-img" src="${r.cover}"/>` : `<div style="width:100%;height:100%;background:${generateGradient(r.title)}"></div>`;
|
|
320
|
+
const stats = [
|
|
321
|
+
r.stats.views && r.stats.views != "0" ? `<span class="stat" style="color:#009688"><b>热</b>${r.stats.views}</span>` : "",
|
|
322
|
+
r.stats.comments && r.stats.comments != "0" ? `<span class="stat" style="color:#673ab7"><b>评</b>${r.stats.comments}</span>` : "",
|
|
323
|
+
r.stats.likes && r.stats.likes != "0" ? `<span class="stat" style="color:#4caf50"><b>赞</b>${r.stats.likes}</span>` : "",
|
|
324
|
+
r.updateTime ? `<span class="stat" style="margin-left:auto;color:#757575">${r.updateTime}</span>` : ""
|
|
325
|
+
].join("");
|
|
326
|
+
return `<div class="item"><div class="cover-box">${bg}</div><div class="content">
|
|
327
|
+
<div class="top-row"><div class="title">${r.title}</div><div class="id-badge">ID: ${r.id}</div></div>
|
|
328
|
+
<div class="author">By ${r.author} ${r.status ? ` · ${r.status}` : ""}</div>
|
|
329
|
+
<div class="tags">${r.tags.map((t) => `<span class="tag">${t}</span>`).join("")}</div>
|
|
330
|
+
<div class="meta-row">${stats || "暂无数据"}</div>
|
|
331
|
+
</div></div>`;
|
|
332
|
+
}).join("")}
|
|
208
333
|
</div>
|
|
209
|
-
</
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
334
|
+
</div>
|
|
335
|
+
</body></html>`;
|
|
336
|
+
const page = await ctx.puppeteer.page();
|
|
337
|
+
await page.setContent(html);
|
|
338
|
+
await page.setViewport({ width: 550, height: 800, deviceScaleFactor: 2 });
|
|
339
|
+
await sleep(300);
|
|
340
|
+
const el = await page.$(".container");
|
|
341
|
+
const img = await el.screenshot({ type: "png" });
|
|
342
|
+
await page.close();
|
|
343
|
+
return img;
|
|
344
|
+
}, "renderSearchResults");
|
|
345
|
+
const renderReadPages = /* @__PURE__ */ __name(async (info) => {
|
|
346
|
+
const cleanedContent = cleanContent(info.Content);
|
|
347
|
+
const html = `
|
|
348
|
+
<!DOCTYPE html>
|
|
349
|
+
<html>
|
|
350
|
+
<head>
|
|
351
|
+
<style>
|
|
352
|
+
body { margin: 0; padding: 0; width: ${config.deviceWidth}px; background-color: #f6f4ec; color: #2c2c2c; font-family: ${fontStack}; }
|
|
353
|
+
#source-container { display: none; }
|
|
354
|
+
.page {
|
|
355
|
+
width: ${config.deviceWidth}px; height: ${config.deviceHeight}px;
|
|
356
|
+
padding: 35px 28px; box-sizing: border-box;
|
|
357
|
+
position: relative; background: #f6f4ec; overflow: hidden;
|
|
358
|
+
display: flex; flex-direction: column;
|
|
359
|
+
}
|
|
360
|
+
.page-header {
|
|
361
|
+
font-size: 12px; color: #8d6e63; border-bottom: 2px solid #d7ccc8;
|
|
362
|
+
padding-bottom: 12px; margin-bottom: 15px; flex-shrink: 0;
|
|
363
|
+
display: flex; justify-content: space-between; font-weight: bold;
|
|
364
|
+
}
|
|
365
|
+
.page-footer {
|
|
366
|
+
position: absolute; bottom: 15px; left: 0; right: 0; text-align: center;
|
|
367
|
+
font-size: 12px; color: #aaa; font-family: sans-serif;
|
|
368
|
+
}
|
|
369
|
+
.page-content { flex: 1; overflow: hidden; font-size: ${config.fontSize}px; line-height: 1.7; text-align: justify; }
|
|
370
|
+
/* 段落样式优化 */
|
|
371
|
+
p { margin: 0 0 0.6em 0; text-indent: 2em; }
|
|
372
|
+
img { max-width: 100%; height: auto; display: block; margin: 10px auto; border-radius: 6px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
|
|
373
|
+
h1, h2, h3 { font-size: 1.1em; margin: 0.5em 0; color: #5d4037; text-indent: 0; font-weight: bold; }
|
|
374
|
+
</style>
|
|
375
|
+
</head>
|
|
376
|
+
<body>
|
|
377
|
+
<div id="source-container"><div class="meta-info" data-title="${info.Title}" data-author="${info.UserName}"></div>${cleanedContent}</div>
|
|
378
|
+
<div id="output"></div>
|
|
379
|
+
<script>
|
|
380
|
+
function paginate() {
|
|
381
|
+
const source = document.getElementById('source-container');
|
|
382
|
+
const output = document.getElementById('output');
|
|
383
|
+
const title = source.querySelector('.meta-info').dataset.title;
|
|
384
|
+
const author = source.querySelector('.meta-info').dataset.author;
|
|
385
|
+
let pageIndex = 1;
|
|
386
|
+
let currentPageContent = null;
|
|
387
|
+
function createNewPage() {
|
|
388
|
+
const page = document.createElement('div'); page.className = 'page';
|
|
389
|
+
const header = document.createElement('div'); header.className = 'page-header';
|
|
390
|
+
header.innerHTML = \`<span>\${title.substring(0, 12) + (title.length>12?'...':'')}</span><span>\${author}</span>\`;
|
|
391
|
+
page.appendChild(header);
|
|
392
|
+
const content = document.createElement('div'); content.className = 'page-content';
|
|
393
|
+
page.appendChild(content);
|
|
394
|
+
const footer = document.createElement('div'); footer.className = 'page-footer';
|
|
395
|
+
footer.id = 'footer-' + pageIndex;
|
|
396
|
+
page.appendChild(footer);
|
|
397
|
+
output.appendChild(page);
|
|
398
|
+
currentPageContent = content;
|
|
399
|
+
return page;
|
|
400
|
+
}
|
|
401
|
+
createNewPage();
|
|
402
|
+
const children = Array.from(source.children);
|
|
403
|
+
for (const child of children) {
|
|
404
|
+
if (child.className === 'meta-info') continue;
|
|
405
|
+
currentPageContent.appendChild(child.cloneNode(true));
|
|
406
|
+
if (currentPageContent.scrollHeight > currentPageContent.clientHeight) {
|
|
407
|
+
currentPageContent.removeChild(currentPageContent.lastChild);
|
|
408
|
+
pageIndex++;
|
|
409
|
+
createNewPage();
|
|
410
|
+
currentPageContent.appendChild(child.cloneNode(true));
|
|
411
|
+
}
|
|
232
412
|
}
|
|
413
|
+
for(let i=1; i<=pageIndex; i++) document.getElementById('footer-'+i).innerText = \`- \${i} / \${pageIndex} -\`;
|
|
414
|
+
return pageIndex;
|
|
233
415
|
}
|
|
416
|
+
</script>
|
|
417
|
+
</body></html>`;
|
|
418
|
+
const page = await ctx.puppeteer.page();
|
|
419
|
+
try {
|
|
420
|
+
await injectCookies(page);
|
|
421
|
+
await page.setContent(html);
|
|
422
|
+
await page.setViewport({ width: config.deviceWidth, height: config.deviceHeight, deviceScaleFactor: 2 });
|
|
423
|
+
await page.evaluate("paginate()");
|
|
424
|
+
const imgs = [];
|
|
425
|
+
const pages = await page.$$(".page");
|
|
426
|
+
for (const p of pages) {
|
|
427
|
+
imgs.push(await p.screenshot({ type: "jpeg", quality: 80 }));
|
|
234
428
|
}
|
|
429
|
+
return imgs;
|
|
430
|
+
} finally {
|
|
235
431
|
await page.close();
|
|
432
|
+
}
|
|
433
|
+
}, "renderReadPages");
|
|
434
|
+
ctx.command("ft.info <threadId:string>", "预览作品").action(async ({ session }, threadId) => {
|
|
435
|
+
if (!threadId) return "请输入ID";
|
|
436
|
+
const res = await fetchThread(threadId);
|
|
437
|
+
if (!res.valid) return `[错误] ${res.msg}`;
|
|
438
|
+
const img = await renderCard(res.data, res.parent);
|
|
439
|
+
return session.send(import_koishi.h.image(img, "image/png"));
|
|
440
|
+
});
|
|
441
|
+
ctx.command("ft.read <threadId:string>", "阅读章节").action(async ({ session }, threadId) => {
|
|
442
|
+
if (!threadId) return "请输入ID";
|
|
443
|
+
const res = await fetchThread(threadId);
|
|
444
|
+
if (!res.valid) return `[错误] 读取失败: ${res.msg}`;
|
|
445
|
+
await session.send(`[加载中] ${res.data.Title}...`);
|
|
446
|
+
try {
|
|
447
|
+
const cardImg = await renderCard(res.data, res.parent);
|
|
448
|
+
await session.send(import_koishi.h.image(cardImg, "image/png"));
|
|
449
|
+
const pages = await renderReadPages(res.data);
|
|
450
|
+
const nodes = pages.map((buf) => (0, import_koishi.h)("message", import_koishi.h.image(buf, "image/jpeg")));
|
|
451
|
+
const navs = [];
|
|
452
|
+
if (res.menu?.length) {
|
|
453
|
+
const idx = res.menu.findIndex((m) => m.ID.toString() === threadId);
|
|
454
|
+
if (idx > 0) navs.push(`[上一章] /ft.read ${res.menu[idx - 1].ID}`);
|
|
455
|
+
if (idx < res.menu.length - 1) navs.push(`[下一章] /ft.read ${res.menu[idx + 1].ID}`);
|
|
456
|
+
}
|
|
457
|
+
if (navs.length) nodes.push((0, import_koishi.h)("message", import_koishi.h.text("章节导航:\n" + navs.join("\n"))));
|
|
458
|
+
return session.send((0, import_koishi.h)("message", { forward: true }, nodes));
|
|
236
459
|
} catch (e) {
|
|
237
460
|
ctx.logger("fimtale").error(e);
|
|
238
|
-
return "
|
|
239
|
-
}
|
|
240
|
-
const navs = [];
|
|
241
|
-
if (prevId) navs.push(`⬅️上一章: ft.read ${prevId}`);
|
|
242
|
-
if (nextId) navs.push(`➡️下一章: ft.read ${nextId}`);
|
|
243
|
-
if (!nextId && (!menu.length || menu.length === 0)) {
|
|
244
|
-
navs.push(`(未检测到目录,尝试下页: ft.read ${parseInt(threadId) + 1})`);
|
|
461
|
+
return "[错误] 渲染失败";
|
|
245
462
|
}
|
|
246
|
-
|
|
247
|
-
|
|
463
|
+
});
|
|
464
|
+
ctx.command("ft.random", "随机作品").action(async ({ session }) => {
|
|
465
|
+
const id = await fetchRandomId();
|
|
466
|
+
if (!id) return "[错误] 获取失败";
|
|
467
|
+
const res = await fetchThread(id);
|
|
468
|
+
if (!res.valid) return `[错误] ID:${id} 读取失败`;
|
|
469
|
+
const img = await renderCard(res.data, res.parent);
|
|
470
|
+
await session.send(import_koishi.h.image(img, "image/png"));
|
|
471
|
+
return `提示: 发送 /ft.read ${res.data.ID} 阅读全文`;
|
|
472
|
+
});
|
|
473
|
+
ctx.command("ft.search <keyword:text>", "搜索作品").action(async ({ session }, keyword) => {
|
|
474
|
+
if (!keyword) return "请输入关键词";
|
|
475
|
+
await session.send("[加载中] 搜索中...");
|
|
476
|
+
const results = await searchThreads(keyword);
|
|
477
|
+
if (!results.length) return "未找到结果。";
|
|
478
|
+
const img = await renderSearchResults(keyword, results);
|
|
479
|
+
await session.send(import_koishi.h.image(img, "image/png"));
|
|
480
|
+
return "提示: 发送 /ft.read <ID> 阅读";
|
|
481
|
+
});
|
|
482
|
+
ctx.command("ft.sub <threadId:string>", "订阅").action(async ({ session }, threadId) => {
|
|
483
|
+
if (!/^\d+$/.test(threadId)) return "ID错误";
|
|
484
|
+
const exist = await ctx.database.get("fimtale_subs", { cid: session.cid, threadId });
|
|
485
|
+
if (exist.length) return "已订阅";
|
|
486
|
+
const res = await fetchThread(threadId);
|
|
487
|
+
if (!res.valid) return "帖子不存在";
|
|
488
|
+
await ctx.database.create("fimtale_subs", { cid: session.cid, threadId, lastCount: res.data.Comments, lastCheck: Date.now() });
|
|
489
|
+
await session.send("[成功] 订阅成功");
|
|
490
|
+
const img = await renderCard(res.data, res.parent);
|
|
491
|
+
return session.send(import_koishi.h.image(img, "image/png"));
|
|
492
|
+
});
|
|
493
|
+
ctx.command("ft.unsub <threadId:string>", "退订").action(async ({ session }, threadId) => {
|
|
494
|
+
const res = await ctx.database.remove("fimtale_subs", { cid: session.cid, threadId });
|
|
495
|
+
return res.matched ? "[成功] 已退订" : "未找到订阅";
|
|
496
|
+
});
|
|
497
|
+
ctx.middleware(async (session, next) => {
|
|
498
|
+
if (!config.autoParseLink) return next();
|
|
499
|
+
const match = session.content.match(/fimtale\.com\/t\/(\d+)/);
|
|
500
|
+
if (match && match[1] && session.userId !== session.selfId) {
|
|
501
|
+
const res = await fetchThread(match[1]);
|
|
502
|
+
if (res.valid) {
|
|
503
|
+
const img = await renderCard(res.data, res.parent);
|
|
504
|
+
session.send(import_koishi.h.image(img, "image/png"));
|
|
505
|
+
}
|
|
248
506
|
}
|
|
249
|
-
return
|
|
507
|
+
return next();
|
|
250
508
|
});
|
|
251
509
|
ctx.setInterval(async () => {
|
|
252
510
|
const subs = await ctx.database.get("fimtale_subs", {});
|
|
253
511
|
if (!subs.length) return;
|
|
254
|
-
const
|
|
255
|
-
for (const tid of
|
|
256
|
-
const
|
|
257
|
-
if (!
|
|
258
|
-
const targets = subs.filter((s) => s.threadId === tid && s.lastCount < data.
|
|
259
|
-
if (targets.length
|
|
260
|
-
const msg =
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
count: data.comments,
|
|
264
|
-
url: `https://fimtale.com/t/${tid}`
|
|
265
|
-
});
|
|
512
|
+
const tids = [...new Set(subs.map((s) => s.threadId))];
|
|
513
|
+
for (const tid of tids) {
|
|
514
|
+
const res = await fetchThread(tid);
|
|
515
|
+
if (!res.valid) continue;
|
|
516
|
+
const targets = subs.filter((s) => s.threadId === tid && s.lastCount < res.data.Comments);
|
|
517
|
+
if (targets.length) {
|
|
518
|
+
const msg = `[更新] ${res.data.Title} 更新了!
|
|
519
|
+
回复: ${res.data.Comments}
|
|
520
|
+
https://fimtale.com/t/${tid}`;
|
|
266
521
|
for (const sub of targets) {
|
|
267
522
|
try {
|
|
268
|
-
await ctx.broadcast([sub.cid], msg);
|
|
269
|
-
await ctx.database.set("fimtale_subs", { id: sub.id }, {
|
|
270
|
-
|
|
271
|
-
lastCheck: Date.now()
|
|
272
|
-
});
|
|
273
|
-
} catch (e) {
|
|
523
|
+
await ctx.broadcast([sub.cid], import_koishi.h.parse(msg));
|
|
524
|
+
await ctx.database.set("fimtale_subs", { id: sub.id }, { lastCount: res.data.Comments });
|
|
525
|
+
} catch {
|
|
274
526
|
}
|
|
275
527
|
}
|
|
276
528
|
}
|
|
277
529
|
}
|
|
278
530
|
}, config.pollInterval);
|
|
279
|
-
ctx.middleware(async (session, next) => {
|
|
280
|
-
if (!config.autoParseLink) return next();
|
|
281
|
-
const match = session.content.match(/fimtale\.com\/t\/(\d+)/);
|
|
282
|
-
if (match && match[1]) {
|
|
283
|
-
if (session.userId === session.selfId) return next();
|
|
284
|
-
const threadId = match[1];
|
|
285
|
-
const data = await fetchThread(threadId);
|
|
286
|
-
if (data.valid) {
|
|
287
|
-
let preview = "";
|
|
288
|
-
if (config.showPreview) {
|
|
289
|
-
const rawText = stripHtml(data.content);
|
|
290
|
-
preview = rawText.length > config.previewLength ? rawText.substring(0, config.previewLength) + "..." : rawText;
|
|
291
|
-
}
|
|
292
|
-
return session.send(formatText(config.messages.infoTemplate, {
|
|
293
|
-
title: data.title,
|
|
294
|
-
author: data.author,
|
|
295
|
-
views: data.views,
|
|
296
|
-
count: data.comments,
|
|
297
|
-
time: data.lastTime,
|
|
298
|
-
preview
|
|
299
|
-
}));
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
return next();
|
|
303
|
-
});
|
|
304
531
|
}
|
|
305
532
|
__name(apply, "apply");
|
|
306
533
|
// Annotate the CommonJS export names for ESM import in node:
|
package/package.json
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-fimtale-api",
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "0.0.
|
|
3
|
+
"description": "Koishi插件,从fimtale搜索/订阅/随机获取小说/解析链接等",
|
|
4
|
+
"version": "0.0.4",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
8
8
|
"lib",
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
|
+
"homepage": "https://github.com/muyni233/koishi-plugin-fimtale-api",
|
|
11
12
|
"license": "MIT",
|
|
12
13
|
"keywords": [
|
|
13
14
|
"chatbot",
|
|
14
15
|
"koishi",
|
|
15
|
-
"plugin"
|
|
16
|
+
"plugin",
|
|
17
|
+
"fiction"
|
|
16
18
|
],
|
|
17
19
|
"peerDependencies": {
|
|
18
20
|
"koishi": "^4.18.7"
|
package/readme.md
CHANGED