From ec44664ee4e030bdd9e54e02019675efd63fccfd Mon Sep 17 00:00:00 2001 From: Matheus Fillipe Date: Tue, 13 Oct 2026 12:15:29 +0200 Subject: [PATCH 1/2] feat: store user and channel counts as fields and refresh them in the daily crawl --- .github/workflows/crawl.yml | 6 +- README.md | 2 + scripts/probe.py | 91 ++++- servers.json | 700 +++++++++++++++++++++++++++--------- 4 files changed, 610 insertions(+), 189 deletions(-) diff --git a/.github/workflows/crawl.yml b/.github/workflows/crawl.yml index 70b90ef..2283514 100644 --- a/.github/workflows/crawl.yml +++ b/.github/workflows/crawl.yml @@ -26,7 +26,7 @@ jobs: with: enable-cache: true - - name: Crawl servers and apply capability + icon corrections + - name: Crawl servers and apply capability, icon and user count updates run: uv run scripts/probe.py --apply ${{ inputs.prune && '--prune' || '' }} --report crawl-report.md - name: Open pull request with detected changes @@ -38,8 +38,8 @@ jobs: server-icons/** branch: bot/server-crawl delete-branch: true - commit-message: 'chore: sync server capabilities from daily crawl' - title: 'Automated server capability sync' + commit-message: 'chore: sync server capabilities and user counts from daily crawl' + title: 'Automated server capability and user count sync' body-path: crawl-report.md labels: | automated diff --git a/README.md b/README.md index 4b194da..779e648 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ This is where Obby's server list on the 'Discover' reads from. If you already know GitHub, you know what to do! 😉 If not, all you need to do is edit this file, add your server in using the same format as the rest (JSON) of the servers, and then click to make a Pull Request from your newly created fork! 😁 + +Leave out `users` and `channels`: the daily crawl reads them from your server and fills them in. ## Does The Change Happen Immediately? No, you will have to wait for one of our team to review your Pull Request. This should take a few hours, but may take a few days. diff --git a/scripts/probe.py b/scripts/probe.py index e87c585..404ad56 100644 --- a/scripts/probe.py +++ b/scripts/probe.py @@ -35,7 +35,8 @@ second connection per server that daemons reject. The icon is downloaded into server-icons/ and rewritten only when it actually changed, so the list tracks the server's current icon without needless churn; the field is left empty for -servers that advertise none. +servers that advertise none. The same welcome burst carries the LUSERS replies, +so the crawl also records the network's current user and channel counts. Running it: @@ -162,6 +163,8 @@ class TransportProbe: caps: dict[str, str] isupport: dict[str, str] complete: bool = False + users: int | None = None + channels: int | None = None @dataclass @@ -179,6 +182,8 @@ class ServerResult: any_reachable: bool detected: Detection | None icon_url: str | None + users: int | None = None + channels: int | None = None @dataclass @@ -195,6 +200,8 @@ class Server: name: str = "" description: str | None = None + users: int | None = None + channels: int | None = None wss: str | None = None ircs: str | None = None obsidian: bool | None = None @@ -216,8 +223,9 @@ class Negotiator: continuation marker until the last. In caps-only mode the handshake ends there. When `register` is set we then complete registration (`CAP END`, the caller having already sent NICK/USER) and keep reading the welcome - burst to collect the RPL_ISUPPORT (005) tokens that carry the icon URL, - finishing at end-of-MOTD (376/422) or a registration error. + burst to collect the RPL_ISUPPORT (005) tokens that carry the icon URL and + the LUSERS counts, finishing at end-of-MOTD (376/422) or a registration + error. """ # Numerics that end the welcome burst (success or terminal failure) and so @@ -232,6 +240,10 @@ def __init__(self, nick: str, register: bool) -> None: self.caps_done = False self.finished = False self._nick_tries = 0 + self._luser_client: int | None = None + self._global_users: int | None = None + self._channels: int | None = None + self._welcomed = False def feed(self, line: str) -> list[str]: line = line.rstrip() @@ -246,7 +258,7 @@ def feed(self, line: str) -> list[str]: if "CAP" in parts: return self._feed_cap(parts, trailing) if len(parts) >= 2 and parts[1].isdigit(): - return self._feed_numeric(parts[1], parts[3:]) + return self._feed_numeric(parts[1], parts[3:], trailing) if parts and parts[0] == "ERROR": self.finished = True return [] @@ -267,8 +279,25 @@ def _feed_cap(self, parts: list[str], trailing: str) -> list[str]: return [] return ["CAP END"] - def _feed_numeric(self, code: str, params: list[str]) -> list[str]: - if code == "005": + @property + def users(self) -> int | None: + return self._global_users if self._global_users is not None else self._luser_client + + @property + def channels(self) -> int | None: + # RFC 2812 sends RPL_LUSERCHANNELS only for a non-zero count. + if self._channels is None and self._welcomed and self.users is not None: + return 0 + return self._channels + + def _feed_numeric(self, code: str, params: list[str], trailing: str) -> list[str]: + if code == "251": + self._luser_client = luser_client_count(trailing) + elif code == "254" and params and params[0].isdigit(): + self._channels = int(params[0]) + elif code == "266": + self._global_users = global_users_count(params, trailing) + elif code == "005": for token in params: name, _, value = token.partition("=") self.isupport[name] = value @@ -279,10 +308,32 @@ def _feed_numeric(self, code: str, params: list[str]) -> list[str]: return [f"NICK {self.nick}"] self.finished = True elif code in self._REGISTRATION_END: + self._welcomed = code in ("376", "422") self.finished = True return [] +_LUSER_CLIENT = re.compile(r"(\d+)\D+?(\d+) invisible") +_FIRST_NUMBER = re.compile(r"\d+") + + +def luser_client_count(trailing: str) -> int | None: + """RPL_LUSERCLIENT reads "There are users and invisible + on servers"; the network's users are both kinds together.""" + match = _LUSER_CLIENT.search(trailing) + return int(match[1]) + int(match[2]) if match else None + + +def global_users_count(params: list[str], trailing: str) -> int | None: + """RPL_GLOBALUSERS carries the current count as its first parameter on most + daemons, and only inside the text ("Current global users 42, max 99") on + the rest.""" + if params and params[0].isdigit(): + return int(params[0]) + match = _FIRST_NUMBER.search(trailing) + return int(match[0]) if match else None + + def detect(caps: dict[str, str]) -> Detection: names = caps.keys() return Detection( @@ -414,7 +465,9 @@ async def _attempt_wss(url: str, timeout: float, register: bool) -> TransportPro error = f"{type(exc).__name__}: {exc}".strip() if not nego.caps_done and error is None: error = "connection closed before capability list" - return TransportProbe("wss", url, nego.caps_done, error, nego.caps, nego.isupport, nego.finished) + return TransportProbe( + "wss", url, nego.caps_done, error, nego.caps, nego.isupport, nego.finished, nego.users, nego.channels + ) async def probe_ircs(url: str, timeout: float, register: bool) -> TransportProbe: @@ -462,7 +515,9 @@ async def _attempt_ircs(url: str, timeout: float, register: bool) -> TransportPr pass if not nego.caps_done and error is None: error = "connection closed before capability list" - return TransportProbe("ircs", url, nego.caps_done, error, nego.caps, nego.isupport, nego.finished) + return TransportProbe( + "ircs", url, nego.caps_done, error, nego.caps, nego.isupport, nego.finished, nego.users, nego.channels + ) async def probe_server(entry: Server, timeout: float) -> ServerResult: @@ -518,12 +573,15 @@ async def crawl_server(entry: Server, timeout: float) -> tuple[ServerResult, lis merged_isupport.update(probe.isupport) any_reachable = any(p.reachable for p in probes) + counted = next((p for p in probes if p.users is not None and p.channels is not None), None) result = ServerResult( name=entry.name or "?", endpoints=[EndpointStatus(p.transport, p.url, p.reachable, p.error) for p in probes], any_reachable=any_reachable, detected=detect(merged_caps) if any_reachable else None, icon_url=icon_url_from(merged_isupport), + users=counted.users if counted else None, + channels=counted.channels if counted else None, ) failures = [f"{p.transport}: {p.error}" for p in probes if not p.complete and p.error] return result, failures @@ -676,7 +734,7 @@ def apply_corrections( Only ever adds or updates: an icon is set when a fresh one was downloaded this run, and a server whose icon could not be re-fetched keeps the one it - already had. Nothing is removed unless `prune` is set -- the default crawl + already had. User and channel counts follow the same rule. Nothing is removed unless `prune` is set -- the default crawl never drops a server or an icon, so a network that blocks the runner's IP (and so looks unreachable from CI) can't propose deleting a live entry. With `prune`, servers unreachable from where this ran are dropped (use only @@ -703,6 +761,9 @@ def apply_corrections( if fetched_icon and fetched_icon != entry.icon: icon_changes.append(f"{result.name}: icon {entry.icon} -> {fetched_icon}") entry.icon = fetched_icon + if result.users is not None and result.channels is not None: + entry.users = result.users + entry.channels = result.channels updated.append(entry) return updated, changes, removed, icon_changes @@ -710,6 +771,10 @@ def apply_corrections( _MARK = {True: "✅", False: "❌", None: "—"} +def _count(value: int | None) -> str: + return "—" if value is None else str(value) + + def render_report( results: list[ServerResult], changes: list[str], @@ -726,7 +791,10 @@ def render_report( lines += ["No connectivity-affecting changes to validate. ✅", ""] return "\n".join(lines) + "\n" - lines += ["| Server | wss | ircs | obsidian | sasl | voice | icon |", "|---|---|---|---|---|---|---|"] + lines += [ + "| Server | wss | ircs | obsidian | sasl | voice | icon | users | channels |", + "|---|---|---|---|---|---|---|---|---|", + ] for r in results: by_transport = {e.transport: e for e in r.endpoints} @@ -742,7 +810,8 @@ def flag(name: str) -> str: icon_cell = _MARK[bool(r.icon_url)] if icons_checked else "—" lines.append( f"| {r.name} | {cell('wss')} | {cell('ircs')} | " - f"{flag('obsidian')} | {flag('sasl')} | {flag('voice')} | {icon_cell} |" + f"{flag('obsidian')} | {flag('sasl')} | {flag('voice')} | {icon_cell} | " + f"{_count(r.users)} | {_count(r.channels)} |" ) removed_set = set(removed) diff --git a/servers.json b/servers.json index ccf5647..3917468 100644 --- a/servers.json +++ b/servers.json @@ -69,42 +69,54 @@ }, { "name": "2600net", - "description": "323 users, 233 channels on average (via netsplit.de).", + "description": "", + "users": 323, + "channels": 233, "wss": "wss://irc.2600.net:443/", "ircs": "ircs://irc.2600.net:6697", "obsidian": false }, { "name": "AIN", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.azirc.net:443/", "ircs": "ircs://irc.azirc.net:6697", "obsidian": false }, { "name": "Albirc", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.albirc.com:443/", "ircs": "ircs://irc.albirc.com:6697", "obsidian": false }, { "name": "All4masti", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.all4masti.com:443/", "ircs": "ircs://irc.all4masti.com:6698", "obsidian": false }, { "name": "AllNightCafe", - "description": "347 users, 55 channels on average (via netsplit.de).", + "description": "", + "users": 347, + "channels": 55, "wss": "wss://irc.allnightcafe.com:443/", "ircs": "ircs://irc.allnightcafe.com:6697", "obsidian": false }, { "name": "AlphaChat", - "description": "135 users, 109 channels on average (via netsplit.de).", + "description": "", + "users": 135, + "channels": 109, "wss": "wss://irc.alphachat.net:443/", "ircs": "ircs://irc.alphachat.net:6697", "obsidian": false, @@ -113,14 +125,18 @@ }, { "name": "AlphaIRC", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.alphairc.com:443/", "ircs": "ircs://irc.alphairc.com:6697", "obsidian": false }, { "name": "AmicaChat", - "description": "27 users, 14 channels on average (via netsplit.de).", + "description": "", + "users": 27, + "channels": 14, "wss": "wss://irc.amicachat.net:443/", "ircs": "ircs://irc.amicachat.net:6697", "obsidian": false, @@ -129,7 +145,9 @@ }, { "name": "AnimeFriends", - "description": "1650 users, 135 channels on average (via netsplit.de).", + "description": "", + "users": 1650, + "channels": 135, "wss": "wss://irc.animefriends.moe:443/", "ircs": "ircs://irc.animefriends.moe:7000", "obsidian": false, @@ -138,7 +156,9 @@ }, { "name": "AnonOps", - "description": "130 users, 186 channels on average (via netsplit.de).", + "description": "", + "users": 130, + "channels": 186, "wss": "wss://irc.anonops.com:443/", "ircs": "ircs://irc.anonops.com:6697", "obsidian": false, @@ -147,35 +167,45 @@ }, { "name": "ApropoRomania", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.aproporomania.ro:443/", "ircs": "ircs://irc.aproporomania.ro:6697", "obsidian": false }, { "name": "asylum", - "description": "25 users, 17 channels on average (via netsplit.de).", + "description": "", + "users": 25, + "channels": 17, "wss": "wss://irc.asylum.xyz:443/", "ircs": "ircs://irc.asylum.xyz:6697", "obsidian": false }, { "name": "AyoChat", - "description": "5211 users, 70 channels on average (via netsplit.de).", + "description": "", + "users": 5211, + "channels": 70, "wss": "wss://irc.ayochat.or.id:443/", "ircs": "ircs://irc.ayochat.or.id:6697", "obsidian": false }, { "name": "Azzurra", - "description": "147 users, 197 channels on average (via netsplit.de).", + "description": "", + "users": 147, + "channels": 197, "wss": "wss://irc.azzurra.chat:443/", "ircs": "ircs://irc.azzurra.chat:6697", "obsidian": false }, { "name": "BitreichIRC", - "description": "60 users, 21 channels on average (via netsplit.de).", + "description": "", + "users": 60, + "channels": 21, "wss": "wss://irc.bitreich.org:443/", "ircs": "ircs://irc.bitreich.org:6697", "obsidian": false, @@ -184,42 +214,54 @@ }, { "name": "Black-Cell.net", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.black-cell.net:443/", "ircs": "ircs://irc.black-cell.net:6697", "obsidian": false }, { "name": "BRASnet", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.redebrasnet.com.br:443/", "ircs": "ircs://irc.redebrasnet.com.br:6697", "obsidian": false }, { "name": "BRASnet.chat", - "description": "44 users, 20 channels on average (via netsplit.de).", + "description": "", + "users": 44, + "channels": 20, "wss": "wss://irc.brasnet.chat:443/", "ircs": "ircs://irc.brasnet.chat:6697", "obsidian": false }, { "name": "Brazink", - "description": "63 users, 143 channels on average (via netsplit.de).", + "description": "", + "users": 63, + "channels": 143, "wss": "wss://irc.brazink.net:443/", "ircs": "ircs://irc.brazink.net:6697", "obsidian": false }, { "name": "C2H5OH", - "description": "63 users, 20 channels on average (via netsplit.de).", + "description": "", + "users": 63, + "channels": 20, "wss": "wss://irc.ludost.net:443/", "ircs": "ircs://irc.ludost.net:6697", "obsidian": false }, { "name": "CakeForceUK", - "description": "31 users, 28 channels on average (via netsplit.de).", + "description": "", + "users": 31, + "channels": 28, "wss": "wss://irc.cakeforce.co.uk:443/", "ircs": "ircs://irc.cakeforce.co.uk:6697", "obsidian": false, @@ -228,7 +270,9 @@ }, { "name": "Chaat", - "description": "1903 users, 288 channels on average (via netsplit.de).", + "description": "", + "users": 1903, + "channels": 288, "wss": "wss://irc.chaat.fr:443/", "ircs": "ircs://irc.chaat.fr:6697", "obsidian": false, @@ -237,49 +281,63 @@ }, { "name": "ChamberIRC", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.chamberirc.net:443/", "ircs": "ircs://irc.chamberirc.net:6697", "obsidian": false }, { "name": "Chat.com.tr", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.chat.com.tr:443/", "ircs": "ircs://irc.chat.com.tr:6697", "obsidian": false }, { "name": "ChatArea", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc2.chatarea.nl:443/", "ircs": "ircs://irc2.chatarea.nl:6697", "obsidian": false }, { "name": "Chateo", - "description": "135 users, 16 channels on average (via netsplit.de).", + "description": "", + "users": 135, + "channels": 16, "wss": "wss://irc.chateo.org:443/", "ircs": "ircs://irc.chateo.org:6697", "obsidian": false }, { "name": "Chating.ID", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.chating.id:443/", "ircs": "ircs://irc.chating.id:6697", "obsidian": false }, { "name": "ChatIRC.net", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.chatirc.net:443/", "ircs": "ircs://irc.chatirc.net:6697", "obsidian": false }, { "name": "ChatLife", - "description": "255 users, 63 channels on average (via netsplit.de).", + "description": "", + "users": 255, + "channels": 63, "wss": "wss://irc.chatlife.net:443/", "ircs": "ircs://irc.chatlife.net:6697", "obsidian": false, @@ -288,7 +346,9 @@ }, { "name": "ChatNova", - "description": "61 users, 21 channels on average (via netsplit.de).", + "description": "", + "users": 61, + "channels": 21, "wss": "wss://irc.chatnova.fr:443/", "ircs": "ircs://irc.chatnova.fr:6697", "obsidian": false, @@ -297,7 +357,9 @@ }, { "name": "ChatNPlay", - "description": "37 users, 45 channels on average (via netsplit.de).", + "description": "", + "users": 37, + "channels": 45, "wss": "wss://irc.chatnplay.org:443/", "ircs": "ircs://irc.chatnplay.org:6697", "obsidian": false, @@ -306,7 +368,9 @@ }, { "name": "ChatSansar", - "description": "105 users, 17 channels on average (via netsplit.de).", + "description": "", + "users": 105, + "channels": 17, "wss": "wss://irc.chatsansar.com:443/", "ircs": "ircs://irc.chatsansar.com:6697", "obsidian": false, @@ -315,7 +379,9 @@ }, { "name": "Chattersnet", - "description": "1136 users, 324 channels on average (via netsplit.de).", + "description": "", + "users": 1136, + "channels": 324, "wss": "wss://irc.chattersnet.nl:443/", "ircs": "ircs://irc.chattersnet.nl:6697", "obsidian": false, @@ -324,14 +390,18 @@ }, { "name": "Chatteurs.fr", - "description": "39 users, 32 channels on average (via netsplit.de).", + "description": "", + "users": 39, + "channels": 32, "wss": "wss://irc.chatteurs.fr:443/", "ircs": "ircs://irc.chatteurs.fr:6697", "obsidian": false }, { "name": "ChillNet", - "description": "8 users, 39 channels on average (via netsplit.de).", + "description": "", + "users": 8, + "channels": 39, "wss": "wss://irc.chillnet.org:443/", "ircs": "ircs://irc.chillnet.org:6697", "obsidian": false, @@ -340,14 +410,18 @@ }, { "name": "Coders-IRC", - "description": "15 users, 12 channels on average (via netsplit.de).", + "description": "", + "users": 15, + "channels": 12, "wss": "wss://irc.coders-irc.net:443/", "ircs": "ircs://irc.coders-irc.net:6697", "obsidian": false }, { "name": "Communiti-Chat", - "description": "22 users, 15 channels on average (via netsplit.de).", + "description": "", + "users": 22, + "channels": 15, "wss": "wss://irc.communiti.chat:443/", "ircs": "ircs://irc.communiti.chat:6697", "obsidian": false, @@ -356,7 +430,9 @@ }, { "name": "Coolsmile.net", - "description": "155 users, 44 channels on average (via netsplit.de).", + "description": "", + "users": 155, + "channels": 44, "wss": "wss://irc.coolsmile.net:443/", "ircs": "ircs://irc.coolsmile.net:6697", "obsidian": false, @@ -365,7 +441,9 @@ }, { "name": "Cuff-Link", - "description": "341 users, 228 channels on average (via netsplit.de).", + "description": "", + "users": 341, + "channels": 228, "wss": "wss://irc.cuff-link.me:443/", "ircs": "ircs://irc.cuff-link.me:6697", "obsidian": false, @@ -374,7 +452,9 @@ }, { "name": "Cuplu", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.cuplu.eu:443/", "ircs": "ircs://irc.cuplu.eu:6697", "obsidian": false, @@ -383,14 +463,18 @@ }, { "name": "CyberArmy", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.cyberarmy.com:443/", "ircs": "ircs://irc.cyberarmy.com:6697", "obsidian": false }, { "name": "CyberIRC", - "description": "3256 users, 36 channels on average (via netsplit.de).", + "description": "", + "users": 3256, + "channels": 36, "wss": "wss://irc.cyberirc.org:443/", "ircs": "ircs://irc.cyberirc.org:6697", "obsidian": false, @@ -399,28 +483,36 @@ }, { "name": "CyGen", - "description": "8 users, 24 channels on average (via netsplit.de).", + "description": "", + "users": 8, + "channels": 24, "wss": "wss://irc.cygen.net:443/", "ircs": "ircs://irc.cygen.net:6697", "obsidian": false }, { "name": "d3x", - "description": "25 users, 21 channels on average (via netsplit.de).", + "description": "", + "users": 25, + "channels": 21, "wss": "wss://irc.afnet.us:443/", "ircs": "ircs://irc.afnet.us:6697", "obsidian": false }, { "name": "DareNET", - "description": "64 users, 132 channels on average (via netsplit.de).", + "description": "", + "users": 64, + "channels": 132, "wss": "wss://irc.darenet.org:443/", "ircs": "ircs://irc.darenet.org:9999", "obsidian": false }, { "name": "darkfasel.net", - "description": "616 users, 133 channels on average (via netsplit.de).", + "description": "", + "users": 616, + "channels": 133, "wss": "wss://irc.darkfasel.net:443/", "ircs": "ircs://irc.darkfasel.net:6697", "obsidian": false, @@ -429,14 +521,18 @@ }, { "name": "darkscience", - "description": "697 users, 170 channels on average (via netsplit.de).", + "description": "", + "users": 697, + "channels": 170, "wss": "wss://irc.darchoods.net:443/", "ircs": "ircs://irc.darchoods.net:6697", "obsidian": false }, { "name": "DarkWorld", - "description": "244 users, 230 channels on average (via netsplit.de).", + "description": "", + "users": 244, + "channels": 230, "wss": "wss://irc.darkworld.network:443/", "ircs": "ircs://irc.darkworld.network:6697", "obsidian": false, @@ -445,21 +541,27 @@ }, { "name": "deepspace.org", - "description": "15 users, 3 channels on average (via netsplit.de).", + "description": "", + "users": 15, + "channels": 3, "wss": "wss://irc.deepspace.org:443/", "ircs": "ircs://irc.deepspace.org:6697", "obsidian": false }, { "name": "degunino.net", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.degunino.net:443/", "ircs": "ircs://irc.degunino.net:6697", "obsidian": false }, { "name": "Deutscher-Chat.de", - "description": "324 users, 44 channels on average (via netsplit.de).", + "description": "", + "users": 324, + "channels": 44, "wss": "wss://irc.deutscher-chat.de:443/", "ircs": "ircs://irc.deutscher-chat.de:6697", "obsidian": false, @@ -468,7 +570,9 @@ }, { "name": "DigitalIRC", - "description": "1932 users, 110 channels on average (via netsplit.de).", + "description": "", + "users": 1932, + "channels": 110, "wss": "wss://irc.digitalirc.org:443/", "ircs": "ircs://irc.digitalirc.org:6697", "obsidian": false, @@ -477,7 +581,9 @@ }, { "name": "Discut.Biz", - "description": "39 users, 16 channels on average (via netsplit.de).", + "description": "", + "users": 39, + "channels": 16, "wss": "wss://irc.discut.biz:443/", "ircs": "ircs://irc.discut.biz:6697", "obsidian": false, @@ -486,14 +592,18 @@ }, { "name": "DynastyNet", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.dynastynet.net:443/", "ircs": "ircs://irc.dynastynet.net:6697", "obsidian": false }, { "name": "EfesNet", - "description": "2406 users, 174 channels on average (via netsplit.de).", + "description": "", + "users": 2406, + "channels": 174, "wss": "wss://irc.efes.net:443/", "ircs": "ircs://irc.efes.net:6697", "obsidian": false, @@ -502,28 +612,36 @@ }, { "name": "EFnet", - "description": "8804 users, 5595 channels on average (via netsplit.de).", + "description": "", + "users": 8804, + "channels": 5595, "wss": "wss://irc.efnet.org:443/", "ircs": "ircs://irc.efnet.org:6697", "obsidian": false }, { "name": "ElectroCode", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://toaster.electrocode.net:443/", "ircs": "ircs://toaster.electrocode.net:6697", "obsidian": false }, { "name": "Entrechat", - "description": "187 users, 107 channels on average (via netsplit.de).", + "description": "", + "users": 187, + "channels": 107, "wss": "wss://irc.entrechat.eu:443/", "ircs": "ircs://irc.entrechat.eu:6677", "obsidian": false }, { "name": "EvilNET", - "description": "453 users, 89 channels on average (via netsplit.de).", + "description": "", + "users": 453, + "channels": 89, "wss": "wss://irc.evilnet.org:443/", "ircs": "ircs://irc.evilnet.org:6697", "obsidian": false, @@ -532,28 +650,36 @@ }, { "name": "EvoChat", - "description": "1229 users, 227 channels on average (via netsplit.de).", + "description": "", + "users": 1229, + "channels": 227, "wss": "wss://irc.evochat.co.id:443/", "ircs": "ircs://irc.evochat.co.id:6697", "obsidian": false }, { "name": "FlexChat", - "description": "5381 users, 75 channels on average (via netsplit.de).", + "description": "", + "users": 5381, + "channels": 75, "wss": "wss://irc.flexchat.org:443/", "ircs": "ircs://irc.flexchat.org:6697", "obsidian": false }, { "name": "ForumCerdas", - "description": "167 users, 63 channels on average (via netsplit.de).", + "description": "", + "users": 167, + "channels": 63, "wss": "wss://irc.forumcerdas.net:443/", "ircs": "ircs://irc.forumcerdas.net:6697", "obsidian": false }, { "name": "freenode", - "description": "4988 users, 7965 channels on average (via netsplit.de).", + "description": "", + "users": 4988, + "channels": 7965, "wss": "wss://chat.freenode.net:443/", "ircs": "ircs://chat.freenode.net:6697", "obsidian": false, @@ -562,42 +688,54 @@ }, { "name": "FreeUniBG", - "description": "13470 users, 652 channels on average (via netsplit.de).", + "description": "", + "users": 13470, + "channels": 652, "wss": "wss://irc.freeunibg.eu:443/", "ircs": "ircs://irc.freeunibg.eu:6697", "obsidian": false }, { "name": "Genscripts", - "description": "54 users, 5 channels on average (via netsplit.de).", + "description": "", + "users": 54, + "channels": 5, "wss": "wss://irc.genscripts.net:443/", "ircs": "ircs://irc.genscripts.net:6669", "obsidian": false }, { "name": "GhostNet", - "description": "13 users, 62 channels on average (via netsplit.de).", + "description": "", + "users": 13, + "channels": 62, "wss": "wss://irc.daleksandrov.eu:443/", "ircs": "ircs://irc.daleksandrov.eu:6697", "obsidian": false }, { "name": "gigachat", - "description": "18 users, 9 channels on average (via netsplit.de).", + "description": "", + "users": 18, + "channels": 9, "wss": "wss://irc.gigachat.net:443/", "ircs": "ircs://irc.gigachat.net:6697", "obsidian": false }, { "name": "Global-Irc.Eu", - "description": "539 users, 46 channels on average (via netsplit.de).", + "description": "", + "users": 539, + "channels": 46, "wss": "wss://irc.global-irc.eu:443/", "ircs": "ircs://irc.global-irc.eu:6697", "obsidian": false }, { "name": "H4x0red", - "description": "19 users, 11 channels on average (via netsplit.de).", + "description": "", + "users": 19, + "channels": 11, "wss": "wss://irc.h4x0.red:443/", "ircs": "ircs://irc.h4x0.red:6697", "obsidian": false, @@ -606,7 +744,9 @@ }, { "name": "hackint", - "description": "10723 users, 2077 channels on average (via netsplit.de).", + "description": "", + "users": 10723, + "channels": 2077, "wss": "wss://irc.hackint.org:443/", "ircs": "ircs://irc.hackint.org:6697", "obsidian": false, @@ -615,7 +755,9 @@ }, { "name": "hashbang.sh", - "description": "78 users, 50 channels on average (via netsplit.de).", + "description": "", + "users": 78, + "channels": 50, "wss": "wss://irc.hashbang.sh:443/", "ircs": "ircs://irc.hashbang.sh:6697", "obsidian": false, @@ -624,21 +766,27 @@ }, { "name": "Hosnet", - "description": "25 users, 10 channels on average (via netsplit.de).", + "description": "", + "users": 25, + "channels": 10, "wss": "wss://tchat.hosnet.fr:443/", "ircs": "ircs://tchat.hosnet.fr:6697", "obsidian": false }, { "name": "Hub4Ever.Org", - "description": "318 users, 17 channels on average (via netsplit.de).", + "description": "", + "users": 318, + "channels": 17, "wss": "wss://irc.hub4ever.org:443/", "ircs": "ircs://irc.hub4ever.org:6697", "obsidian": false }, { "name": "HybridIRC", - "description": "22781 users, 441 channels on average (via netsplit.de).", + "description": "", + "users": 22781, + "channels": 441, "wss": "wss://irc.hybridirc.com:443/", "ircs": "ircs://irc.hybridirc.com:6697", "obsidian": false, @@ -647,35 +795,45 @@ }, { "name": "IceStarIRC", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.icestarirc.org:443/", "ircs": "ircs://irc.icestarirc.org:6697", "obsidian": false }, { "name": "iChat", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc2.ichat.ro:443/", "ircs": "ircs://irc2.ichat.ro:6697", "obsidian": false }, { "name": "ICQ-Chat", - "description": "132 users, 84 channels on average (via netsplit.de).", + "description": "", + "users": 132, + "channels": 84, "wss": "wss://irc.icq-chat.com:443/", "ircs": "ircs://irc.icq-chat.com:6697", "obsidian": false }, { "name": "Inframonde", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.inframonde.org:443/", "ircs": "ircs://irc.inframonde.org:6697", "obsidian": false }, { "name": "interlinked.me", - "description": "66 users, 60 channels on average (via netsplit.de).", + "description": "", + "users": 66, + "channels": 60, "wss": "wss://irc.interlinked.me:443/", "ircs": "ircs://irc.interlinked.me:9999", "obsidian": false, @@ -684,14 +842,18 @@ }, { "name": "IRC-Ban.de", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.irc-ban.de:443/", "ircs": "ircs://irc.irc-ban.de:6697", "obsidian": false }, { "name": "IRC-Nerds", - "description": "1235 users, 248 channels on average (via netsplit.de).", + "description": "", + "users": 1235, + "channels": 248, "wss": "wss://irc.irc-nerds.net:443/", "ircs": "ircs://irc.irc-nerds.net:6697", "obsidian": false, @@ -700,7 +862,9 @@ }, { "name": "IRCCloud", - "description": "695 users, 221 channels on average (via netsplit.de).", + "description": "", + "users": 695, + "channels": 221, "wss": "wss://irc.irccloud.com:443/", "ircs": "ircs://irc.irccloud.com:6697", "obsidian": false, @@ -709,28 +873,36 @@ }, { "name": "IRCDrama", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.ircdrama.ca:443/", "ircs": "ircs://irc.ircdrama.ca:6697", "obsidian": false }, { "name": "IRCGate.it", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.ircgate.it:443/", "ircs": "ircs://irc.ircgate.it:6697", "obsidian": false }, { "name": "IRCnet", - "description": "15611 users, 9203 channels on average (via netsplit.de).", + "description": "", + "users": 15611, + "channels": 9203, "wss": "wss://irc.us.ircnet.net:443/", "ircs": "ircs://irc.us.ircnet.net:6697", "obsidian": false }, { "name": "IRCNow", - "description": "544 users, 567 channels on average (via netsplit.de).", + "description": "", + "users": 544, + "channels": 567, "wss": "wss://irc.ircnow.org:443/", "ircs": "ircs://irc.ircnow.org:6697", "obsidian": false, @@ -739,42 +911,54 @@ }, { "name": "IRCserveR_Italia", - "description": "59 users, 29 channels on average (via netsplit.de).", + "description": "", + "users": 59, + "channels": 29, "wss": "wss://irc.ircserver.it:443/", "ircs": "ircs://irc.ircserver.it:6696", "obsidian": false }, { "name": "IRCsource", - "description": "111 users, 70 channels on average (via netsplit.de).", + "description": "", + "users": 111, + "channels": 70, "wss": "wss://irc.ircsource.net:443/", "ircs": "ircs://irc.ircsource.net:6697", "obsidian": false }, { "name": "Jeko", - "description": "14 users, 3 channels on average (via netsplit.de).", + "description": "", + "users": 14, + "channels": 3, "wss": "wss://irc.jeko.org:443/", "ircs": "ircs://irc.jeko.org:6697", "obsidian": false }, { "name": "Kahkaha.Gen.Tr", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.kahkaha.gen.tr:443/", "ircs": "ircs://irc.kahkaha.gen.tr:6697", "obsidian": false }, { "name": "KonnectChatIRC", - "description": "4705 users, 100 channels on average (via netsplit.de).", + "description": "", + "users": 4705, + "channels": 100, "wss": "wss://irc.konnectchatirc.net:443/", "ircs": "ircs://irc.konnectchatirc.net:6697", "obsidian": false }, { "name": "LewdChat", - "description": "252 users, 585 channels on average (via netsplit.de).", + "description": "", + "users": 252, + "channels": 585, "wss": "wss://irc.lewdchat.com:443/", "ircs": "ircs://irc.lewdchat.com:6697", "obsidian": false, @@ -783,7 +967,9 @@ }, { "name": "Libera.Chat", - "description": "31721 users, 22360 channels on average (via netsplit.de).", + "description": "", + "users": 31721, + "channels": 22360, "wss": "wss://irc.libera.chat:443/", "ircs": "ircs://irc.libera.chat:6697", "obsidian": false, @@ -792,7 +978,9 @@ }, { "name": "LibertaCasa", - "description": "21 users, 141 channels on average (via netsplit.de).", + "description": "", + "users": 21, + "channels": 141, "wss": "wss://irc.liberta.casa:443/", "ircs": "ircs://irc.liberta.casa:6697", "obsidian": false, @@ -801,7 +989,9 @@ }, { "name": "LibraIRC", - "description": "203 users, 265 channels on average (via netsplit.de).", + "description": "", + "users": 203, + "channels": 265, "wss": "wss://irc.librairc.net:443/", "ircs": "ircs://irc.librairc.net:6697", "obsidian": false, @@ -810,21 +1000,27 @@ }, { "name": "LinkNet", - "description": "2420 users, 1807 channels on average (via netsplit.de).", + "description": "", + "users": 2420, + "channels": 1807, "wss": "wss://irc.link-net.org:443/", "ircs": "ircs://irc.link-net.org:7000", "obsidian": false }, { "name": "LizardIRC", - "description": "94 users, 92 channels on average (via netsplit.de).", + "description": "", + "users": 94, + "channels": 92, "wss": "wss://irc.lizardirc.org:443/", "ircs": "ircs://irc.lizardirc.org:6697", "obsidian": false }, { "name": "lowendirc.com", - "description": "28 users, 13 channels on average (via netsplit.de).", + "description": "", + "users": 28, + "channels": 13, "wss": "wss://lowendirc.com:443/", "ircs": "ircs://lowendirc.com:6697", "obsidian": false, @@ -833,14 +1029,18 @@ }, { "name": "Luatic", - "description": "157 users, 1654 channels on average (via netsplit.de).", + "description": "", + "users": 157, + "channels": 1654, "wss": "wss://irc.luatic.net:443/", "ircs": "ircs://irc.luatic.net:6697", "obsidian": false }, { "name": "LUG", - "description": "43 users, 10 channels on average (via netsplit.de).", + "description": "", + "users": 43, + "channels": 10, "wss": "wss://irc1.lug.ro:443/", "ircs": "ircs://irc1.lug.ro:6697", "obsidian": false, @@ -849,7 +1049,9 @@ }, { "name": "LunarIRC", - "description": "231 users, 221 channels on average (via netsplit.de).", + "description": "", + "users": 231, + "channels": 221, "wss": "wss://irc.lunarirc.net:443/", "ircs": "ircs://irc.lunarirc.net:6697", "obsidian": false, @@ -858,7 +1060,9 @@ }, { "name": "MansionNET", - "description": "108 users, 58 channels on average (via netsplit.de).", + "description": "", + "users": 108, + "channels": 58, "wss": "wss://irc.inthemansion.com:443/", "ircs": "ircs://irc.inthemansion.com:6697", "obsidian": false, @@ -868,28 +1072,36 @@ }, { "name": "Mavra.Net", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.mavra.net:443/", "ircs": "ircs://irc.mavra.net:6697", "obsidian": false }, { "name": "MeFalcon", - "description": "62 users, 14 channels on average (via netsplit.de).", + "description": "", + "users": 62, + "channels": 14, "wss": "wss://irc.mefalcon.org:443/", "ircs": "ircs://irc.mefalcon.org:6697", "obsidian": false }, { "name": "Mobilci.Net", - "description": "63 users, 23 channels on average (via netsplit.de).", + "description": "", + "users": 63, + "channels": 23, "wss": "wss://irc.mobilci.net:443/", "ircs": "ircs://irc.mobilci.net:6697", "obsidian": false }, { "name": "Morphie", - "description": "49 users, 27 channels on average (via netsplit.de).", + "description": "", + "users": 49, + "channels": 27, "wss": "wss://irc.morphie.net:443/", "ircs": "ircs://irc.morphie.net:6697", "obsidian": false, @@ -898,7 +1110,9 @@ }, { "name": "MyCooldude", - "description": "78 users, 119 channels on average (via netsplit.de).", + "description": "", + "users": 78, + "channels": 119, "wss": "wss://irc.mycooldude.info:443/", "ircs": "ircs://irc.mycooldude.info:6697", "obsidian": false, @@ -907,7 +1121,9 @@ }, { "name": "NekoNet", - "description": "14 users, 2 channels on average (via netsplit.de).", + "description": "", + "users": 14, + "channels": 2, "wss": "wss://irc.neko-net.org:443/", "ircs": "ircs://irc.neko-net.org:6697", "obsidian": false, @@ -916,7 +1132,9 @@ }, { "name": "Net-Tchat", - "description": "615 users, 111 channels on average (via netsplit.de).", + "description": "", + "users": 615, + "channels": 111, "wss": "wss://irc.net-tchat.fr:443/", "ircs": "ircs://irc.net-tchat.fr:6697", "obsidian": false, @@ -925,7 +1143,9 @@ }, { "name": "Nether.RIP", - "description": "1064 users, 18 channels on average (via netsplit.de).", + "description": "", + "users": 1064, + "channels": 18, "wss": "wss://irc.nether.rip:443/", "ircs": "ircs://irc.nether.rip:6697", "obsidian": false, @@ -934,14 +1154,18 @@ }, { "name": "newchat.gr", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://nostalgia.newchat.gr:443/", "ircs": "ircs://nostalgia.newchat.gr:6697", "obsidian": false }, { "name": "NewNet", - "description": "204 users, 115 channels on average (via netsplit.de).", + "description": "", + "users": 204, + "channels": 115, "wss": "wss://irc.newnet.net:443/", "ircs": "ircs://irc.newnet.net:6697", "obsidian": false, @@ -950,7 +1174,9 @@ }, { "name": "ngpre", - "description": "50 users, 25 channels on average (via netsplit.de).", + "description": "", + "users": 50, + "channels": 25, "wss": "wss://irc.ngp.re:443/", "ircs": "ircs://irc.ngp.re:6697", "obsidian": false, @@ -959,7 +1185,9 @@ }, { "name": "Noxether", - "description": "67 users, 21 channels on average (via netsplit.de).", + "description": "", + "users": 67, + "channels": 21, "wss": "wss://irc.noxether.net:443/", "ircs": "ircs://irc.noxether.net:6697", "obsidian": false, @@ -968,7 +1196,9 @@ }, { "name": "OFTC", - "description": "19740 users, 4116 channels on average (via netsplit.de).", + "description": "", + "users": 19740, + "channels": 4116, "wss": "wss://irc.oftc.net:443/", "ircs": "ircs://irc.oftc.net:6697", "obsidian": false, @@ -977,7 +1207,9 @@ }, { "name": "Orixon", - "description": "595 users, 197 channels on average (via netsplit.de).", + "description": "", + "users": 595, + "channels": 197, "wss": "wss://irc.orixon.org:443/", "ircs": "ircs://irc.orixon.org:9999", "obsidian": false, @@ -986,14 +1218,18 @@ }, { "name": "oshaberi", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.oshaberi.ne.jp:443/", "ircs": "ircs://irc.oshaberi.ne.jp:6697", "obsidian": false }, { "name": "OtakuboX", - "description": "48 users, 47 channels on average (via netsplit.de).", + "description": "", + "users": 48, + "channels": 47, "wss": "wss://irc.otakubox.de:443/", "ircs": "ircs://irc.otakubox.de:6697", "obsidian": false, @@ -1002,7 +1238,9 @@ }, { "name": "ouchnet", - "description": "40 users, 91 channels on average (via netsplit.de).", + "description": "", + "users": 40, + "channels": 91, "wss": "wss://lorddank.ouch.chat:443/", "ircs": "ircs://lorddank.ouch.chat:6697", "obsidian": false, @@ -1011,7 +1249,9 @@ }, { "name": "out-dated.net", - "description": "77 users, 24 channels on average (via netsplit.de).", + "description": "", + "users": 77, + "channels": 24, "wss": "wss://irc.out-dated.net:443/", "ircs": "ircs://irc.out-dated.net:6697", "obsidian": false, @@ -1020,21 +1260,27 @@ }, { "name": "OzOrg", - "description": "66 users, 37 channels on average (via netsplit.de).", + "description": "", + "users": 66, + "channels": 37, "wss": "wss://irc.oz.org:443/", "ircs": "ircs://irc.oz.org:6697", "obsidian": false }, { "name": "PenguinFriends", - "description": "12 users, 8 channels on average (via netsplit.de).", + "description": "", + "users": 12, + "channels": 8, "wss": "wss://irc.penguinfriends.org:443/", "ircs": "ircs://irc.penguinfriends.org:6670", "obsidian": false }, { "name": "PriyoBD", - "description": "22 users, 4 channels on average (via netsplit.de).", + "description": "", + "users": 22, + "channels": 4, "wss": "wss://irc.priyobd.com:443/", "ircs": "ircs://irc.priyobd.com:6697", "obsidian": false, @@ -1043,35 +1289,45 @@ }, { "name": "RadioClick", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.radioclick.ro:443/", "ircs": "ircs://irc.radioclick.ro:6697", "obsidian": false }, { "name": "RedeSul", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.redesul.net.br:443/", "ircs": "ircs://irc.redesul.net.br:6697", "obsidian": false }, { "name": "RenCorner", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.rencorner.com:443/", "ircs": "ircs://irc.rencorner.com:6697", "obsidian": false }, { "name": "ReplIRC_Solanum", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://shell.oddprotocol.org:443/", "ircs": "ircs://shell.oddprotocol.org:7427", "obsidian": false }, { "name": "RetroNode", - "description": "128 users, 133 channels on average (via netsplit.de).", + "description": "", + "users": 128, + "channels": 133, "wss": "wss://irc.retronode.org:443/", "ircs": "ircs://irc.retronode.org:6697", "obsidian": false, @@ -1080,14 +1336,18 @@ }, { "name": "Ring.of.Lightning", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.ringoflightning.net:443/", "ircs": "ircs://irc.ringoflightning.net:6697", "obsidian": false }, { "name": "RisposteInformatiche", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.risposteinformatiche.it:443/", "ircs": "ircs://irc.risposteinformatiche.it:6697", "obsidian": false, @@ -1096,14 +1356,18 @@ }, { "name": "RoIRCop", - "description": "88 users, 17 channels on average (via netsplit.de).", + "description": "", + "users": 88, + "channels": 17, "wss": "wss://irc.roircop.info:443/", "ircs": "ircs://irc.roircop.info:6697", "obsidian": false }, { "name": "runxiyu", - "description": "117 users, 63 channels on average (via netsplit.de).", + "description": "", + "users": 117, + "channels": 63, "wss": "wss://irc.runxiyu.org:443/", "ircs": "ircs://irc.runxiyu.org:6697", "obsidian": false, @@ -1112,28 +1376,36 @@ }, { "name": "RusNet", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.rus-net.org:443/", "ircs": "ircs://irc.rus-net.org:6697", "obsidian": false }, { "name": "SBSeeds", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://sbseeds.sytes.net:443/", "ircs": "ircs://sbseeds.sytes.net:6697", "obsidian": false }, { "name": "Serverfreakz", - "description": "39 users, 13 channels on average (via netsplit.de).", + "description": "", + "users": 39, + "channels": 13, "wss": "wss://irc.serverfreakz.de:443/", "ircs": "ircs://irc.serverfreakz.de:6697", "obsidian": false }, { "name": "Shelltalk", - "description": "542 users, 565 channels on average (via netsplit.de).", + "description": "", + "users": 542, + "channels": 565, "wss": "wss://shelltalk.net:443/", "ircs": "ircs://shelltalk.net:6697", "obsidian": false, @@ -1142,7 +1414,9 @@ }, { "name": "SimosNap", - "description": "3083 users, 606 channels on average (via netsplit.de).", + "description": "", + "users": 3083, + "channels": 606, "wss": "wss://irc.simosnap.com:443/", "ircs": "ircs://irc.simosnap.com:6697", "obsidian": false, @@ -1151,70 +1425,90 @@ }, { "name": "SiSrv", - "description": "19 users, 4 channels on average (via netsplit.de).", + "description": "", + "users": 19, + "channels": 4, "wss": "wss://irc.sisrv.net:443/", "ircs": "ircs://irc.sisrv.net:6697", "obsidian": false }, { "name": "SkyChatz", - "description": "11555 users, 955 channels on average (via netsplit.de).", + "description": "", + "users": 11555, + "channels": 955, "wss": "wss://irc.skychatz.org:443/", "ircs": "ircs://irc.skychatz.org:6697", "obsidian": false }, { "name": "Smurfnet", - "description": "223 users, 94 channels on average (via netsplit.de).", + "description": "", + "users": 223, + "channels": 94, "wss": "wss://irc.smurfnet.ch:443/", "ircs": "ircs://irc.smurfnet.ch:6697", "obsidian": false }, { "name": "SohbetBurada", - "description": "412 users, 84 channels on average (via netsplit.de).", + "description": "", + "users": 412, + "channels": 84, "wss": "wss://irc.sohbetburada.com:443/", "ircs": "ircs://irc.sohbetburada.com:6697", "obsidian": false }, { "name": "SomeNet", - "description": "18 users, 10 channels on average (via netsplit.de).", + "description": "", + "users": 18, + "channels": 10, "wss": "wss://irc.somenet.org:443/", "ircs": "ircs://irc.somenet.org:6697", "obsidian": false }, { "name": "Soyjak-Party", - "description": "84 users, 11 channels on average (via netsplit.de).", + "description": "", + "users": 84, + "channels": 11, "wss": "wss://irc.soyjak.st:443/", "ircs": "ircs://irc.soyjak.st:6697", "obsidian": false }, { "name": "soyle.net", - "description": "412 users, 85 channels on average (via netsplit.de).", + "description": "", + "users": 412, + "channels": 85, "wss": "wss://irc.soyle.net:443/", "ircs": "ircs://irc.soyle.net:6697", "obsidian": false }, { "name": "SoylentNews", - "description": "84 users, 55 channels on average (via netsplit.de).", + "description": "", + "users": 84, + "channels": 55, "wss": "wss://irc.soylentnews.org:443/", "ircs": "ircs://irc.soylentnews.org:6697", "obsidian": false }, { "name": "StayNet", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.staynet.org:443/", "ircs": "ircs://irc.staynet.org:6697", "obsidian": false }, { "name": "Striked", - "description": "12 users, 2 channels on average (via netsplit.de).", + "description": "", + "users": 12, + "channels": 2, "wss": "wss://irc.striked.org:443/", "ircs": "ircs://irc.striked.org:6697", "obsidian": false, @@ -1223,7 +1517,9 @@ }, { "name": "StyxNet", - "description": "89 users, 50 channels on average (via netsplit.de).", + "description": "", + "users": 89, + "channels": 50, "wss": "wss://irc.styxnet.tech:443/", "ircs": "ircs://irc.styxnet.tech:6697", "obsidian": false, @@ -1232,14 +1528,18 @@ }, { "name": "Subluminal", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.subluminal.net:443/", "ircs": "ircs://irc.subluminal.net:6697", "obsidian": false }, { "name": "tawx", - "description": "58 users, 15 channels on average (via netsplit.de).", + "description": "", + "users": 58, + "channels": 15, "wss": "wss://irc.tawx.net:443/", "ircs": "ircs://irc.tawx.net:6668", "obsidian": false, @@ -1248,21 +1548,27 @@ }, { "name": "tChat", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.tchat.chat:443/", "ircs": "ircs://irc.tchat.chat:6697", "obsidian": false }, { "name": "tChatIRC", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://tchatirc.fr:443/", "ircs": "ircs://tchatirc.fr:6697", "obsidian": false }, { "name": "TeapotChat", - "description": "33 users, 22 channels on average (via netsplit.de).", + "description": "", + "users": 33, + "channels": 22, "wss": "wss://irc.teapot.chat:443/", "ircs": "ircs://irc.teapot.chat:6697", "obsidian": false, @@ -1271,7 +1577,9 @@ }, { "name": "TechNet", - "description": "177 users, 155 channels on average (via netsplit.de).", + "description": "", + "users": 177, + "channels": 155, "wss": "wss://irc.technet.chat:443/", "ircs": "ircs://irc.technet.chat:6697", "obsidian": false, @@ -1280,7 +1588,9 @@ }, { "name": "techrights", - "description": "70 users, 93 channels on average (via netsplit.de).", + "description": "", + "users": 70, + "channels": 93, "wss": "wss://irc.techrights.org:443/", "ircs": "ircs://irc.techrights.org:6697", "obsidian": false, @@ -1289,7 +1599,9 @@ }, { "name": "Techtronix", - "description": "63 users, 36 channels on average (via netsplit.de).", + "description": "", + "users": 63, + "channels": 36, "wss": "wss://irc.techtronix.net:443/", "ircs": "ircs://irc.techtronix.net:6697", "obsidian": false, @@ -1298,7 +1610,9 @@ }, { "name": "Teranova", - "description": "164 users, 32 channels on average (via netsplit.de).", + "description": "", + "users": 164, + "channels": 32, "wss": "wss://irc.teranova.net:443/", "ircs": "ircs://irc.teranova.net:6697", "obsidian": false, @@ -1307,7 +1621,9 @@ }, { "name": "TheAirlock", - "description": "40 users, 38 channels on average (via netsplit.de).", + "description": "", + "users": 40, + "channels": 38, "wss": "wss://irc.theairlock.net:443/", "ircs": "ircs://irc.theairlock.net:7778", "obsidian": false, @@ -1316,7 +1632,9 @@ }, { "name": "tilde.chat", - "description": "906 users, 384 channels on average (via netsplit.de).", + "description": "", + "users": 906, + "channels": 384, "wss": "wss://irc.tilde.chat:443/", "ircs": "ircs://irc.tilde.chat:6697", "obsidian": false, @@ -1325,7 +1643,9 @@ }, { "name": "TransAdvice", - "description": "361 users, 52 channels on average (via netsplit.de).", + "description": "", + "users": 361, + "channels": 52, "wss": "wss://irc.transadvice.org:443/", "ircs": "ircs://irc.transadvice.org:6697", "obsidian": false, @@ -1334,14 +1654,18 @@ }, { "name": "TrYPNET", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.trypnet.net:443/", "ircs": "ircs://irc.trypnet.net:6697", "obsidian": false }, { "name": "TwistedNet", - "description": "214 users, 103 channels on average (via netsplit.de).", + "description": "", + "users": 214, + "channels": 103, "wss": "wss://irc.twistednet.org:443/", "ircs": "ircs://irc.twistednet.org:6697", "obsidian": false, @@ -1350,14 +1674,18 @@ }, { "name": "UnifiedChat", - "description": "661 users, 29 channels on average (via netsplit.de).", + "description": "", + "users": 661, + "channels": 29, "wss": "wss://irc.unifiedchat.net:443/", "ircs": "ircs://irc.unifiedchat.net:6697", "obsidian": false }, { "name": "UnitedChat", - "description": "50 users, 52 channels on average (via netsplit.de).", + "description": "", + "users": 50, + "channels": 52, "wss": "wss://irc.unitedchat.org:443/", "ircs": "ircs://irc.unitedchat.org:6697", "obsidian": false, @@ -1366,21 +1694,27 @@ }, { "name": "UniversoChat", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.universochat.com:443/", "ircs": "ircs://irc.universochat.com:6697", "obsidian": false }, { "name": "VoodooNet", - "description": "19 users, 2 channels on average (via netsplit.de).", + "description": "", + "users": 19, + "channels": 2, "wss": "wss://irc.voodoonet.biz:443/", "ircs": "ircs://irc.voodoonet.biz:6668", "obsidian": false }, { "name": "w3.org", - "description": "259 users, 522 channels on average (via netsplit.de).", + "description": "", + "users": 259, + "channels": 522, "wss": "wss://irc.w3.org:443/", "ircs": "ircs://irc.w3.org:6697", "obsidian": false, @@ -1389,7 +1723,9 @@ }, { "name": "WebIRC", - "description": "46 users, 33 channels on average (via netsplit.de).", + "description": "", + "users": 46, + "channels": 33, "wss": "wss://irc.webirc.chat:443/", "ircs": "ircs://irc.webirc.chat:6697", "obsidian": false, @@ -1398,28 +1734,36 @@ }, { "name": "WetFish", - "description": "183 users, 147 channels on average (via netsplit.de).", + "description": "", + "users": 183, + "channels": 147, "wss": "wss://irc.wetfish.net:443/", "ircs": "ircs://irc.wetfish.net:6697", "obsidian": false }, { "name": "WNet", - "description": "698 users, 42 channels on average (via netsplit.de).", + "description": "", + "users": 698, + "channels": 42, "wss": "wss://irc.wnet.tk:443/", "ircs": "ircs://irc.wnet.tk:6697", "obsidian": false }, { "name": "Wotnet", - "description": "0 users, 0 channels on average (via netsplit.de).", + "description": "", + "users": 0, + "channels": 0, "wss": "wss://irc.wotnet.com:443/", "ircs": "ircs://irc.wotnet.com:9000", "obsidian": false }, { "name": "XeroMem", - "description": "66 users, 48 channels on average (via netsplit.de).", + "description": "", + "users": 66, + "channels": 48, "wss": "wss://irc.xeromem.com:443/", "ircs": "ircs://irc.xeromem.com:6697", "obsidian": false, @@ -1428,7 +1772,9 @@ }, { "name": "Xertion", - "description": "119 users, 94 channels on average (via netsplit.de).", + "description": "", + "users": 119, + "channels": 94, "wss": "wss://irc.xertion.org:443/", "ircs": "ircs://irc.xertion.org:6697", "obsidian": false, @@ -1437,14 +1783,18 @@ }, { "name": "Zemra", - "description": "46 users, 13 channels on average (via netsplit.de).", + "description": "", + "users": 46, + "channels": 13, "wss": "wss://irc.zemra.org:443/", "ircs": "ircs://irc.zemra.org:6697", "obsidian": false }, { "name": "Zik.chat", - "description": "41 users, 40 channels on average (via netsplit.de).", + "description": "", + "users": 41, + "channels": 40, "wss": "wss://irc.zik.chat:443/", "ircs": "ircs://irc.zik.chat:6697", "obsidian": false, From e5bb1fadcaea1f66340946d86c0e7582fa408363 Mon Sep 17 00:00:00 2001 From: Matheus Fillipe Date: Tue, 13 Oct 2026 12:55:57 +0200 Subject: [PATCH 2/2] fix: parse only short ascii counts and list servers whose counts were not refreshed --- README.md | 1 + scripts/probe.py | 42 ++++++++++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 779e648..da4750c 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ If you already know GitHub, you know what to do! 😉 If not, all you need to do is edit this file, add your server in using the same format as the rest (JSON) of the servers, and then click to make a Pull Request from your newly created fork! 😁 Leave out `users` and `channels`: the daily crawl reads them from your server and fills them in. + ## Does The Change Happen Immediately? No, you will have to wait for one of our team to review your Pull Request. This should take a few hours, but may take a few days. diff --git a/scripts/probe.py b/scripts/probe.py index 404ad56..ef0b922 100644 --- a/scripts/probe.py +++ b/scripts/probe.py @@ -223,8 +223,8 @@ class Negotiator: continuation marker until the last. In caps-only mode the handshake ends there. When `register` is set we then complete registration (`CAP END`, the caller having already sent NICK/USER) and keep reading the welcome - burst to collect the RPL_ISUPPORT (005) tokens that carry the icon URL and - the LUSERS counts, finishing at end-of-MOTD (376/422) or a registration + burst to collect the RPL_ISUPPORT (005) tokens that carry the icon URL, and + the LUSERS replies, finishing at end-of-MOTD (376/422) or a registration error. """ @@ -285,7 +285,7 @@ def users(self) -> int | None: @property def channels(self) -> int | None: - # RFC 2812 sends RPL_LUSERCHANNELS only for a non-zero count. + # RFC 2812 servers omit RPL_LUSERCHANNELS when no channels exist, so we read its absence as zero. if self._channels is None and self._welcomed and self.users is not None: return 0 return self._channels @@ -293,7 +293,7 @@ def channels(self) -> int | None: def _feed_numeric(self, code: str, params: list[str], trailing: str) -> list[str]: if code == "251": self._luser_client = luser_client_count(trailing) - elif code == "254" and params and params[0].isdigit(): + elif code == "254" and params and _COUNT.fullmatch(params[0]): self._channels = int(params[0]) elif code == "266": self._global_users = global_users_count(params, trailing) @@ -313,8 +313,9 @@ def _feed_numeric(self, code: str, params: list[str], trailing: str) -> list[str return [] -_LUSER_CLIENT = re.compile(r"(\d+)\D+?(\d+) invisible") -_FIRST_NUMBER = re.compile(r"\d+") +# Servers are untrusted, so we accept only short ASCII digit runs that int() always parses. +_COUNT = re.compile(r"\d{1,9}", re.ASCII) +_LUSER_CLIENT = re.compile(r"(\d{1,9})\D+?(\d{1,9}) invisible", re.ASCII) def luser_client_count(trailing: str) -> int | None: @@ -328,9 +329,9 @@ def global_users_count(params: list[str], trailing: str) -> int | None: """RPL_GLOBALUSERS carries the current count as its first parameter on most daemons, and only inside the text ("Current global users 42, max 99") on the rest.""" - if params and params[0].isdigit(): + if params and _COUNT.fullmatch(params[0]): return int(params[0]) - match = _FIRST_NUMBER.search(trailing) + match = _COUNT.search(trailing) return int(match[0]) if match else None @@ -466,7 +467,8 @@ async def _attempt_wss(url: str, timeout: float, register: bool) -> TransportPro if not nego.caps_done and error is None: error = "connection closed before capability list" return TransportProbe( - "wss", url, nego.caps_done, error, nego.caps, nego.isupport, nego.finished, nego.users, nego.channels + "wss", url, nego.caps_done, error, nego.caps, nego.isupport, + complete=nego.finished, users=nego.users, channels=nego.channels, ) @@ -516,7 +518,8 @@ async def _attempt_ircs(url: str, timeout: float, register: bool) -> TransportPr if not nego.caps_done and error is None: error = "connection closed before capability list" return TransportProbe( - "ircs", url, nego.caps_done, error, nego.caps, nego.isupport, nego.finished, nego.users, nego.channels + "ircs", url, nego.caps_done, error, nego.caps, nego.isupport, + complete=nego.finished, users=nego.users, channels=nego.channels, ) @@ -734,7 +737,8 @@ def apply_corrections( Only ever adds or updates: an icon is set when a fresh one was downloaded this run, and a server whose icon could not be re-fetched keeps the one it - already had. User and channel counts follow the same rule. Nothing is removed unless `prune` is set -- the default crawl + already had. User and channel counts follow the same rule. Nothing is + removed unless `prune` is set -- the default crawl never drops a server or an icon, so a network that blocks the runner's IP (and so looks unreachable from CI) can't propose deleting a live entry. With `prune`, servers unreachable from where this ran are dropped (use only @@ -772,7 +776,7 @@ def apply_corrections( def _count(value: int | None) -> str: - return "—" if value is None else str(value) + return _MARK[None] if value is None else str(value) def render_report( @@ -791,9 +795,11 @@ def render_report( lines += ["No connectivity-affecting changes to validate. ✅", ""] return "\n".join(lines) + "\n" + # Only the crawl registers, so only the crawl has counts to show. + count_columns = " users | channels |" if icons_checked else "" lines += [ - "| Server | wss | ircs | obsidian | sasl | voice | icon | users | channels |", - "|---|---|---|---|---|---|---|---|---|", + f"| Server | wss | ircs | obsidian | sasl | voice | icon |{count_columns}", + "|---|---|---|---|---|---|---|" + ("---|---|" if icons_checked else ""), ] for r in results: by_transport = {e.transport: e for e in r.endpoints} @@ -808,10 +814,10 @@ def flag(name: str) -> str: return _MARK[getattr(r.detected, name)] icon_cell = _MARK[bool(r.icon_url)] if icons_checked else "—" + count_cells = f" {_count(r.users)} | {_count(r.channels)} |" if icons_checked else "" lines.append( f"| {r.name} | {cell('wss')} | {cell('ircs')} | " - f"{flag('obsidian')} | {flag('sasl')} | {flag('voice')} | {icon_cell} | " - f"{_count(r.users)} | {_count(r.channels)} |" + f"{flag('obsidian')} | {flag('sasl')} | {flag('voice')} | {icon_cell} |{count_cells}" ) removed_set = set(removed) @@ -836,6 +842,10 @@ def flag(name: str) -> str: if icon_changes: lines += ["", "### Icon updates", ""] lines += [f"- {c}" for c in icon_changes] + stale_counts = [r.name for r in results if r.users is None] if icons_checked else [] + if stale_counts: + lines += ["", "### User counts not refreshed (previous numbers kept)", ""] + lines += [f"- {name}" for name in stale_counts] if harvest_failures: lines += ["", "### Icon harvest failed — could not read ISUPPORT", ""] lines += [f"- **{name}** — {'; '.join(errs)}" for name, errs in harvest_failures]