koishi-plugin-rocom 1.0.1
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/client.d.ts +53 -0
- package/lib/client.js +473 -0
- package/lib/commands/account.d.ts +5 -0
- package/lib/commands/account.js +205 -0
- package/lib/commands/admin.d.ts +2 -0
- package/lib/commands/admin.js +117 -0
- package/lib/commands/egg.d.ts +2 -0
- package/lib/commands/egg.js +196 -0
- package/lib/commands/merchant.d.ts +2 -0
- package/lib/commands/merchant.js +242 -0
- package/lib/commands/query.d.ts +2 -0
- package/lib/commands/query.js +1264 -0
- package/lib/commands/wiki.d.ts +2 -0
- package/lib/commands/wiki.js +11 -0
- package/lib/egg-service.d.ts +229 -0
- package/lib/egg-service.js +705 -0
- package/lib/index.d.ts +24 -0
- package/lib/index.js +3746 -0
- package/lib/render-templates/bind-list/index.html +51 -0
- package/lib/render-templates/bind-list/style.css +178 -0
- package/lib/render-templates/exchange-hall/css/_@astro-renderers.0KDkAyVb.css +1 -0
- package/lib/render-templates/exchange-hall/css/index.B3tv56V6.css +1 -0
- package/lib/render-templates/exchange-hall/css/index.D2LGPudy.css +1 -0
- package/lib/render-templates/exchange-hall/extracted.css +393 -0
- package/lib/render-templates/exchange-hall/index.html +99 -0
- package/lib/render-templates/exchange-hall/style.css +267 -0
- package/lib/render-templates/friendship/index.html +58 -0
- package/lib/render-templates/friendship/style.css +182 -0
- package/lib/render-templates/home/data/home_item_list.json +122 -0
- package/lib/render-templates/home/img/home_icon/100604.png +0 -0
- package/lib/render-templates/home/img/home_icon/100604_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100604_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100605.png +0 -0
- package/lib/render-templates/home/img/home_icon/100605_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100605_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100606.png +0 -0
- package/lib/render-templates/home/img/home_icon/100606_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100606_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100607.png +0 -0
- package/lib/render-templates/home/img/home_icon/100607_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100607_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100608.png +0 -0
- package/lib/render-templates/home/img/home_icon/100608_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100608_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100622.png +0 -0
- package/lib/render-templates/home/img/home_icon/100622_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100622_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100623.png +0 -0
- package/lib/render-templates/home/img/home_icon/100623_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100623_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100624.png +0 -0
- package/lib/render-templates/home/img/home_icon/100624_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100624_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100627.png +0 -0
- package/lib/render-templates/home/img/home_icon/100627_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100627_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100684.png +0 -0
- package/lib/render-templates/home/img/home_icon/100684_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100684_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100686.png +0 -0
- package/lib/render-templates/home/img/home_icon/100686_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100686_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100687.png +0 -0
- package/lib/render-templates/home/img/home_icon/100687_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100687_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100689.png +0 -0
- package/lib/render-templates/home/img/home_icon/100689_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100689_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100690.png +0 -0
- package/lib/render-templates/home/img/home_icon/100690_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100690_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100691.png +0 -0
- package/lib/render-templates/home/img/home_icon/100691_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100691_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100692.png +0 -0
- package/lib/render-templates/home/img/home_icon/100692_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100692_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100693.png +0 -0
- package/lib/render-templates/home/img/home_icon/100693_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100693_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100694.png +0 -0
- package/lib/render-templates/home/img/home_icon/100694_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100694_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100706.png +0 -0
- package/lib/render-templates/home/img/home_icon/100706_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100706_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100751.png +0 -0
- package/lib/render-templates/home/img/home_icon/100751_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100751_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100755.png +0 -0
- package/lib/render-templates/home/img/home_icon/100755_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100755_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100762.png +0 -0
- package/lib/render-templates/home/img/home_icon/100762_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100762_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100764.png +0 -0
- package/lib/render-templates/home/img/home_icon/100764_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100764_2.png +0 -0
- package/lib/render-templates/home/img/home_icon/100869.png +0 -0
- package/lib/render-templates/home/img/home_icon/100869_1.png +0 -0
- package/lib/render-templates/home/img/home_icon/100869_2.png +0 -0
- package/lib/render-templates/home/img/img_HomeVisit_Icon1.png +0 -0
- package/lib/render-templates/home/img/img_LevelReward_Bg2.png +0 -0
- package/lib/render-templates/home/index.html +139 -0
- package/lib/render-templates/home/style.css +537 -0
- package/lib/render-templates/ingame-shop/index.html +87 -0
- package/lib/render-templates/ingame-shop/style.css +220 -0
- package/lib/render-templates/inspect/index.html +47 -0
- package/lib/render-templates/inspect/style.css +149 -0
- package/lib/render-templates/lineup/index.html +77 -0
- package/lib/render-templates/lineup/style.css +255 -0
- package/lib/render-templates/lineup-detail/index.html +63 -0
- package/lib/render-templates/lineup-detail/style.css +218 -0
- package/lib/render-templates/menu/index.html +36 -0
- package/lib/render-templates/menu/style.css +126 -0
- package/lib/render-templates/package/index.html +115 -0
- package/lib/render-templates/package/style.css +352 -0
- package/lib/render-templates/personal-card/index.html +292 -0
- package/lib/render-templates/personal-card/style.css +2114 -0
- package/lib/render-templates/pet-wiki/index.html +118 -0
- package/lib/render-templates/pet-wiki/style.css +382 -0
- package/lib/render-templates/player-search/index.html +60 -0
- package/lib/render-templates/player-search/style.css +192 -0
- package/lib/render-templates/record/index.html +86 -0
- package/lib/render-templates/record/style.css +322 -0
- package/lib/render-templates/searcheggs/Pets.json +104328 -0
- package/lib/render-templates/searcheggs/candidates.html +52 -0
- package/lib/render-templates/searcheggs/eggs.py +599 -0
- package/lib/render-templates/searcheggs/index.html +198 -0
- package/lib/render-templates/searcheggs/pair.html +81 -0
- package/lib/render-templates/searcheggs/size.html +82 -0
- package/lib/render-templates/searcheggs/style.css +586 -0
- package/lib/render-templates/searcheggs/want.html +63 -0
- package/lib/render-templates/skill-wiki/index.html +68 -0
- package/lib/render-templates/skill-wiki/style.css +182 -0
- package/lib/render-templates/student/index.html +95 -0
- package/lib/render-templates/student/style.css +255 -0
- package/lib/render-templates/student-perks/index.html +78 -0
- package/lib/render-templates/student-perks/style.css +238 -0
- package/lib/render-templates/student-state/index.html +52 -0
- package/lib/render-templates/student-state/style.css +157 -0
- package/lib/render-templates/yuanxing-shangren/index.html +371 -0
- package/lib/render-templates/yuanxing-shangren/style.css +371 -0
- package/lib/render.d.ts +11 -0
- package/lib/render.js +226 -0
- package/lib/role-token.d.ts +27 -0
- package/lib/role-token.js +137 -0
- package/lib/send-image.d.ts +3 -0
- package/lib/send-image.js +135 -0
- package/lib/subscription-send.d.ts +8 -0
- package/lib/subscription-send.js +48 -0
- package/lib/types.d.ts +32 -0
- package/lib/types.js +2 -0
- package/lib/user.d.ts +67 -0
- package/lib/user.js +176 -0
- package/package.json +58 -0
- package/readme.md +575 -0
package/lib/render.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
export declare class Renderer {
|
|
3
|
+
private resPath;
|
|
4
|
+
constructor(resPath: string);
|
|
5
|
+
resourceUrl(relativePath: string): string;
|
|
6
|
+
private getResourceRoot;
|
|
7
|
+
private getTemplateRoot;
|
|
8
|
+
private getTemplatePath;
|
|
9
|
+
private getStylePath;
|
|
10
|
+
renderHtml(ctx: Context, templateName: string, data: any): Promise<Buffer | null>;
|
|
11
|
+
}
|
package/lib/render.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.Renderer = void 0;
|
|
40
|
+
const koishi_1 = require("koishi");
|
|
41
|
+
const template = __importStar(require("art-template"));
|
|
42
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
43
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
44
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
45
|
+
const node_url_1 = require("node:url");
|
|
46
|
+
const logger = new koishi_1.Logger('rocom-render');
|
|
47
|
+
const TEMPLATE_CAPTURE_PADDING = {
|
|
48
|
+
package: { left: 0, right: 0, top: 0, bottom: 0 },
|
|
49
|
+
};
|
|
50
|
+
function toDirectoryFileUrl(dirPath) {
|
|
51
|
+
const href = (0, node_url_1.pathToFileURL)(dirPath).href;
|
|
52
|
+
return href.endsWith('/') ? href : `${href}/`;
|
|
53
|
+
}
|
|
54
|
+
function normalizeTemplateResourcePaths(content) {
|
|
55
|
+
return content.replace(/\{\{(_res_path|pluResPath)\}\}render\//g, '{{$1}}render-templates/');
|
|
56
|
+
}
|
|
57
|
+
class Renderer {
|
|
58
|
+
resPath;
|
|
59
|
+
constructor(resPath) {
|
|
60
|
+
this.resPath = resPath;
|
|
61
|
+
}
|
|
62
|
+
resourceUrl(relativePath) {
|
|
63
|
+
return (0, node_url_1.pathToFileURL)(node_path_1.default.join(this.getResourceRoot(), relativePath)).href;
|
|
64
|
+
}
|
|
65
|
+
getResourceRoot() {
|
|
66
|
+
const builtRoot = node_path_1.default.join(this.resPath, 'lib');
|
|
67
|
+
if (node_fs_1.default.existsSync(node_path_1.default.join(builtRoot, 'render-templates')))
|
|
68
|
+
return builtRoot;
|
|
69
|
+
return node_path_1.default.join(this.resPath, 'src');
|
|
70
|
+
}
|
|
71
|
+
getTemplateRoot() {
|
|
72
|
+
return node_path_1.default.join(this.getResourceRoot(), 'render-templates');
|
|
73
|
+
}
|
|
74
|
+
getTemplatePath(templateName) {
|
|
75
|
+
const directHtmlPath = node_path_1.default.join(this.getTemplateRoot(), `${templateName}.html`);
|
|
76
|
+
if (node_fs_1.default.existsSync(directHtmlPath))
|
|
77
|
+
return directHtmlPath;
|
|
78
|
+
return node_path_1.default.join(this.getTemplateRoot(), templateName, 'index.html');
|
|
79
|
+
}
|
|
80
|
+
getStylePath(templateName) {
|
|
81
|
+
return node_path_1.default.join(this.getTemplateRoot(), templateName, 'style.css');
|
|
82
|
+
}
|
|
83
|
+
async renderHtml(ctx, templateName, data) {
|
|
84
|
+
try {
|
|
85
|
+
const templatePath = this.getTemplatePath(templateName);
|
|
86
|
+
if (!node_fs_1.default.existsSync(templatePath)) {
|
|
87
|
+
logger.error(`template file missing: ${templatePath}`);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const templateContent = node_fs_1.default.readFileSync(templatePath, 'utf-8');
|
|
91
|
+
const normalizedTemplateContent = normalizeTemplateResourcePaths(templateContent);
|
|
92
|
+
const resPathUrl = toDirectoryFileUrl(this.getResourceRoot());
|
|
93
|
+
const renderData = { ...data, _res_path: resPathUrl, pluResPath: resPathUrl };
|
|
94
|
+
const html = template.render(normalizedTemplateContent, renderData);
|
|
95
|
+
if (!ctx.puppeteer?.page) {
|
|
96
|
+
logger.error('puppeteer service is unavailable');
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const page = await ctx.puppeteer.page();
|
|
100
|
+
const tempDir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), 'rocom-render-'));
|
|
101
|
+
const tempHtmlPath = node_path_1.default.join(tempDir, `${templateName.replace(/[\\/]/g, '_')}.html`);
|
|
102
|
+
try {
|
|
103
|
+
await page.setCacheEnabled(false);
|
|
104
|
+
node_fs_1.default.writeFileSync(tempHtmlPath, html, 'utf-8');
|
|
105
|
+
await page.setViewport({ width: 1280, height: 768, deviceScaleFactor: 2 });
|
|
106
|
+
try {
|
|
107
|
+
await page.goto((0, node_url_1.pathToFileURL)(tempHtmlPath).href, {
|
|
108
|
+
waitUntil: 'networkidle0',
|
|
109
|
+
timeout: 15000,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
logger.warn(`page.goto failed for ${templateName}: ${err}`);
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
await page.evaluate(async () => {
|
|
117
|
+
const images = Array.from(document.images);
|
|
118
|
+
await Promise.all(images.map((img) => {
|
|
119
|
+
if (img.complete)
|
|
120
|
+
return Promise.resolve();
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
img.onload = () => resolve();
|
|
123
|
+
img.onerror = () => resolve();
|
|
124
|
+
});
|
|
125
|
+
}));
|
|
126
|
+
const fonts = document.fonts;
|
|
127
|
+
if (fonts?.ready) {
|
|
128
|
+
await fonts.ready;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
logger.warn(`asset wait failed for ${templateName}: ${err}`);
|
|
134
|
+
}
|
|
135
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
136
|
+
const selectors = [
|
|
137
|
+
'.exchange-page',
|
|
138
|
+
'.record-page',
|
|
139
|
+
'.package-cont',
|
|
140
|
+
'.searcheggs-cont',
|
|
141
|
+
'.bwiki-shell',
|
|
142
|
+
'.skill-shell',
|
|
143
|
+
'.lineup-page',
|
|
144
|
+
'.lineup-detail-page',
|
|
145
|
+
'.page-section-main',
|
|
146
|
+
'.stats-cont',
|
|
147
|
+
'.inspect-page',
|
|
148
|
+
'.player-search-page',
|
|
149
|
+
'.ingame-shop-page',
|
|
150
|
+
'.friendship-page',
|
|
151
|
+
'.student-state-page',
|
|
152
|
+
'.student-perks-page',
|
|
153
|
+
'.student-page',
|
|
154
|
+
'.home-page',
|
|
155
|
+
];
|
|
156
|
+
let target = null;
|
|
157
|
+
for (const selector of selectors) {
|
|
158
|
+
target = await page.$(selector);
|
|
159
|
+
if (target)
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
if (!target) {
|
|
163
|
+
target = await page.$('body');
|
|
164
|
+
}
|
|
165
|
+
if (target) {
|
|
166
|
+
const box = await target.boundingBox();
|
|
167
|
+
if (box && box.width > 0 && box.height > 0) {
|
|
168
|
+
const elementMetrics = await page.evaluate((el) => {
|
|
169
|
+
const rect = el.getBoundingClientRect();
|
|
170
|
+
const element = el;
|
|
171
|
+
return {
|
|
172
|
+
x: rect.left + window.scrollX,
|
|
173
|
+
y: rect.top + window.scrollY,
|
|
174
|
+
width: Math.max(rect.width, element.scrollWidth, element.offsetWidth),
|
|
175
|
+
height: Math.max(rect.height, element.scrollHeight, element.offsetHeight),
|
|
176
|
+
};
|
|
177
|
+
}, target);
|
|
178
|
+
const capturePadding = TEMPLATE_CAPTURE_PADDING[templateName] || { left: 0, right: 0, top: 0, bottom: 0 };
|
|
179
|
+
await page.setViewport({
|
|
180
|
+
width: Math.max(Math.ceil(elementMetrics.x + elementMetrics.width + capturePadding.right) + 8, 200),
|
|
181
|
+
height: Math.max(Math.ceil(elementMetrics.y + elementMetrics.height + capturePadding.bottom) + 8, 200),
|
|
182
|
+
deviceScaleFactor: 2,
|
|
183
|
+
});
|
|
184
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
185
|
+
const hasOverflow = elementMetrics.width > box.width + 0.5 ||
|
|
186
|
+
elementMetrics.height > box.height + 0.5;
|
|
187
|
+
if (capturePadding.left || capturePadding.right || capturePadding.top || capturePadding.bottom || hasOverflow) {
|
|
188
|
+
const clipX = Math.max(0, elementMetrics.x - capturePadding.left);
|
|
189
|
+
const clipY = Math.max(0, elementMetrics.y - capturePadding.top);
|
|
190
|
+
const clipWidth = elementMetrics.width + capturePadding.left + capturePadding.right;
|
|
191
|
+
const clipHeight = elementMetrics.height + capturePadding.top + capturePadding.bottom;
|
|
192
|
+
const screenshot = await page.screenshot({
|
|
193
|
+
type: 'png',
|
|
194
|
+
clip: {
|
|
195
|
+
x: clipX,
|
|
196
|
+
y: clipY,
|
|
197
|
+
width: clipWidth,
|
|
198
|
+
height: clipHeight,
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
return Buffer.isBuffer(screenshot) ? screenshot : Buffer.from(screenshot);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const screenshot = await target.screenshot({ type: 'png' });
|
|
205
|
+
return Buffer.isBuffer(screenshot) ? screenshot : Buffer.from(screenshot);
|
|
206
|
+
}
|
|
207
|
+
const screenshot = await page.screenshot({ fullPage: true });
|
|
208
|
+
return Buffer.isBuffer(screenshot) ? screenshot : Buffer.from(screenshot);
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
try {
|
|
212
|
+
await page.close();
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// ignore
|
|
216
|
+
}
|
|
217
|
+
node_fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
logger.error(`render failed: ${e}`);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
exports.Renderer = Renderer;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { UserManager } from './user';
|
|
3
|
+
export interface RoleToken {
|
|
4
|
+
UserId: string;
|
|
5
|
+
fwt: string;
|
|
6
|
+
bindingId: string;
|
|
7
|
+
roleId: string;
|
|
8
|
+
loginType: string;
|
|
9
|
+
updatedAt: Date;
|
|
10
|
+
}
|
|
11
|
+
declare module 'koishi' {
|
|
12
|
+
interface Tables {
|
|
13
|
+
roleToken: RoleToken;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export declare function setupRoleTokenModel(ctx: Context): void;
|
|
17
|
+
export declare function upsertRoleToken(ctx: Context, payload: {
|
|
18
|
+
userId: string;
|
|
19
|
+
fwt: string;
|
|
20
|
+
bindingId?: string;
|
|
21
|
+
roleId?: string;
|
|
22
|
+
loginType?: string;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
export declare function getRoleToken(ctx: Context, userId: string): Promise<RoleToken | null>;
|
|
25
|
+
export declare function removeRoleToken(ctx: Context, userId: string): Promise<void>;
|
|
26
|
+
export declare function migrateRoleTokensToUserId(ctx: Context): Promise<number>;
|
|
27
|
+
export declare function migrateLegacyFrameworkTokens(ctx: Context, userMgr: UserManager): Promise<number>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupRoleTokenModel = setupRoleTokenModel;
|
|
4
|
+
exports.upsertRoleToken = upsertRoleToken;
|
|
5
|
+
exports.getRoleToken = getRoleToken;
|
|
6
|
+
exports.removeRoleToken = removeRoleToken;
|
|
7
|
+
exports.migrateRoleTokensToUserId = migrateRoleTokensToUserId;
|
|
8
|
+
exports.migrateLegacyFrameworkTokens = migrateLegacyFrameworkTokens;
|
|
9
|
+
function normalize(value) {
|
|
10
|
+
return String(value ?? '').trim();
|
|
11
|
+
}
|
|
12
|
+
function pickLatest(rows) {
|
|
13
|
+
if (!rows.length)
|
|
14
|
+
return null;
|
|
15
|
+
return [...rows].sort((a, b) => {
|
|
16
|
+
const aTime = new Date(a.updatedAt).getTime() || 0;
|
|
17
|
+
const bTime = new Date(b.updatedAt).getTime() || 0;
|
|
18
|
+
return bTime - aTime;
|
|
19
|
+
})[0] ?? null;
|
|
20
|
+
}
|
|
21
|
+
function pickLegacyToken(binding) {
|
|
22
|
+
return normalize(binding.framework_token);
|
|
23
|
+
}
|
|
24
|
+
function setupRoleTokenModel(ctx) {
|
|
25
|
+
;
|
|
26
|
+
ctx.model.extend('roleToken', {
|
|
27
|
+
UserId: 'string',
|
|
28
|
+
fwt: 'string',
|
|
29
|
+
bindingId: 'string',
|
|
30
|
+
roleId: 'string',
|
|
31
|
+
loginType: 'string',
|
|
32
|
+
updatedAt: 'timestamp',
|
|
33
|
+
}, {
|
|
34
|
+
primary: 'UserId',
|
|
35
|
+
indexes: ['bindingId'],
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async function upsertRoleToken(ctx, payload) {
|
|
39
|
+
const userId = normalize(payload.userId);
|
|
40
|
+
const fwt = normalize(payload.fwt);
|
|
41
|
+
if (!userId || !fwt)
|
|
42
|
+
return;
|
|
43
|
+
await ctx.database.remove('roleToken', { UserId: userId });
|
|
44
|
+
await ctx.database.upsert('roleToken', () => [{
|
|
45
|
+
UserId: userId,
|
|
46
|
+
fwt,
|
|
47
|
+
bindingId: normalize(payload.bindingId),
|
|
48
|
+
roleId: normalize(payload.roleId),
|
|
49
|
+
loginType: normalize(payload.loginType),
|
|
50
|
+
updatedAt: new Date(),
|
|
51
|
+
}], 'UserId');
|
|
52
|
+
}
|
|
53
|
+
async function getRoleToken(ctx, userId) {
|
|
54
|
+
const normalizedUserId = normalize(userId);
|
|
55
|
+
if (!normalizedUserId)
|
|
56
|
+
return null;
|
|
57
|
+
const rows = await ctx.database.get('roleToken', { UserId: normalizedUserId });
|
|
58
|
+
if (!Array.isArray(rows) || !rows.length)
|
|
59
|
+
return null;
|
|
60
|
+
return pickLatest(rows);
|
|
61
|
+
}
|
|
62
|
+
async function removeRoleToken(ctx, userId) {
|
|
63
|
+
const normalizedUserId = normalize(userId);
|
|
64
|
+
if (!normalizedUserId)
|
|
65
|
+
return;
|
|
66
|
+
await ctx.database.remove('roleToken', { UserId: normalizedUserId });
|
|
67
|
+
}
|
|
68
|
+
async function migrateRoleTokensToUserId(ctx) {
|
|
69
|
+
const rows = await ctx.database.get('roleToken', {});
|
|
70
|
+
if (!Array.isArray(rows) || !rows.length)
|
|
71
|
+
return 0;
|
|
72
|
+
const groups = new Map();
|
|
73
|
+
for (const row of rows) {
|
|
74
|
+
const userId = normalize(row?.UserId);
|
|
75
|
+
if (!userId)
|
|
76
|
+
continue;
|
|
77
|
+
const bucket = groups.get(userId) || [];
|
|
78
|
+
bucket.push(row);
|
|
79
|
+
groups.set(userId, bucket);
|
|
80
|
+
}
|
|
81
|
+
let migrated = 0;
|
|
82
|
+
for (const [userId, items] of groups) {
|
|
83
|
+
if (items.length <= 1)
|
|
84
|
+
continue;
|
|
85
|
+
const latest = pickLatest(items);
|
|
86
|
+
if (!latest?.fwt)
|
|
87
|
+
continue;
|
|
88
|
+
await removeRoleToken(ctx, userId);
|
|
89
|
+
await upsertRoleToken(ctx, {
|
|
90
|
+
userId,
|
|
91
|
+
fwt: latest.fwt,
|
|
92
|
+
bindingId: latest.bindingId,
|
|
93
|
+
roleId: latest.roleId,
|
|
94
|
+
loginType: latest.loginType,
|
|
95
|
+
});
|
|
96
|
+
migrated++;
|
|
97
|
+
}
|
|
98
|
+
return migrated;
|
|
99
|
+
}
|
|
100
|
+
async function migrateLegacyFrameworkTokens(ctx, userMgr) {
|
|
101
|
+
const allUsers = userMgr.getAllUsersBindings();
|
|
102
|
+
let migrated = 0;
|
|
103
|
+
for (const [userId, bindings] of Object.entries(allUsers)) {
|
|
104
|
+
const normalizedUserId = normalize(userId);
|
|
105
|
+
if (!normalizedUserId)
|
|
106
|
+
continue;
|
|
107
|
+
const existing = await getRoleToken(ctx, normalizedUserId);
|
|
108
|
+
const bindingsWithToken = bindings.filter(binding => pickLegacyToken(binding));
|
|
109
|
+
const chosen = bindingsWithToken.find(binding => binding.is_primary) || bindingsWithToken[0];
|
|
110
|
+
if (!existing?.fwt && chosen) {
|
|
111
|
+
const legacyToken = pickLegacyToken(chosen);
|
|
112
|
+
if (legacyToken) {
|
|
113
|
+
await upsertRoleToken(ctx, {
|
|
114
|
+
userId: normalizedUserId,
|
|
115
|
+
fwt: legacyToken,
|
|
116
|
+
bindingId: chosen.binding_id,
|
|
117
|
+
roleId: chosen.role_id,
|
|
118
|
+
loginType: chosen.login_type,
|
|
119
|
+
});
|
|
120
|
+
migrated++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (bindingsWithToken.length > 0) {
|
|
124
|
+
let needsSave = false;
|
|
125
|
+
for (const binding of bindings) {
|
|
126
|
+
if (!binding.framework_token)
|
|
127
|
+
continue;
|
|
128
|
+
delete binding.framework_token;
|
|
129
|
+
needsSave = true;
|
|
130
|
+
}
|
|
131
|
+
if (needsSave) {
|
|
132
|
+
userMgr.saveUserBindings(normalizedUserId, bindings);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return migrated;
|
|
137
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { PluginConfig } from './types';
|
|
2
|
+
export declare function compressPngImage(image: Buffer, config: Pick<PluginConfig, 'imageCompressionEnabled' | 'imageCompressionMinBytes' | 'imageCompressionLevel'>): Buffer;
|
|
3
|
+
export declare function sendImageWithFallback(session: any, image: Buffer | null, fallbackText: string, scene: string, compressionConfig?: Pick<PluginConfig, 'imageCompressionEnabled' | 'imageCompressionMinBytes' | 'imageCompressionLevel'>): Promise<void>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.compressPngImage = compressPngImage;
|
|
7
|
+
exports.sendImageWithFallback = sendImageWithFallback;
|
|
8
|
+
const koishi_1 = require("koishi");
|
|
9
|
+
const node_zlib_1 = __importDefault(require("node:zlib"));
|
|
10
|
+
const logger = new koishi_1.Logger('rocom-send');
|
|
11
|
+
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
12
|
+
function formatSessionContext(session) {
|
|
13
|
+
if (!session)
|
|
14
|
+
return 'session=<null>';
|
|
15
|
+
const platform = session.platform ?? 'unknown';
|
|
16
|
+
const userId = session.userId ?? 'unknown';
|
|
17
|
+
const guildId = session.guildId ?? 'private';
|
|
18
|
+
const channelId = session.channelId ?? 'unknown';
|
|
19
|
+
return `platform=${platform} user=${userId} guild=${guildId} channel=${channelId}`;
|
|
20
|
+
}
|
|
21
|
+
function hasSendResult(result) {
|
|
22
|
+
if (result == null)
|
|
23
|
+
return false;
|
|
24
|
+
if (typeof result === 'string')
|
|
25
|
+
return result.length > 0;
|
|
26
|
+
if (Array.isArray(result))
|
|
27
|
+
return result.length > 0;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
function crc32(buffer) {
|
|
31
|
+
let crc = 0xffffffff;
|
|
32
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
33
|
+
crc ^= buffer[i];
|
|
34
|
+
for (let bit = 0; bit < 8; bit++) {
|
|
35
|
+
crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
39
|
+
}
|
|
40
|
+
function pngChunk(type, data) {
|
|
41
|
+
const typeBuffer = Buffer.from(type, 'ascii');
|
|
42
|
+
const lengthBuffer = Buffer.allocUnsafe(4);
|
|
43
|
+
lengthBuffer.writeUInt32BE(data.length, 0);
|
|
44
|
+
const crcBuffer = Buffer.allocUnsafe(4);
|
|
45
|
+
crcBuffer.writeUInt32BE(crc32(Buffer.concat([typeBuffer, data])), 0);
|
|
46
|
+
return Buffer.concat([lengthBuffer, typeBuffer, data, crcBuffer]);
|
|
47
|
+
}
|
|
48
|
+
function isPng(buffer) {
|
|
49
|
+
return buffer.length > PNG_SIGNATURE.length && buffer.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE);
|
|
50
|
+
}
|
|
51
|
+
function compressPngImage(image, config) {
|
|
52
|
+
if (!config.imageCompressionEnabled)
|
|
53
|
+
return image;
|
|
54
|
+
if (!isPng(image))
|
|
55
|
+
return image;
|
|
56
|
+
if (image.length < Math.max(0, config.imageCompressionMinBytes || 0))
|
|
57
|
+
return image;
|
|
58
|
+
try {
|
|
59
|
+
const chunks = [];
|
|
60
|
+
const idatChunks = [];
|
|
61
|
+
let offset = PNG_SIGNATURE.length;
|
|
62
|
+
while (offset + 12 <= image.length) {
|
|
63
|
+
const length = image.readUInt32BE(offset);
|
|
64
|
+
const type = image.subarray(offset + 4, offset + 8).toString('ascii');
|
|
65
|
+
const dataStart = offset + 8;
|
|
66
|
+
const dataEnd = dataStart + length;
|
|
67
|
+
const nextOffset = dataEnd + 4;
|
|
68
|
+
if (dataEnd > image.length || nextOffset > image.length)
|
|
69
|
+
return image;
|
|
70
|
+
const data = image.subarray(dataStart, dataEnd);
|
|
71
|
+
chunks.push({ type, data });
|
|
72
|
+
if (type === 'IDAT')
|
|
73
|
+
idatChunks.push(data);
|
|
74
|
+
offset = nextOffset;
|
|
75
|
+
if (type === 'IEND')
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
if (!idatChunks.length)
|
|
79
|
+
return image;
|
|
80
|
+
const rawImageData = node_zlib_1.default.inflateSync(Buffer.concat(idatChunks));
|
|
81
|
+
const level = Math.max(0, Math.min(9, Math.floor(config.imageCompressionLevel ?? 9)));
|
|
82
|
+
const compressedIdat = node_zlib_1.default.deflateSync(rawImageData, { level });
|
|
83
|
+
const rebuiltChunks = [];
|
|
84
|
+
let wroteIdat = false;
|
|
85
|
+
for (const chunk of chunks) {
|
|
86
|
+
if (chunk.type === 'IDAT') {
|
|
87
|
+
if (!wroteIdat) {
|
|
88
|
+
rebuiltChunks.push(pngChunk('IDAT', compressedIdat));
|
|
89
|
+
wroteIdat = true;
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
rebuiltChunks.push(pngChunk(chunk.type, chunk.data));
|
|
94
|
+
}
|
|
95
|
+
const compressed = Buffer.concat([PNG_SIGNATURE, ...rebuiltChunks]);
|
|
96
|
+
if (compressed.length < image.length)
|
|
97
|
+
return compressed;
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
logger.warn(`PNG compression failed, sending original image: ${err}`);
|
|
101
|
+
}
|
|
102
|
+
return image;
|
|
103
|
+
}
|
|
104
|
+
async function sendImageWithFallback(session, image, fallbackText, scene, compressionConfig) {
|
|
105
|
+
const ctxInfo = formatSessionContext(session);
|
|
106
|
+
if (!image) {
|
|
107
|
+
logger.warn(`[${scene}] render returned empty image, fallback to text | ${ctxInfo}`);
|
|
108
|
+
try {
|
|
109
|
+
await session.send(fallbackText);
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
logger.error(`[${scene}] text fallback send failed | ${ctxInfo} | ${e}`);
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const outputImage = compressionConfig ? compressPngImage(image, compressionConfig) : image;
|
|
117
|
+
if (outputImage.length < image.length) {
|
|
118
|
+
logger.info(`[${scene}] compressed image ${image.length}B -> ${outputImage.length}B | ${ctxInfo}`);
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const result = await session.send(koishi_1.h.image(outputImage, 'image/png'));
|
|
122
|
+
if (!hasSendResult(result)) {
|
|
123
|
+
logger.warn(`[${scene}] image send returned empty result | size=${outputImage.length}B | ${ctxInfo}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
logger.error(`[${scene}] image send failed | size=${outputImage.length}B | ${ctxInfo} | ${e}`);
|
|
128
|
+
try {
|
|
129
|
+
await session.send(fallbackText);
|
|
130
|
+
}
|
|
131
|
+
catch (fallbackErr) {
|
|
132
|
+
logger.error(`[${scene}] text fallback send failed after image send error | ${ctxInfo} | ${fallbackErr}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
export interface SubscriptionTarget {
|
|
3
|
+
platform?: string;
|
|
4
|
+
channelId?: string;
|
|
5
|
+
guildId?: string;
|
|
6
|
+
userId?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function sendScheduledMessage(ctx: Context, target: SubscriptionTarget, message: string): Promise<boolean>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sendScheduledMessage = sendScheduledMessage;
|
|
4
|
+
const koishi_1 = require("koishi");
|
|
5
|
+
const logger = new koishi_1.Logger('rocom-subscription-send');
|
|
6
|
+
function findBot(ctx, platform = '') {
|
|
7
|
+
if (!ctx.bots?.length)
|
|
8
|
+
return null;
|
|
9
|
+
if (!platform)
|
|
10
|
+
return ctx.bots[0];
|
|
11
|
+
return ctx.bots.find((bot) => bot.platform === platform) || ctx.bots[0];
|
|
12
|
+
}
|
|
13
|
+
async function sendScheduledMessage(ctx, target, message) {
|
|
14
|
+
const platform = target.platform || '';
|
|
15
|
+
const channelId = target.channelId || target.guildId || '';
|
|
16
|
+
const userId = target.userId || '';
|
|
17
|
+
const bot = findBot(ctx, platform);
|
|
18
|
+
if (!bot) {
|
|
19
|
+
logger.warn('no available bot, skip scheduled push');
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
if (userId && !target.guildId) {
|
|
24
|
+
if (typeof bot.sendPrivateMessage === 'function') {
|
|
25
|
+
await bot.sendPrivateMessage(userId, message);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (typeof bot.sendMessage === 'function') {
|
|
29
|
+
await bot.sendMessage(userId, message);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (platform && channelId) {
|
|
34
|
+
await ctx.broadcast([`${platform}:${channelId}`], message);
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (channelId && typeof bot.sendMessage === 'function') {
|
|
38
|
+
await bot.sendMessage(channelId, message, target.guildId);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
logger.warn(`scheduled push failed: ${err}`);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
logger.warn(`scheduled push target is incomplete: ${JSON.stringify(target)}`);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { RocomClient } from './client';
|
|
3
|
+
import { UserManager, MerchantSubscriptionManager, HomeSubscriptionManager } from './user';
|
|
4
|
+
import { EggService } from './egg-service';
|
|
5
|
+
import { Renderer } from './render';
|
|
6
|
+
export interface PluginConfig {
|
|
7
|
+
apiBaseUrl: string;
|
|
8
|
+
wegameApiKey: string;
|
|
9
|
+
qqLoginDebugMode: boolean;
|
|
10
|
+
adminUserIds: string[];
|
|
11
|
+
autoRefreshEnabled: boolean;
|
|
12
|
+
autoRefreshTime: string[];
|
|
13
|
+
merchantSubscriptionEnabled: boolean;
|
|
14
|
+
merchantSubscriptionItems: string[];
|
|
15
|
+
merchantPrivateSubscriptionEnabled: boolean;
|
|
16
|
+
merchantCheckInterval: number;
|
|
17
|
+
homeSubscriptionEnabled: boolean;
|
|
18
|
+
homeSubscriptionIntervalMinutes: number;
|
|
19
|
+
imageCompressionEnabled: boolean;
|
|
20
|
+
imageCompressionMinBytes: number;
|
|
21
|
+
imageCompressionLevel: number;
|
|
22
|
+
}
|
|
23
|
+
export interface PluginDeps {
|
|
24
|
+
ctx: Context;
|
|
25
|
+
config: PluginConfig;
|
|
26
|
+
client: RocomClient;
|
|
27
|
+
userMgr: UserManager;
|
|
28
|
+
merchantSubMgr: MerchantSubscriptionManager;
|
|
29
|
+
homeSubMgr: HomeSubscriptionManager;
|
|
30
|
+
eggService: EggService;
|
|
31
|
+
renderer: Renderer;
|
|
32
|
+
}
|
package/lib/types.js
ADDED