koishi-plugin-cfmrmod 1.1.6 → 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.
@@ -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 = (config === null || config === void 0 ? void 0 : config.prefixes) || {};
199
+ const prefixes = runtimeConfig.prefixes;
100
200
  const shared = {
101
201
  prefixes,
102
- timeouts: config === null || config === void 0 ? void 0 : config.timeouts,
103
- debug: config === null || config === void 0 ? void 0 : config.debug,
202
+ timeouts: runtimeConfig.timeouts,
203
+ debug: runtimeConfig.debug,
104
204
  canvas: canvasAdapter,
105
205
  };
106
206
  if (cfmr.apply)
107
- cfmr.apply(ctx, { ...((config === null || config === void 0 ? void 0 : config.cfmr) || {}), ...shared });
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, { ...((config === null || config === void 0 ? void 0 : config.mcmod) || {}), ...shared });
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, (config === null || config === void 0 ? void 0 : config.nlu) || {}, shared);
211
+ nlu.apply(ctx, runtimeConfig.nlu, shared);
112
212
  if (notify.apply && canvasAdapter)
113
- notify.apply(ctx, (config === null || config === void 0 ? void 0 : config.notify) || {}, { cfmr: (config === null || config === void 0 ? void 0 : config.cfmr) || {} });
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);