koishi-plugin-cfmrmod 1.1.7 → 1.1.8
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/dist/cfmr/index.js +32 -3
- package/dist/index.js +107 -7
- package/dist/mcmod/cards/author-card.js +0 -12
- package/dist/mcmod/cards/center-card.js +0 -3
- package/dist/mcmod/cards/comment-card.js +488 -0
- package/dist/mcmod/cards/info-card.js +0 -5
- package/dist/mcmod/cards/mod-card.js +433 -5
- package/dist/mcmod/cards/tutorial-card.js +0 -9
- package/dist/mcmod/cards.js +4 -1
- package/dist/mcmod/plugin.js +122 -2
- package/dist/notify.js +26 -1
- package/package.json +1 -1
package/dist/cfmr/index.js
CHANGED
|
@@ -59,6 +59,9 @@ exports.Config = Schema.object({
|
|
|
59
59
|
pageSize: Schema.number().default(10).description('每页显示数量'),
|
|
60
60
|
cacheTtl: Schema.number().default(5 * 60 * 1000).description('缓存有效期(ms)'),
|
|
61
61
|
requestTimeout: Schema.number().default(15000).description('请求超时(ms)'),
|
|
62
|
+
curseforgeApiKey: Schema.string().default('').description('CurseForge 官方 API Key(可选,未配置时使用镜像/页面抓取)'),
|
|
63
|
+
curseforgeGameId: Schema.number().default(432).description('CurseForge 游戏 ID,Minecraft 默认为 432'),
|
|
64
|
+
maxCanvasHeight: Schema.number().default(8000).description('单张渲染图片最大高度,超出后分页'),
|
|
62
65
|
sendLink: Schema.boolean().default(true).description('发送卡片后是否附带链接'),
|
|
63
66
|
fontPath: Schema.string().role('path').description('可选:自定义字体文件路径'),
|
|
64
67
|
debug: Schema.boolean().default(false).description('输出渲染调试日志'),
|
|
@@ -766,6 +769,7 @@ async function drawProjectCard(data) {
|
|
|
766
769
|
dummy.font = `16px "${font}"`; // Desc
|
|
767
770
|
const descH = wrapText(dummy, (data.summary || '').substring(0, 150), 0, 0, headerTextW, 24, 2, false);
|
|
768
771
|
headerContentH += descH + 10;
|
|
772
|
+
headerContentH += 30;
|
|
769
773
|
// Stats & Tags 行高度(按实际宽度计算是否换行)
|
|
770
774
|
dummy.font = `600 15px "${font}"`;
|
|
771
775
|
const dlText = formatNumber(data.downloads);
|
|
@@ -912,6 +916,16 @@ async function drawProjectCard(data) {
|
|
|
912
916
|
// ================= Header Draw =================
|
|
913
917
|
let cy = margin;
|
|
914
918
|
const hx = margin;
|
|
919
|
+
if (!contentOnly && data.source === 'Modrinth') {
|
|
920
|
+
ctx.fillStyle = '#1bd96a';
|
|
921
|
+
roundRect(ctx, width - margin - 158, margin, 158, 34, 17);
|
|
922
|
+
ctx.fill();
|
|
923
|
+
ctx.fillStyle = '#0b1f14';
|
|
924
|
+
ctx.font = `800 15px "${font}"`;
|
|
925
|
+
ctx.textAlign = 'center';
|
|
926
|
+
ctx.fillText('Modrinth Project', width - margin - 79, margin + 10);
|
|
927
|
+
ctx.textAlign = 'left';
|
|
928
|
+
}
|
|
915
929
|
// Icon
|
|
916
930
|
if (!contentOnly && data.icon) {
|
|
917
931
|
try {
|
|
@@ -944,6 +958,15 @@ async function drawProjectCard(data) {
|
|
|
944
958
|
ctx.font = `16px "${font}"`;
|
|
945
959
|
hTy = wrapText(ctx, (data.summary || '').substring(0, 150), hTx, hTy, headerTextW, 24, 2, true) + 12;
|
|
946
960
|
}
|
|
961
|
+
if (!contentOnly) {
|
|
962
|
+
ctx.fillStyle = '#e7fbef';
|
|
963
|
+
roundRect(ctx, hTx, hTy + 2, 104, 24, 12);
|
|
964
|
+
ctx.fill();
|
|
965
|
+
ctx.fillStyle = '#137a3d';
|
|
966
|
+
ctx.font = `800 12px "${font}"`;
|
|
967
|
+
ctx.fillText('MODRINTH', hTx + 12, hTy + 8);
|
|
968
|
+
hTy += 30;
|
|
969
|
+
}
|
|
947
970
|
// Stats & Tags Row
|
|
948
971
|
// Downloads Icon
|
|
949
972
|
const drawIcon = (path, x, y) => {
|
|
@@ -1340,6 +1363,15 @@ async function drawProjectCardCF(data) {
|
|
|
1340
1363
|
// ================= Header Draw =================
|
|
1341
1364
|
let cy = margin;
|
|
1342
1365
|
if (!contentOnly) {
|
|
1366
|
+
ctx.fillStyle = C_ACCENT;
|
|
1367
|
+
roundRect(ctx, width - margin - 150, cy, 150, 34, 4);
|
|
1368
|
+
ctx.fill();
|
|
1369
|
+
ctx.fillStyle = '#ffffff';
|
|
1370
|
+
ctx.font = `bold 15px "${font}"`;
|
|
1371
|
+
ctx.textAlign = 'center';
|
|
1372
|
+
ctx.textBaseline = 'top';
|
|
1373
|
+
ctx.fillText('CurseForge', width - margin - 75, cy + 8);
|
|
1374
|
+
ctx.textAlign = 'left';
|
|
1343
1375
|
// Icon
|
|
1344
1376
|
const iconSize = 80;
|
|
1345
1377
|
if (data.icon) {
|
|
@@ -1941,11 +1973,8 @@ async function drawProjectCardCFNotify(data, latest) {
|
|
|
1941
1973
|
try {
|
|
1942
1974
|
const icon = await loadImageSafe(data.icon);
|
|
1943
1975
|
ctx.save();
|
|
1944
|
-
ctx.shadowColor = 'rgba(0,0,0,0.5)';
|
|
1945
|
-
ctx.shadowBlur = 10;
|
|
1946
1976
|
roundRect(ctx, margin, cy, iconSize, iconSize, 8);
|
|
1947
1977
|
ctx.fill();
|
|
1948
|
-
ctx.shadowColor = 'transparent';
|
|
1949
1978
|
roundRect(ctx, margin, cy, iconSize, iconSize, 8);
|
|
1950
1979
|
ctx.clip();
|
|
1951
1980
|
ctx.drawImage(icon, margin, cy, iconSize, iconSize);
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.Config = exports.inject = exports.name = void 0;
|
|
37
|
+
exports.normalizeConfig = normalizeConfig;
|
|
37
38
|
exports.apply = apply;
|
|
38
39
|
const koishi_1 = require("koishi");
|
|
39
40
|
const cfmr = __importStar(require("./plugins/cfmr"));
|
|
@@ -77,8 +78,107 @@ exports.Config = koishi_1.Schema.object({
|
|
|
77
78
|
cfmr: cfmr.Config.default({}).description('CurseForge/Modrinth 搜索与图片卡片'),
|
|
78
79
|
mcmod: mcmod.Config.default({}).description('MCMod.cn 搜索与图片卡片'),
|
|
79
80
|
});
|
|
81
|
+
const DEFAULT_PREFIXES = { cf: 'cf', mr: 'mr', cnmc: 'cnmc' };
|
|
82
|
+
const DEFAULT_NOTIFY = {
|
|
83
|
+
enabled: false,
|
|
84
|
+
interval: 30 * 60 * 1000,
|
|
85
|
+
adminAuthority: 3,
|
|
86
|
+
stateFile: 'data/cfmrmod_notify_state.json',
|
|
87
|
+
configFile: 'data/cfmrmod_notify_config.json',
|
|
88
|
+
groups: [],
|
|
89
|
+
};
|
|
90
|
+
const DEFAULT_CFMR = {
|
|
91
|
+
pageSize: 10,
|
|
92
|
+
cacheTtl: 5 * 60 * 1000,
|
|
93
|
+
requestTimeout: 15000,
|
|
94
|
+
sendLink: true,
|
|
95
|
+
debug: false,
|
|
96
|
+
curseforgeApiKey: '',
|
|
97
|
+
curseforgeGameId: 432,
|
|
98
|
+
maxCanvasHeight: 8000,
|
|
99
|
+
render: {
|
|
100
|
+
emoji: {
|
|
101
|
+
twemoji: true,
|
|
102
|
+
cdn: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72',
|
|
103
|
+
},
|
|
104
|
+
image: {
|
|
105
|
+
fetchWithHeaders: true,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
const DEFAULT_MCMOD = {
|
|
110
|
+
sendLink: true,
|
|
111
|
+
cookie: '',
|
|
112
|
+
autoCookie: false,
|
|
113
|
+
cookieCheckInterval: 30 * 60 * 1000,
|
|
114
|
+
debug: false,
|
|
115
|
+
comment: {
|
|
116
|
+
pageSize: 5,
|
|
117
|
+
maxPageSize: 10,
|
|
118
|
+
includeImages: true,
|
|
119
|
+
},
|
|
120
|
+
render: {
|
|
121
|
+
emoji: {
|
|
122
|
+
twemoji: true,
|
|
123
|
+
cdn: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72',
|
|
124
|
+
},
|
|
125
|
+
image: {
|
|
126
|
+
fetchWithHeaders: true,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
function isPlainObject(value) {
|
|
131
|
+
return value && typeof value === 'object' && !Array.isArray(value);
|
|
132
|
+
}
|
|
133
|
+
function cloneValue(value) {
|
|
134
|
+
if (Array.isArray(value))
|
|
135
|
+
return value.map(item => cloneValue(item));
|
|
136
|
+
if (isPlainObject(value)) {
|
|
137
|
+
const result = {};
|
|
138
|
+
Object.keys(value).forEach(key => {
|
|
139
|
+
result[key] = cloneValue(value[key]);
|
|
140
|
+
});
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
function mergeDefaults(defaults, input) {
|
|
146
|
+
const result = cloneValue(defaults);
|
|
147
|
+
if (!isPlainObject(input))
|
|
148
|
+
return result;
|
|
149
|
+
Object.keys(input).forEach(key => {
|
|
150
|
+
const value = input[key];
|
|
151
|
+
if (value === undefined)
|
|
152
|
+
return;
|
|
153
|
+
if (isPlainObject(value) && isPlainObject(result[key])) {
|
|
154
|
+
result[key] = mergeDefaults(result[key], value);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
result[key] = cloneValue(value);
|
|
158
|
+
});
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
function normalizeConfig(input) {
|
|
162
|
+
var _a, _b;
|
|
163
|
+
const prefixes = mergeDefaults(DEFAULT_PREFIXES, input === null || input === void 0 ? void 0 : input.prefixes);
|
|
164
|
+
const debug = (_a = input === null || input === void 0 ? void 0 : input.debug) !== null && _a !== void 0 ? _a : false;
|
|
165
|
+
const timeouts = Number((_b = input === null || input === void 0 ? void 0 : input.timeouts) !== null && _b !== void 0 ? _b : 60000) || 60000;
|
|
166
|
+
const cfmrConfig = mergeDefaults(DEFAULT_CFMR, input === null || input === void 0 ? void 0 : input.cfmr);
|
|
167
|
+
const mcmodConfig = mergeDefaults(DEFAULT_MCMOD, input === null || input === void 0 ? void 0 : input.mcmod);
|
|
168
|
+
return {
|
|
169
|
+
prefixes,
|
|
170
|
+
timeouts,
|
|
171
|
+
debug,
|
|
172
|
+
nlu: cloneValue((input === null || input === void 0 ? void 0 : input.nlu) || {}),
|
|
173
|
+
cfmr: cfmrConfig,
|
|
174
|
+
mcmod: mcmodConfig,
|
|
175
|
+
notify: mergeDefaults(DEFAULT_NOTIFY, input === null || input === void 0 ? void 0 : input.notify),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
80
178
|
function apply(ctx, config) {
|
|
179
|
+
var _a, _b;
|
|
81
180
|
const logger = ctx.logger(exports.name);
|
|
181
|
+
const runtimeConfig = normalizeConfig(config || {});
|
|
82
182
|
let canvasAdapter = null;
|
|
83
183
|
try {
|
|
84
184
|
// Dynamic load: keep package lightweight for market scan.
|
|
@@ -96,21 +196,21 @@ function apply(ctx, config) {
|
|
|
96
196
|
catch (e) {
|
|
97
197
|
logger.warn(`未检测到 @napi-rs/canvas,图片生成功能已禁用。请在 Koishi 实例目录执行: npm i @napi-rs/canvas (${(e === null || e === void 0 ? void 0 : e.message) || e})`);
|
|
98
198
|
}
|
|
99
|
-
const prefixes =
|
|
199
|
+
const prefixes = runtimeConfig.prefixes;
|
|
100
200
|
const shared = {
|
|
101
201
|
prefixes,
|
|
102
|
-
timeouts:
|
|
103
|
-
debug:
|
|
202
|
+
timeouts: runtimeConfig.timeouts,
|
|
203
|
+
debug: runtimeConfig.debug,
|
|
104
204
|
canvas: canvasAdapter,
|
|
105
205
|
};
|
|
106
206
|
if (cfmr.apply)
|
|
107
|
-
cfmr.apply(ctx, { ...(
|
|
207
|
+
cfmr.apply(ctx, { ...runtimeConfig.cfmr, ...shared, debug: (_a = runtimeConfig.cfmr.debug) !== null && _a !== void 0 ? _a : shared.debug });
|
|
108
208
|
if (mcmod.apply)
|
|
109
|
-
mcmod.apply(ctx, { ...(
|
|
209
|
+
mcmod.apply(ctx, { ...runtimeConfig.mcmod, ...shared, debug: (_b = runtimeConfig.mcmod.debug) !== null && _b !== void 0 ? _b : shared.debug });
|
|
110
210
|
if (nlu.apply)
|
|
111
|
-
nlu.apply(ctx,
|
|
211
|
+
nlu.apply(ctx, runtimeConfig.nlu, shared);
|
|
112
212
|
if (notify.apply && canvasAdapter)
|
|
113
|
-
notify.apply(ctx,
|
|
213
|
+
notify.apply(ctx, runtimeConfig.notify, { cfmr: runtimeConfig.cfmr });
|
|
114
214
|
if (!canvasAdapter)
|
|
115
215
|
logger.warn('notify 更新卡片功能已禁用(缺少 @napi-rs/canvas)。');
|
|
116
216
|
}
|
|
@@ -203,17 +203,11 @@ async function drawAuthorCard(url) {
|
|
|
203
203
|
}
|
|
204
204
|
// 4. 绘制 Acrylic 窗口
|
|
205
205
|
const windowW = width - windowMargin * 2;
|
|
206
|
-
ctx.save();
|
|
207
|
-
// 窗口阴影
|
|
208
|
-
ctx.shadowColor = 'rgba(0,0,0,0.3)';
|
|
209
|
-
ctx.shadowBlur = 40;
|
|
210
|
-
ctx.shadowOffsetY = 20;
|
|
211
206
|
// 窗口背景 (40% Acrylic - 模拟)
|
|
212
207
|
// 使用白色半透明 + 背景模糊效果 (Canvas 无法直接 backdrop-filter,只能通过叠加半透明白)
|
|
213
208
|
ctx.fillStyle = 'rgba(255, 255, 255, 0.75)'; // 提高不透明度以遮盖背景杂乱
|
|
214
209
|
(0, rendering_1.roundRect)(ctx, windowMargin, windowMargin, windowW, windowH, 20);
|
|
215
210
|
ctx.fill();
|
|
216
|
-
ctx.restore();
|
|
217
211
|
// 窗口边框
|
|
218
212
|
ctx.strokeStyle = 'rgba(255,255,255,0.6)';
|
|
219
213
|
ctx.lineWidth = 1.5;
|
|
@@ -244,13 +238,10 @@ async function drawAuthorCard(url) {
|
|
|
244
238
|
const avatarSize = 100;
|
|
245
239
|
// Avatar
|
|
246
240
|
ctx.save();
|
|
247
|
-
ctx.shadowColor = 'rgba(0,0,0,0.1)';
|
|
248
|
-
ctx.shadowBlur = 10;
|
|
249
241
|
ctx.beginPath();
|
|
250
242
|
ctx.arc(contentX + avatarSize / 2, cursorY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
|
|
251
243
|
ctx.fillStyle = '#fff';
|
|
252
244
|
ctx.fill();
|
|
253
|
-
ctx.shadowBlur = 0;
|
|
254
245
|
ctx.clip();
|
|
255
246
|
if (avatarUrl) {
|
|
256
247
|
try {
|
|
@@ -331,11 +322,8 @@ async function drawAuthorCard(url) {
|
|
|
331
322
|
ly += 45;
|
|
332
323
|
}
|
|
333
324
|
ctx.fillStyle = '#fff';
|
|
334
|
-
ctx.shadowColor = 'rgba(0,0,0,0.05)';
|
|
335
|
-
ctx.shadowBlur = 5;
|
|
336
325
|
(0, rendering_1.roundRect)(ctx, lx, ly, lw, 34, 17);
|
|
337
326
|
ctx.fill();
|
|
338
|
-
ctx.shadowBlur = 0;
|
|
339
327
|
ctx.fillStyle = '#333';
|
|
340
328
|
ctx.fillText(l.n, lx + 15, ly + 8);
|
|
341
329
|
lx += lw + 10;
|
|
@@ -258,12 +258,9 @@ async function drawCenterCardImpl(uid, logger) {
|
|
|
258
258
|
ctx.fillRect(0, 0, width, bannerH);
|
|
259
259
|
// Header
|
|
260
260
|
const cardTop = bannerH - cardOverlap;
|
|
261
|
-
ctx.shadowColor = 'rgba(0,0,0,0.1)';
|
|
262
|
-
ctx.shadowBlur = 10;
|
|
263
261
|
ctx.fillStyle = '#fff';
|
|
264
262
|
(0, rendering_1.roundRect)(ctx, 20, cardTop, width - 40, headerH, 10);
|
|
265
263
|
ctx.fill();
|
|
266
|
-
ctx.shadowBlur = 0;
|
|
267
264
|
const avX = 50, avY = cardTop - 30;
|
|
268
265
|
ctx.beginPath();
|
|
269
266
|
ctx.arc(avX + 50, avY + 50, 54, 0, Math.PI * 2);
|