Skip to content

Commit b893aff

Browse files
committed
Extend bad characters for pooping
1 parent ed2c6e9 commit b893aff

File tree

2 files changed

+249
-2
lines changed

2 files changed

+249
-2
lines changed

lib/src/modules/poop_name.dart

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,29 @@ const poopCharacters = [
3838
const minimalPoopCharacters = ['.', "'", '<', '[', '!', '@', r'$', '^', '*'];
3939

4040
final poopRegexp = RegExp("[${poopCharacters.map((c) => RegExp.escape(c)).join()}]");
41+
42+
/// Detects problematic characters in usernames including:
43+
/// - Invisible/control characters (zero-width spaces, direction overrides, etc.)
44+
/// - ASCII control characters
45+
/// - Mathematical Alphanumeric Symbols (U+1D400-U+1D7FF) - catches fancy Unicode text like "𝕯𝖗𝖚𝖌𝖘", "𝑇𝑟𝑎𝑣𝑖𝑠"
46+
/// - Excessive combining marks (3+ in sequence) - catches zalgo text like "ţ̴͓̺̫͓͙̹̀̔h̴͉͇̟̳̫̪͖̻̥̳͌̔̇̓̈́͗͒͌͛͛͒̕i̸̢̡̗͔͔̻̙̎̓̀̈́͒́̚ș̶̡̛̛̛̺̺͓̭̻͉̞͎̲̱̫̇͒̽͠͝"
47+
/// - Extended Latin characters - catches obscure Latin variants like "ⱦħīꞩīꞩⱦēꞩⱦ"
4148
final weirdCharsRegexp = RegExp(
42-
r'[\u200B\u200C\u200D\u2060\uFEFF\u180E\u202A-\u202E\u2066-\u2069\u00AD\u061C\uFFF9-\uFFFB\uFFFD]|[\x00-\x1F\x7F-\x9F]',
49+
r'[\u200B\u200C\u200D\u2060\uFEFF\u180E\u202A-\u202E\u2066-\u2069\u00AD\u061C\uFFF9-\uFFFB\uFFFD]'
50+
r'|[\x00-\x1F\x7F-\x9F]'
51+
r'|[\u{1D400}-\u{1D7FF}]' // Mathematical Alphanumeric Symbols
52+
r'|[\u{0300}-\u{036F}]{3,}' // 3+ combining marks (zalgo text)
53+
r'|[\u{0100}-\u{017F}]' // Latin Extended-A
54+
r'|[\u{0180}-\u{024F}]' // Latin Extended-B
55+
r'|[\u{2C60}-\u{2C7F}]' // Latin Extended-C
56+
r'|[\u{A720}-\u{A7FF}]', // Latin Extended-D
57+
unicode: true,
58+
);
59+
60+
/// Detects excessive currency symbol abuse (3+ currency symbols in username)
61+
final excessiveCurrencyRegexp = RegExp(
62+
r'([\u{20A0}-\u{20CF}].*){3,}', // 3+ currency symbols anywhere in string
63+
unicode: true,
4364
);
4465

4566
class PoopNameModule implements RequiresInitialization {
@@ -73,7 +94,8 @@ class PoopNameModule implements RequiresInitialization {
7394
return true;
7495
}
7596

76-
bool _shouldPoopName(String name) => name.startsWith(poopRegexp) || weirdCharsRegexp.hasMatch(name);
97+
bool _shouldPoopName(String name) =>
98+
name.startsWith(poopRegexp) || weirdCharsRegexp.hasMatch(name) || excessiveCurrencyRegexp.hasMatch(name);
7799

78100
String? getMemberNameForPooping(Member member) => member.nick ?? member.user?.globalName ?? member.user?.username;
79101

test/poop_name_test.dart

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
import 'package:test/test.dart';
2+
import 'package:running_on_dart/src/modules/poop_name.dart';
3+
4+
void main() {
5+
group('weirdCharsRegexp', () {
6+
test('should catch Mathematical Alphanumeric Symbols (original examples)', () {
7+
final testCases = [
8+
'𝕯𝖗𝖚𝖌𝖘',
9+
'𝕾𝖚𝖌𝖆𝖗𝕾𝖊𝖆𝖓',
10+
'𝸸𝖚𝖗𝖉𝖚𝖍 ♛',
11+
'𝑇𝑟𝑎𝑣𝑖𝑠',
12+
'𝔃𝓲𝓰𝓪',
13+
];
14+
15+
for (final testCase in testCases) {
16+
expect(
17+
weirdCharsRegexp.hasMatch(testCase),
18+
isTrue,
19+
reason: 'Should catch Mathematical Alphanumeric: "$testCase"',
20+
);
21+
}
22+
});
23+
24+
test('should catch zalgo text with excessive combining marks', () {
25+
final testCases = [
26+
'ţ̴͓̺̫͓͙̹̀̔h̴͉͇̟̳̫̪͖̻̥̳͌̔̇̓̈́͗͒͌͛͛͒̕i̸̢̡̗͔͔̻̙̎̓̀̈́͒́̚ș̶̡̛̛̛̺̺͓̭̻͉̞͎̲̱̫̇͒̽͠͝į̶̧͓̰͕̻͕̰͉̈̒̔s̴̜̪͔̲͙̻͔̗̖̩̮̔͛͘̕t̸͍̥͗͌̈́͘͘͝e̴̡͚͍̓̕ş̵̰̠͕͚̲̪̙̲̥͓̯̊͊͒͂̈͋̾̇̊̊́̊͠t̴̛̪̤͓̙̩͎͊̔͆͑̉̈́̾̈́̔͝',
27+
'th̷̼̦̞̯̞͖̝̖͍̲̪̜̱̥̝̜͍̏̐ͩ̍ͫ̾ͭ̎̀̒̋̀ͮ̈́̽̒ͭͧͤ̾́ͫ̚̕͢͟͞͞ͅi̸̡̡̡̥͕̤͎̜̗̼̰̣̥͙ͭͭ̉̔ͩ̒͂͛ͯ̑̍ͧͣͣͩ͠͞͞ș̵̸̷̨̛̮̫͙̠̲̝͉̯̗̾̈̏͂͛̏̽̊̔̌́ͧ͊̌̆̎͆̽ͦ̈́͆̚̕͞͠͠ì̸̶̶̸̢̩̱̻͔̲͓̫̼͍̮͐ͯ̉̉ͨ̑͆͐̉̑͟͡s̨̳̖͕̟̦͍̱̳̪͌ͨͫͨ͛̈̆̆̚͢t̔́ͤ̓̓̉ͨ͢͡e͕̝͒̉̃ͥ́̋s̶̻̥̱͎͉̻͙̱ͣ́̀̔͆̇̉̌̕͜t̡̮͍͔̥ͬ͂ͣ́͊_͓̞͔̰ͭ̑̐̊ͣ',
28+
'a\u0300\u0301\u0302',
29+
'e\u0300\u0301\u0302\u0303\u0304',
30+
];
31+
32+
for (final testCase in testCases) {
33+
expect(
34+
weirdCharsRegexp.hasMatch(testCase),
35+
isTrue,
36+
reason: 'Should catch zalgo text: "${testCase.replaceAll(RegExp(r'[^\x20-\x7E]'), '?')}"',
37+
);
38+
}
39+
});
40+
41+
test('should catch excessive currency symbol abuse (3+ symbols)', () {
42+
final testCases = [
43+
'₮₴₹test',
44+
'₮₴₹₽₪test',
45+
'test₮₴₹name',
46+
'₮₴₹₽₪₫₡₢₣₤₥₦₧₨₩₮₯₰₱₲₳₴₵₶₷₸₹₺₻₼₽₾₿',
47+
];
48+
49+
for (final testCase in testCases) {
50+
expect(
51+
excessiveCurrencyRegexp.hasMatch(testCase),
52+
isTrue,
53+
reason: 'Should catch excessive currency symbols: "$testCase"',
54+
);
55+
}
56+
});
57+
58+
test('should NOT catch moderate currency symbol use (1-2 symbols)', () {
59+
final testCases = [
60+
'₮testname',
61+
'test₴name',
62+
'₮test₴',
63+
'user₹name₽',
64+
];
65+
66+
for (final testCase in testCases) {
67+
expect(
68+
excessiveCurrencyRegexp.hasMatch(testCase),
69+
isFalse,
70+
reason: 'Should NOT catch moderate currency use: "$testCase"',
71+
);
72+
}
73+
});
74+
75+
test('should catch extended Latin character abuse', () {
76+
final testCases = [
77+
'ⱦħīꞩīꞩⱦēꞩⱦ',
78+
'Ă㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚě',
79+
'ƁƂƃƄƅƆƇƈƉƊƋƌƍƎƏƐƑƒƓƔƕƖƗƘƙƚƛƜƝƞƟ',
80+
'ⱠⱡⱢⱣⱤⱥⱦⱧⱨⱩⱪⱫⱬⱭⱮⱯ',
81+
'ꜰꜱꜲꜳꜴꜵꜶꜷꜸꜹꜺꜻꜼꜽꜾꜿꝀꝁꝂꝃꝄꝅ',
82+
];
83+
84+
for (final testCase in testCases) {
85+
expect(weirdCharsRegexp.hasMatch(testCase), isTrue, reason: 'Should catch extended Latin abuse: "$testCase"');
86+
}
87+
});
88+
89+
test('should preserve existing invisible character detection', () {
90+
final testCases = [
91+
'test\u200Bname',
92+
'name\u202Etext',
93+
'test\u2060name',
94+
'name\uFEFFtext',
95+
'test\u00ADname',
96+
];
97+
98+
for (final testCase in testCases) {
99+
expect(
100+
weirdCharsRegexp.hasMatch(testCase),
101+
isTrue,
102+
reason: 'Should catch existing invisible chars: "${testCase.replaceAll(RegExp(r'[^\x20-\x7E]'), '?')}"',
103+
);
104+
}
105+
});
106+
107+
test('should NOT catch normal usernames', () {
108+
final testCases = [
109+
'NormalUsername',
110+
'TestUser123',
111+
'regular_name',
112+
'user-name',
113+
'Valid.Name',
114+
'CamelCaseUser',
115+
'snake_case_user',
116+
'kebab-case-user',
117+
'User123',
118+
'SimpleTest',
119+
'café',
120+
'naïve',
121+
'résumé',
122+
];
123+
124+
for (final testCase in testCases) {
125+
expect(weirdCharsRegexp.hasMatch(testCase), isFalse, reason: 'Should NOT catch normal username: "$testCase"');
126+
}
127+
});
128+
129+
test('should catch ASCII control characters', () {
130+
final testCases = [
131+
'test\x00name',
132+
'test\x1Fname',
133+
'test\x7Fname',
134+
'test\x9Fname',
135+
];
136+
137+
for (final testCase in testCases) {
138+
expect(
139+
weirdCharsRegexp.hasMatch(testCase),
140+
isTrue,
141+
reason: 'Should catch ASCII control chars: "${testCase.replaceAll(RegExp(r'[^\x20-\x7E]'), '?')}"',
142+
);
143+
}
144+
});
145+
});
146+
147+
group('Combined detection logic (replicates _shouldPoopName)', () {
148+
test('should detect usernames that start with poop chars OR have weird chars OR excessive currency', () {
149+
final shouldBeDetected = [
150+
'.username',
151+
'!username',
152+
'@username',
153+
'(username',
154+
'𝕯𝖗𝖚𝖌𝖘',
155+
'test𝑇𝑟𝑎𝑣𝑖𝑠',
156+
'ţ̴͓̺̫͓͙̹̀̔end',
157+
'ⱦħīꞩtest',
158+
'test\u200Bname',
159+
'₮₴₹test',
160+
'user₮₴₹₽name',
161+
];
162+
163+
for (final username in shouldBeDetected) {
164+
final startsWithPoop = username.startsWith(poopRegexp);
165+
final hasWeirdChars = weirdCharsRegexp.hasMatch(username);
166+
final hasExcessiveCurrency = excessiveCurrencyRegexp.hasMatch(username);
167+
final shouldPoop = startsWithPoop || hasWeirdChars || hasExcessiveCurrency;
168+
169+
expect(
170+
shouldPoop,
171+
isTrue,
172+
reason:
173+
'Should detect problematic username: "${username.length > 20 ? '${username.substring(0, 20)}...' : username}"',
174+
);
175+
}
176+
});
177+
178+
test('should NOT detect normal usernames', () {
179+
final shouldNotBeDetected = [
180+
'NormalUser',
181+
'TestUser123',
182+
'valid_name',
183+
'CamelCase',
184+
'legitimate-name',
185+
'User.Name',
186+
'café',
187+
'résumé',
188+
'username.test',
189+
'user!test',
190+
'₮testname',
191+
'user₴name',
192+
];
193+
194+
for (final username in shouldNotBeDetected) {
195+
final startsWithPoop = username.startsWith(poopRegexp);
196+
final hasWeirdChars = weirdCharsRegexp.hasMatch(username);
197+
final hasExcessiveCurrency = excessiveCurrencyRegexp.hasMatch(username);
198+
final shouldPoop = startsWithPoop || hasWeirdChars || hasExcessiveCurrency;
199+
200+
expect(shouldPoop, isFalse, reason: 'Should NOT detect normal username: "$username"');
201+
}
202+
});
203+
});
204+
205+
group('Edge cases and performance', () {
206+
test('should handle empty and null strings gracefully', () {
207+
expect(weirdCharsRegexp.hasMatch(''), isFalse);
208+
expect(weirdCharsRegexp.hasMatch('a'), isFalse);
209+
});
210+
211+
test('should handle very long strings', () {
212+
final longNormal = 'a' * 1000;
213+
final longProblematic = '𝑎' * 100;
214+
215+
expect(weirdCharsRegexp.hasMatch(longNormal), isFalse);
216+
expect(weirdCharsRegexp.hasMatch(longProblematic), isTrue);
217+
});
218+
219+
test('should detect problematic characters anywhere in string', () {
220+
expect(weirdCharsRegexp.hasMatch('start𝑎end'), isTrue);
221+
expect(weirdCharsRegexp.hasMatch('startⱦend'), isTrue);
222+
expect(excessiveCurrencyRegexp.hasMatch('start₮₴₹end'), isTrue);
223+
});
224+
});
225+
}

0 commit comments

Comments
 (0)