Threads ohne zuordenbaren Spieler wurden bisher verworfen. Von 502 Antraegen im Forum kamen dadurch nur die wenigen an, deren Ersteller schon als Spieler mit Forum-Namen erfasst war - der Rest war schlicht unsichtbar. Sie werden jetzt erfasst und stehen unter "Ohne zugeordneten Spieler", durchsuchbar nach Titel und Ersteller, aber ausserhalb der Arbeitsliste: ohne Spieler laesst sich nichts buchen. Wird spaeter ein Spieler mit passendem Forum-Namen angelegt, der Name nachgetragen oder ein frueheres Konto ergaenzt, wandern seine wartenden Antraege von selbst hinueber. Ein erneuter Abgleich ist dafuer nicht noetig, und eine bereits getroffene Zuordnung bleibt unberuehrt. Der Name eines CCs fuehrt auf der Spielerseite jetzt zum Forumsbeitrag, sofern einer verknuepft ist. Weitere Korrekturen am Abgleich: - Wer sein Forumskonto geloescht hat, stand ohne Profil-Link in der Zeile und hatte damit keinen Autor. Solche Antraege liessen sich niemandem zuordnen. - Bei einer abgewiesenen Anfrage brach das ganze Board ab; die bis dahin gelesenen Seiten gingen mit verloren. Jetzt wird die genannte Wartezeit eingehalten, das Tempo fuer den Rest des Laufs gedrosselt und die Seite erneut versucht. Was gelesen wurde, bleibt in jedem Fall erhalten. - Derselbe Schutz im GExtension-Abgleich, der sonst den Spieler uebersprungen haette. - Der Board-Filter fragte nach einem Feld "board", das es nicht gibt. Ein voller Lauf dauert rund eine halbe Minute und laenger, wenn das Forum bremst. Er laeuft deshalb im Hintergrund weiter und meldet seinen Fortschritt, statt an einer HTTP-Zeitgrenze zu sterben. Gelesen und geschrieben wird gebuendelt statt Thread fuer Thread.
141 lines
5.3 KiB
TypeScript
141 lines
5.3 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { describe, it } from 'node:test';
|
|
import { hasIgnoredLabel, parseBoard, parsePageCount, parseThreadId } from '../src/lib/forum.js';
|
|
import { isRateLimited, retryAfterMs } from '../src/lib/ratelimit.js';
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const BOARD = readFileSync(join(here, 'fixtures', 'board.html'), 'utf8');
|
|
|
|
describe('Board-Übersicht lesen', () => {
|
|
const threads = parseBoard(BOARD);
|
|
|
|
it('liest die Threads der Liste', () => {
|
|
assert.equal(threads.length, 6);
|
|
assert.deepEqual(
|
|
threads.map((t) => t.threadId),
|
|
[7001, 7002, 7004, 7005, 7003, 7006],
|
|
);
|
|
});
|
|
|
|
it('liest den Ersteller auch ohne Profil-Link', () => {
|
|
// Wer sein Konto geloescht hat, steht als blosser Text in der Zelle. Ohne
|
|
// Rueckfall haette der Thread keinen Autor und liesse sich niemandem
|
|
// zuordnen - er waere im Abgleich unsichtbar.
|
|
const ohneKonto = threads.find((t) => t.threadId === 7006);
|
|
assert.equal(ohneKonto?.authorName, 'Ehemaliger');
|
|
assert.equal(ohneKonto?.authorForumId, null);
|
|
});
|
|
|
|
it('nimmt den Ersteller aus der Übersicht, nicht den letzten Antwortenden', () => {
|
|
const first = threads.find((t) => t.threadId === 7001);
|
|
assert.equal(first?.authorName, 'Antragsteller');
|
|
assert.equal(first?.authorForumId, 501, 'die Forum-ID überlebt Namensänderungen');
|
|
});
|
|
|
|
it('löst Umlaut- und Anführungszeichen-Entities auf', () => {
|
|
const second = threads.find((t) => t.threadId === 7002);
|
|
assert.equal(second?.title, 'Custom Character Redesign | Alt zu Neu');
|
|
});
|
|
|
|
it('erkennt einen zurückgelassenen Verweis als solchen', () => {
|
|
const stub = threads.find((t) => t.threadId === 7003);
|
|
assert.equal(stub?.isMovedStub, true);
|
|
});
|
|
|
|
it('ignoriert Unterforen und deren letzte Beiträge', () => {
|
|
// 9999 steht im wbbBoardNode-Kasten, gehoert aber nicht in dieses Board.
|
|
assert.equal(threads.some((t) => t.threadId === 9999), false);
|
|
});
|
|
|
|
it('liest die Gesamtzahl der Seiten, nicht nur die verlinkten', () => {
|
|
// Die Blätterleiste verlinkt hier nur Seite 2 und 3, das Board hat aber 25.
|
|
// Wer die Links auswertet, liest den grössten Teil des Archivs nie.
|
|
assert.equal(parsePageCount(BOARD), 25);
|
|
});
|
|
|
|
it('fällt ohne das Element auf die Links zurück', () => {
|
|
assert.equal(parsePageCount('<a href="x&pageNo=7">7</a>'), 7);
|
|
assert.equal(parsePageCount('<html>ohne Blaetterleiste</html>'), 1);
|
|
});
|
|
|
|
it('kommt mit einer leeren Seite klar', () => {
|
|
assert.deepEqual(parseBoard('<html><body>nichts</body></html>'), []);
|
|
});
|
|
});
|
|
|
|
describe('Ankündigungen aussortieren', () => {
|
|
const threads = parseBoard(BOARD);
|
|
|
|
it('liest die Labels eines Threads', () => {
|
|
const regelwerk = threads.find((t) => t.threadId === 7004);
|
|
assert.deepEqual(regelwerk?.labels, ['Wichtig', 'Global']);
|
|
});
|
|
|
|
it('erkennt Wichtig und Information als Ausschluss', () => {
|
|
assert.equal(hasIgnoredLabel(threads.find((t) => t.threadId === 7004)!), true);
|
|
assert.equal(hasIgnoredLabel(threads.find((t) => t.threadId === 7005)!), true);
|
|
});
|
|
|
|
it('lässt echte Anträge durch, auch mit anderen Labels', () => {
|
|
assert.equal(hasIgnoredLabel(threads.find((t) => t.threadId === 7001)!), false);
|
|
});
|
|
|
|
it('wertet den Titel nicht aus', () => {
|
|
// "The Duke Rang Update" war ein echter Antrag, "Regelwerk" keiner. Am Titel
|
|
// ist das nicht zu erkennen - deshalb entscheidet allein das Label.
|
|
const ohneStichwort = parseBoard(BOARD).find((t) => t.threadId === 7005);
|
|
assert.ok(ohneStichwort, 'auch ein Thread ohne CC im Titel wird gelesen');
|
|
});
|
|
});
|
|
|
|
describe('Thread-Verweis lesen', () => {
|
|
it('nimmt den vollen Link', () => {
|
|
assert.equal(
|
|
parseThreadId('https://senseigaming.de/forum/index.php?thread/8041-cc-antrag-feldagent/'),
|
|
8041,
|
|
);
|
|
});
|
|
|
|
it('nimmt auch nur den Pfad oder die blanke Nummer', () => {
|
|
assert.equal(parseThreadId('index.php?thread/7001-irgendwas/'), 7001);
|
|
assert.equal(parseThreadId('7001'), 7001);
|
|
});
|
|
|
|
it('weist Unbrauchbares ab', () => {
|
|
assert.equal(parseThreadId(''), null);
|
|
assert.equal(parseThreadId('kein link'), null);
|
|
});
|
|
});
|
|
|
|
describe('Ruecksicht auf einen bremsenden Server', () => {
|
|
it('erkennt die Abweisungen, die "spaeter nochmal" bedeuten', () => {
|
|
assert.equal(isRateLimited(429), true);
|
|
assert.equal(isRateLimited(503), true);
|
|
assert.equal(isRateLimited(404), false, 'eine fehlende Seite kommt auch spaeter nicht');
|
|
assert.equal(isRateLimited(200), false);
|
|
});
|
|
|
|
it('haelt sich an die vom Forum genannte Wartezeit', () => {
|
|
assert.equal(retryAfterMs('30', 0), 30000);
|
|
});
|
|
|
|
it('deckelt eine unverschaemt lange Angabe', () => {
|
|
assert.equal(retryAfterMs('86400', 0), 60000);
|
|
});
|
|
|
|
it('wartet ohne Angabe mit jedem Versuch laenger', () => {
|
|
assert.equal(retryAfterMs(null, 0), 5000);
|
|
assert.equal(retryAfterMs(null, 1), 10000);
|
|
assert.equal(retryAfterMs(null, 2), 15000);
|
|
});
|
|
|
|
it('ignoriert eine unbrauchbare Angabe', () => {
|
|
// WoltLab darf hier auch ein Datum schicken - dann greift die Staffelung.
|
|
assert.equal(retryAfterMs('Wed, 21 Oct 2026 07:28:00 GMT', 1), 10000);
|
|
assert.equal(retryAfterMs('-5', 0), 5000);
|
|
});
|
|
});
|