koishi-plugin-bns-blacklist 1.0.4 → 1.0.6
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.js +120 -69
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -183,83 +183,134 @@ ${itemsHtml}
|
|
|
183
183
|
</body></html>`;
|
|
184
184
|
}
|
|
185
185
|
// ─── Data Scraping ─────────────────────────────────────────
|
|
186
|
-
async function scrapeSheet(page, url) {
|
|
186
|
+
async function scrapeSheet(page, url, logger) {
|
|
187
187
|
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
|
|
188
|
-
// Wait for the sheet to render
|
|
189
|
-
await new Promise(r => setTimeout(r,
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
188
|
+
// Wait for the sheet to fully render
|
|
189
|
+
await new Promise(r => setTimeout(r, 10000));
|
|
190
|
+
// Log page info for debugging
|
|
191
|
+
if (logger) {
|
|
192
|
+
const title = await page.title();
|
|
193
|
+
const bodyLen = await page.evaluate(() => document.body.innerText.length);
|
|
194
|
+
logger.info(`Page title: "${title}", body text length: ${bodyLen}`);
|
|
195
|
+
}
|
|
196
|
+
// Strategy 1: Use the page's own fetch to call Tencent Docs internal API
|
|
197
|
+
const apiData = await page.evaluate(async () => {
|
|
194
198
|
const results = [];
|
|
195
|
-
//
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
if (rowData.some(c => c))
|
|
215
|
-
results.push(rowData);
|
|
199
|
+
// Try common Tencent Docs API endpoints from within the authenticated page
|
|
200
|
+
const baseUrl = 'https://docs.qq.com';
|
|
201
|
+
const sheetId = 'DZXRuRmtTS21mWU9V';
|
|
202
|
+
const endpoints = [
|
|
203
|
+
`/dop-api/sheet/data?id=${sheetId}`,
|
|
204
|
+
`/dop-api/sheet/get?id=${sheetId}`,
|
|
205
|
+
`/dop-api/spreadsheet/${sheetId}`,
|
|
206
|
+
`/api/sheet/data?id=${sheetId}`,
|
|
207
|
+
`/api/sheet/${sheetId}/values`,
|
|
208
|
+
`/openapi/v1/spreadsheet/${sheetId}/values/Sheet1`,
|
|
209
|
+
`/v2/sheet/${sheetId}/export?format=json`,
|
|
210
|
+
`/cgi-bin/online_docs/sheet_get?docId=${sheetId}`,
|
|
211
|
+
];
|
|
212
|
+
for (const endpoint of endpoints) {
|
|
213
|
+
try {
|
|
214
|
+
const resp = await fetch(baseUrl + endpoint, { credentials: 'include' });
|
|
215
|
+
const text = await resp.text();
|
|
216
|
+
if (text.length > 50 && !text.includes('Not Found') && !text.includes('<html')) {
|
|
217
|
+
results.push({ endpoint, text, status: resp.status });
|
|
216
218
|
}
|
|
217
|
-
return results;
|
|
218
219
|
}
|
|
220
|
+
catch (_) { /* skip */ }
|
|
219
221
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
for (let r = 0; r <= maxRow; r++) {
|
|
235
|
-
const row = [];
|
|
236
|
-
for (let c = 0; c <= maxCol; c++) {
|
|
237
|
-
row.push(cellMap.get(`${r},${c}`) || '');
|
|
238
|
-
}
|
|
239
|
-
if (row.some(c => c))
|
|
240
|
-
results.push(row);
|
|
241
|
-
}
|
|
242
|
-
return results;
|
|
222
|
+
return results;
|
|
223
|
+
});
|
|
224
|
+
if (logger)
|
|
225
|
+
logger.info(`API endpoints tried: ${apiData.length} responded`);
|
|
226
|
+
for (const item of apiData) {
|
|
227
|
+
if (logger)
|
|
228
|
+
logger.info(` ${item.endpoint} -> status=${item.status}, len=${item.body?.length || item.text?.length}`);
|
|
229
|
+
try {
|
|
230
|
+
const json = JSON.parse(item.text || item.body);
|
|
231
|
+
const rows = extractRowsFromJson(json);
|
|
232
|
+
if (rows.length > 0) {
|
|
233
|
+
if (logger)
|
|
234
|
+
logger.info(`Found data: ${rows.length} rows from ${item.endpoint}`);
|
|
235
|
+
return parseSheetData(rows);
|
|
243
236
|
}
|
|
244
237
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
238
|
+
catch (_) { /* try next */ }
|
|
239
|
+
}
|
|
240
|
+
// Strategy 2: Dump page text and try to parse
|
|
241
|
+
const pageText = await page.evaluate(() => document.body.innerText);
|
|
242
|
+
if (pageText.length > 100) {
|
|
243
|
+
const lines = pageText.split('\n').filter((l) => l.trim());
|
|
244
|
+
// Look for data lines that have at least 3 tab-separated fields
|
|
245
|
+
const tabLines = lines.filter((l) => l.split('\t').length >= 3);
|
|
246
|
+
if (tabLines.length >= 2) {
|
|
247
|
+
const rows = tabLines.map((l) => l.split('\t'));
|
|
248
|
+
if (logger)
|
|
249
|
+
logger.info(`Parsed ${rows.length} rows from page text`);
|
|
250
|
+
return parseSheetData(rows);
|
|
251
|
+
}
|
|
252
|
+
// If no tabs, maybe data is in a different format - log first few lines
|
|
253
|
+
if (logger) {
|
|
254
|
+
logger.info(`Page text first 5 lines (${lines.length} total):`);
|
|
255
|
+
for (let i = 0; i < Math.min(5, lines.length); i++) {
|
|
256
|
+
logger.info(` [${i}] ${lines[i].substring(0, 200)}`);
|
|
255
257
|
}
|
|
256
|
-
if (results.length > 1)
|
|
257
|
-
return results;
|
|
258
258
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
259
|
+
}
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
262
|
+
// Recursively search JSON for array-of-arrays that looks like sheet data
|
|
263
|
+
function extractRowsFromJson(obj, depth = 0) {
|
|
264
|
+
if (depth > 8)
|
|
265
|
+
return [];
|
|
266
|
+
if (!obj || typeof obj !== 'object')
|
|
267
|
+
return [];
|
|
268
|
+
// Direct array of arrays (most common sheet data format)
|
|
269
|
+
if (Array.isArray(obj) && obj.length > 0 && Array.isArray(obj[0])) {
|
|
270
|
+
const rows = obj.map((row) => row.map(cell => {
|
|
271
|
+
if (cell === null || cell === undefined)
|
|
272
|
+
return '';
|
|
273
|
+
if (typeof cell === 'object') {
|
|
274
|
+
return cell.text || cell.value || cell.v || cell.formattedValue || '';
|
|
275
|
+
}
|
|
276
|
+
return String(cell);
|
|
277
|
+
}));
|
|
278
|
+
if (rows.length >= 2 && rows[0].length >= 2)
|
|
279
|
+
return rows;
|
|
280
|
+
}
|
|
281
|
+
// Search nested objects
|
|
282
|
+
if (Array.isArray(obj)) {
|
|
283
|
+
for (const item of obj) {
|
|
284
|
+
const found = extractRowsFromJson(item, depth + 1);
|
|
285
|
+
if (found.length > 0)
|
|
286
|
+
return found;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
for (const key of Object.keys(obj)) {
|
|
291
|
+
// Skip non-data keys
|
|
292
|
+
if (['config', 'style', 'meta', 'theme', 'permission'].some(s => key.toLowerCase().includes(s)))
|
|
293
|
+
continue;
|
|
294
|
+
const found = extractRowsFromJson(obj[key], depth + 1);
|
|
295
|
+
if (found.length > 0)
|
|
296
|
+
return found;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
// Search a JSON string for array patterns that look like sheet rows
|
|
302
|
+
function findSheetRowsInJson(jsonStr) {
|
|
303
|
+
// Try to find array patterns that could be sheet data
|
|
304
|
+
const results = [];
|
|
305
|
+
// Look for patterns like ["cell1","cell2",...] repeated
|
|
306
|
+
const rowRegex = /\[((?:"[^"]*"\s*,?\s*)+)\]/g;
|
|
307
|
+
let match;
|
|
308
|
+
while ((match = rowRegex.exec(jsonStr)) !== null) {
|
|
309
|
+
const cells = match[1].split(/"\s*,\s*"/).map(s => s.replace(/^"|"$/g, ''));
|
|
310
|
+
if (cells.length >= 3)
|
|
311
|
+
results.push(cells);
|
|
312
|
+
}
|
|
313
|
+
return results;
|
|
263
314
|
}
|
|
264
315
|
function parseSheetData(rows) {
|
|
265
316
|
if (rows.length === 0)
|
|
@@ -333,7 +384,7 @@ function apply(ctx, config) {
|
|
|
333
384
|
ctx.logger('bns-blacklist').info('Refreshing data from Tencent Docs...');
|
|
334
385
|
page = await puppeteer.page();
|
|
335
386
|
await page.setViewport({ width: 1920, height: 1080 });
|
|
336
|
-
const records = await scrapeSheet(page, config.sheetUrl);
|
|
387
|
+
const records = await scrapeSheet(page, config.sheetUrl, ctx.logger('bns-blacklist'));
|
|
337
388
|
if (records.length > 0) {
|
|
338
389
|
blacklistCache = records;
|
|
339
390
|
saveCache();
|