Add the Tools > Ban List window

Reimplements the official client's ban list: a searchable table of the
server's bans with Add / Remove / Edit, an own-ban filter and highlight,
choosable columns, and the Add/Edit dialog whose IP and name patterns
can be read as IPv4/IPv6 wildcards, fixed strings or regular expressions
(translated to the regexes banadd takes exactly as the official client
does). Editing is add-then-delete, since the protocol has no banedit.

The ban list is read from the notifybanlist burst that answers banlist,
like channelpermlist. The menu item is only shown where the server grants
b_client_ban_list.

Also fixes ServerModel numbering permissions from 0 instead of 1, which
had every selfPermissionValue() lookup reading the neighbouring
permission — including the group-assignment menus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 23:00:18 +00:00
parent 636090d075
commit 22e0304f25
20 changed files with 1705 additions and 44 deletions

View File

@@ -0,0 +1,67 @@
package com.ts3client.net.ban;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class BanPatternTest {
@Test
void wildcardIpv4TurnsStarsIntoAddressGroups() {
assertEquals("192\\.168\\.[0-9]+\\.[0-9]+", BanPattern.WILDCARD_IPV4.ipToRegex("192.168.*.*"));
assertEquals("2001:db8:[0-9a-fA-F]+:[0-9a-fA-F]+", BanPattern.WILDCARD_IPV6.ipToRegex("2001:db8:*:*"));
}
@Test
void wildcardNamesMatchAnything() {
assertEquals("Troll.*", BanPattern.WILDCARD_IPV4.nameToRegex("Troll*"));
assertEquals("\\[Bot\\].*", BanPattern.WILDCARD_IPV6.nameToRegex("[Bot]*"));
}
@Test
void fixedStringsAreTakenLiterally() {
assertEquals("10\\.0\\.0\\.1", BanPattern.FIXED_STRING.ipToRegex("10.0.0.1"));
assertEquals("a\\*b\\?\\(c\\)\\$\\^\\|\\\\", BanPattern.FIXED_STRING.nameToRegex("a*b?(c)$^|\\"));
}
@Test
void regularExpressionsPassThrough() {
assertEquals("^Troll[0-9]{2}$", BanPattern.REGULAR_EXPRESSION.nameToRegex("^Troll[0-9]{2}$"));
}
@Test
void decodeRecoversTheNotationThatProducedTheExpression() {
BanPattern.Decoded d = BanPattern.decode("192\\.168\\.[0-9]+\\.[0-9]+", "Troll.*");
assertEquals(BanPattern.WILDCARD_IPV4, d.pattern);
assertEquals("192.168.*.*", d.ip);
assertEquals("Troll*", d.name);
d = BanPattern.decode("fe80:[0-9a-fA-F]+", "");
assertEquals(BanPattern.WILDCARD_IPV6, d.pattern);
assertEquals("fe80:*", d.ip);
d = BanPattern.decode("", "a\\*b");
assertEquals(BanPattern.FIXED_STRING, d.pattern);
assertEquals("a*b", d.name);
d = BanPattern.decode("", "^Troll[0-9]{2}$");
assertEquals(BanPattern.REGULAR_EXPRESSION, d.pattern);
assertEquals("^Troll[0-9]{2}$", d.name);
}
@Test
void plainAddressesDecodeAsWildcardsLikeTheDialogDefault() {
BanPattern.Decoded d = BanPattern.decode("10\\.0\\.0\\.1", "");
assertEquals(BanPattern.WILDCARD_IPV4, d.pattern);
assertEquals("10.0.0.1", d.ip);
}
@Test
void validityFollowsJavaRegexSyntax() {
assertTrue(BanPattern.isValidRegex(""));
assertTrue(BanPattern.isValidRegex("^a.*$"));
assertFalse(BanPattern.isValidRegex("(unclosed"));
}
}