koishi-plugin-bns-blacklist 1.0.3 → 1.0.5
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 +5 -6
- package/lib/index.js +112 -67
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { Context, Schema } from 'koishi';
|
|
2
|
-
interface PuppeteerService {
|
|
3
|
-
page(): Promise<any>;
|
|
4
|
-
browser: any;
|
|
5
|
-
}
|
|
6
2
|
declare module 'koishi' {
|
|
7
3
|
interface Context {
|
|
8
|
-
puppeteer:
|
|
4
|
+
puppeteer: {
|
|
5
|
+
page(): Promise<any>;
|
|
6
|
+
browser: any;
|
|
7
|
+
};
|
|
9
8
|
}
|
|
10
9
|
}
|
|
11
10
|
export declare const name = "bns-blacklist";
|
|
11
|
+
export declare const inject: string[];
|
|
12
12
|
export interface Config {
|
|
13
13
|
sheetUrl: string;
|
|
14
14
|
cachePath: string;
|
|
@@ -16,4 +16,3 @@ export interface Config {
|
|
|
16
16
|
}
|
|
17
17
|
export declare const Config: Schema<Config>;
|
|
18
18
|
export declare function apply(ctx: Context, config: Config): void;
|
|
19
|
-
export {};
|
package/lib/index.js
CHANGED
|
@@ -33,12 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.Config = exports.name = void 0;
|
|
36
|
+
exports.Config = exports.inject = exports.name = void 0;
|
|
37
37
|
exports.apply = apply;
|
|
38
38
|
const koishi_1 = require("koishi");
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
41
|
exports.name = 'bns-blacklist';
|
|
42
|
+
exports.inject = ['puppeteer'];
|
|
42
43
|
exports.Config = koishi_1.Schema.object({
|
|
43
44
|
sheetUrl: koishi_1.Schema.string()
|
|
44
45
|
.default('https://docs.qq.com/sheet/DZXRuRmtTS21mWU9V?tab=BB08J2')
|
|
@@ -182,83 +183,127 @@ ${itemsHtml}
|
|
|
182
183
|
</body></html>`;
|
|
183
184
|
}
|
|
184
185
|
// ─── Data Scraping ─────────────────────────────────────────
|
|
185
|
-
async function scrapeSheet(page, url) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const state = app.store.getState();
|
|
198
|
-
// Navigate through common state paths
|
|
199
|
-
const sheetData = state?.sheet?.data || state?.sheetData || state?.data;
|
|
200
|
-
if (sheetData?.rows) {
|
|
201
|
-
for (const row of sheetData.rows) {
|
|
202
|
-
const rowData = [];
|
|
203
|
-
for (const cell of row.cells || row) {
|
|
204
|
-
if (typeof cell === 'string')
|
|
205
|
-
rowData.push(cell);
|
|
206
|
-
else if (cell?.text)
|
|
207
|
-
rowData.push(cell.text);
|
|
208
|
-
else if (cell?.value)
|
|
209
|
-
rowData.push(String(cell.value));
|
|
210
|
-
else
|
|
211
|
-
rowData.push('');
|
|
212
|
-
}
|
|
213
|
-
if (rowData.some(c => c))
|
|
214
|
-
results.push(rowData);
|
|
186
|
+
async function scrapeSheet(page, url, logger) {
|
|
187
|
+
// Collect network responses that might contain sheet data
|
|
188
|
+
const capturedData = [];
|
|
189
|
+
page.on('response', async (response) => {
|
|
190
|
+
const respUrl = response.url();
|
|
191
|
+
// Tencent Docs sheet data APIs
|
|
192
|
+
if (respUrl.includes('/api/') || respUrl.includes('/dop-api/') || respUrl.includes('opendoc')) {
|
|
193
|
+
try {
|
|
194
|
+
const body = await response.text();
|
|
195
|
+
// Only capture JSON responses that look like sheet data
|
|
196
|
+
if (body.length > 100 && body.length < 500000) {
|
|
197
|
+
capturedData.push({ url: respUrl, body });
|
|
215
198
|
}
|
|
216
|
-
return results;
|
|
217
199
|
}
|
|
200
|
+
catch (_) { /* ignore */ }
|
|
218
201
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
202
|
+
});
|
|
203
|
+
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
|
|
204
|
+
// Wait additional time for async data loading
|
|
205
|
+
await new Promise(r => setTimeout(r, 8000));
|
|
206
|
+
// Try to find the sheet data in captured API responses
|
|
207
|
+
for (const item of capturedData) {
|
|
208
|
+
try {
|
|
209
|
+
const json = JSON.parse(item.body);
|
|
210
|
+
const rows = extractRowsFromJson(json);
|
|
211
|
+
if (rows.length > 0) {
|
|
212
|
+
if (logger)
|
|
213
|
+
logger.info(`Found data via API: ${item.url} (${rows.length} rows)`);
|
|
214
|
+
return parseSheetData(rows);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (_) { /* try next */ }
|
|
218
|
+
}
|
|
219
|
+
// Fallback: try to extract from page context
|
|
220
|
+
const fallbackData = await page.evaluate(() => {
|
|
221
|
+
const results = [];
|
|
222
|
+
// Try to find Tencent Docs internal data stores
|
|
223
|
+
const allKeys = Object.keys(window).filter(k => {
|
|
224
|
+
try {
|
|
225
|
+
return window[k] && typeof window[k] === 'object';
|
|
226
|
+
}
|
|
227
|
+
catch (_) {
|
|
228
|
+
return false;
|
|
242
229
|
}
|
|
230
|
+
});
|
|
231
|
+
// Look for data in common global stores
|
|
232
|
+
for (const key of ['__app', 'app', '__SHEET_APP__', '__INITIAL_STATE__', '__NUXT__', '__NEXT_DATA__']) {
|
|
233
|
+
const obj = window[key];
|
|
234
|
+
if (!obj)
|
|
235
|
+
continue;
|
|
236
|
+
const jsonStr = JSON.stringify(obj);
|
|
237
|
+
const found = findSheetRowsInJson(jsonStr);
|
|
238
|
+
if (found.length > 0)
|
|
239
|
+
return found;
|
|
243
240
|
}
|
|
244
|
-
//
|
|
245
|
-
const
|
|
246
|
-
if (
|
|
247
|
-
const
|
|
248
|
-
// Parse tab-separated text
|
|
249
|
-
const lines = text.split('\n').filter(l => l.trim());
|
|
241
|
+
// Last resort: all visible text
|
|
242
|
+
const body = document.body.innerText;
|
|
243
|
+
if (body.length > 100) {
|
|
244
|
+
const lines = body.split('\n').filter((l) => l.trim());
|
|
250
245
|
for (const line of lines) {
|
|
251
246
|
const cells = line.split('\t');
|
|
252
|
-
if (cells.length
|
|
247
|
+
if (cells.length >= 3)
|
|
253
248
|
results.push(cells);
|
|
254
249
|
}
|
|
255
|
-
if (results.length > 1)
|
|
256
|
-
return results;
|
|
257
250
|
}
|
|
258
251
|
return results;
|
|
259
252
|
});
|
|
260
|
-
|
|
261
|
-
|
|
253
|
+
return parseSheetData(fallbackData);
|
|
254
|
+
}
|
|
255
|
+
// Recursively search JSON for array-of-arrays that looks like sheet data
|
|
256
|
+
function extractRowsFromJson(obj, depth = 0) {
|
|
257
|
+
if (depth > 8)
|
|
258
|
+
return [];
|
|
259
|
+
if (!obj || typeof obj !== 'object')
|
|
260
|
+
return [];
|
|
261
|
+
// Direct array of arrays (most common sheet data format)
|
|
262
|
+
if (Array.isArray(obj) && obj.length > 0 && Array.isArray(obj[0])) {
|
|
263
|
+
const rows = obj.map((row) => row.map(cell => {
|
|
264
|
+
if (cell === null || cell === undefined)
|
|
265
|
+
return '';
|
|
266
|
+
if (typeof cell === 'object') {
|
|
267
|
+
return cell.text || cell.value || cell.v || cell.formattedValue || '';
|
|
268
|
+
}
|
|
269
|
+
return String(cell);
|
|
270
|
+
}));
|
|
271
|
+
if (rows.length >= 2 && rows[0].length >= 2)
|
|
272
|
+
return rows;
|
|
273
|
+
}
|
|
274
|
+
// Search nested objects
|
|
275
|
+
if (Array.isArray(obj)) {
|
|
276
|
+
for (const item of obj) {
|
|
277
|
+
const found = extractRowsFromJson(item, depth + 1);
|
|
278
|
+
if (found.length > 0)
|
|
279
|
+
return found;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
for (const key of Object.keys(obj)) {
|
|
284
|
+
// Skip non-data keys
|
|
285
|
+
if (['config', 'style', 'meta', 'theme', 'permission'].some(s => key.toLowerCase().includes(s)))
|
|
286
|
+
continue;
|
|
287
|
+
const found = extractRowsFromJson(obj[key], depth + 1);
|
|
288
|
+
if (found.length > 0)
|
|
289
|
+
return found;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
// Search a JSON string for array patterns that look like sheet rows
|
|
295
|
+
function findSheetRowsInJson(jsonStr) {
|
|
296
|
+
// Try to find array patterns that could be sheet data
|
|
297
|
+
const results = [];
|
|
298
|
+
// Look for patterns like ["cell1","cell2",...] repeated
|
|
299
|
+
const rowRegex = /\[((?:"[^"]*"\s*,?\s*)+)\]/g;
|
|
300
|
+
let match;
|
|
301
|
+
while ((match = rowRegex.exec(jsonStr)) !== null) {
|
|
302
|
+
const cells = match[1].split(/"\s*,\s*"/).map(s => s.replace(/^"|"$/g, ''));
|
|
303
|
+
if (cells.length >= 3)
|
|
304
|
+
results.push(cells);
|
|
305
|
+
}
|
|
306
|
+
return results;
|
|
262
307
|
}
|
|
263
308
|
function parseSheetData(rows) {
|
|
264
309
|
if (rows.length === 0)
|
|
@@ -332,7 +377,7 @@ function apply(ctx, config) {
|
|
|
332
377
|
ctx.logger('bns-blacklist').info('Refreshing data from Tencent Docs...');
|
|
333
378
|
page = await puppeteer.page();
|
|
334
379
|
await page.setViewport({ width: 1920, height: 1080 });
|
|
335
|
-
const records = await scrapeSheet(page, config.sheetUrl);
|
|
380
|
+
const records = await scrapeSheet(page, config.sheetUrl, ctx.logger('bns-blacklist'));
|
|
336
381
|
if (records.length > 0) {
|
|
337
382
|
blacklistCache = records;
|
|
338
383
|
saveCache();
|