From bc1c26aef41bcc09d1e36afd84f65bf5f3f6aac8 Mon Sep 17 00:00:00 2001 From: Max Leiter <8675906+MaxLeiter@users.noreply.github.com> Date: Thu, 9 Apr 2026 23:36:52 -0700 Subject: [PATCH] ircv3: ACCOUNTEXTBAN support --- src/commands/handlers/registration.js | 10 +++ src/networkinfo.js | 9 +++ test/networkinfo.test.js | 88 +++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/src/commands/handlers/registration.js b/src/commands/handlers/registration.js index 7d9daf97..6b3fbdeb 100644 --- a/src/commands/handlers/registration.js +++ b/src/commands/handlers/registration.js @@ -87,6 +87,16 @@ const handlers = { } else if (option[0] === 'CLIENTTAGDENY') { // https://ircv3.net/specs/extensions/message-tags#rpl_isupport-tokens handler.network.options.CLIENTTAGDENY = option[1].split(',').filter((f) => !!f); + } else if (option[0] === 'EXTBAN') { + // https://ircv3.net/specs/extensions/account-extban + const parts = option[1].split(','); + handler.network.options.EXTBAN = { + prefix: parts[0] || '', + types: parts.slice(1).join('').split(''), + }; + } else if (option[0] === 'ACCOUNTEXTBAN') { + // https://ircv3.net/specs/extensions/account-extban + handler.network.options.ACCOUNTEXTBAN = option[1].split(',').filter((f) => !!f); } else if (option[0] === 'NETWORK') { handler.network.name = option[1]; } else if (option[0] === 'NAMESX' && !handler.network.cap.isEnabled('multi-prefix')) { diff --git a/src/networkinfo.js b/src/networkinfo.js index e759e144..8e94f4f8 100644 --- a/src/networkinfo.js +++ b/src/networkinfo.js @@ -97,6 +97,15 @@ function NetworkInfo() { return this.options.CLIENTTAGDENY.some((tag) => tag === `-${tag_name}`); }; + this.accountBanMask = function accountBanMask(account) { + if (!this.options.EXTBAN || !this.options.ACCOUNTEXTBAN || !this.options.ACCOUNTEXTBAN.length) { + return null; + } + + const type = this.options.ACCOUNTEXTBAN[0]; + return this.options.EXTBAN.prefix + type + ':' + account; + }; + this.isChannelName = function isChannelName(channel_name) { if (typeof channel_name !== 'string' || channel_name === '') { return false; diff --git a/test/networkinfo.test.js b/test/networkinfo.test.js index 44a4254b..14307bd4 100644 --- a/test/networkinfo.test.js +++ b/test/networkinfo.test.js @@ -156,4 +156,92 @@ describe('src/networkinfo.js', function() { assert.isFalse(client.network.supportsTag('b')); }); }); + + describe('EXTBAN and ACCOUNTEXTBAN support', function() { + it('should parse EXTBAN into prefix and types', function() { + const client = newMockClient(); + client.dispatch({ + command: '005', + params: ['nick', 'EXTBAN=$,ARar'], + tags: [] + }); + assert.deepEqual(client.network.options.EXTBAN, { + prefix: '$', + types: ['A', 'R', 'a', 'r'], + }); + }); + + it('should parse EXTBAN with tilde prefix', function() { + const client = newMockClient(); + client.dispatch({ + command: '005', + params: ['nick', 'EXTBAN=~,a'], + tags: [] + }); + assert.deepEqual(client.network.options.EXTBAN, { + prefix: '~', + types: ['a'], + }); + }); + + it('should parse ACCOUNTEXTBAN as a list', function() { + const client = newMockClient(); + client.dispatch({ + command: '005', + params: ['nick', 'ACCOUNTEXTBAN=a,account'], + tags: [] + }); + assert.deepEqual(client.network.options.ACCOUNTEXTBAN, ['a', 'account']); + }); + + it('should parse single ACCOUNTEXTBAN value', function() { + const client = newMockClient(); + client.dispatch({ + command: '005', + params: ['nick', 'ACCOUNTEXTBAN=R'], + tags: [] + }); + assert.deepEqual(client.network.options.ACCOUNTEXTBAN, ['R']); + }); + + it('should construct account ban mask with $ prefix', function() { + const client = newMockClient(); + client.dispatch({ + command: '005', + params: ['nick', 'EXTBAN=$,ARar', 'ACCOUNTEXTBAN=R'], + tags: [] + }); + assert.equal(client.network.accountBanMask('bob'), '$R:bob'); + }); + + it('should construct account ban mask with ~ prefix', function() { + const client = newMockClient(); + client.dispatch({ + command: '005', + params: ['nick', 'EXTBAN=~,a', 'ACCOUNTEXTBAN=a,account'], + tags: [] + }); + assert.equal(client.network.accountBanMask('bob'), '~a:bob'); + }); + + it('should return null when EXTBAN is not available', function() { + const client = newMockClient(); + client.dispatch({ + command: '005', + params: ['nick', 'ACCOUNTEXTBAN=R'], + tags: [] + }); + assert.isNull(client.network.accountBanMask('bob')); + }); + + it('should return null when ACCOUNTEXTBAN is not available', function() { + const client = newMockClient(); + client.dispatch({ + command: '005', + params: ['nick', 'EXTBAN=$,ARar'], + tags: [] + }); + assert.isNull(client.network.accountBanMask('bob')); + }); + }); });