add a nicklist manager class
This commit is contained in:
parent
ad0ed84f7e
commit
280c8a5e2f
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
/node_modules/
|
/node_modules/
|
||||||
/lib/
|
/lib/
|
||||||
|
/docs
|
||||||
|
173
package-lock.json
generated
173
package-lock.json
generated
@ -11,6 +11,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.7.18",
|
"@types/node": "^18.7.18",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
|
"typedoc": "^0.23.15",
|
||||||
"typescript": "^4.8.3"
|
"typescript": "^4.8.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -20,6 +21,57 @@
|
|||||||
"integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==",
|
"integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/balanced-match": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/brace-expansion": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jsonc-parser": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/lunr": {
|
||||||
|
"version": "2.3.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||||
|
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/marked": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"marked": "bin/marked.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/minimatch": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "2.7.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||||
@ -35,6 +87,38 @@
|
|||||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/shiki": {
|
||||||
|
"version": "0.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.11.1.tgz",
|
||||||
|
"integrity": "sha512-EugY9VASFuDqOexOgXR18ZV+TbFrQHeCpEYaXamO+SZlsnT/2LxuLBX25GGtIrwaEVFXUAbUQ601SWE2rMwWHA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"jsonc-parser": "^3.0.0",
|
||||||
|
"vscode-oniguruma": "^1.6.1",
|
||||||
|
"vscode-textmate": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typedoc": {
|
||||||
|
"version": "0.23.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.15.tgz",
|
||||||
|
"integrity": "sha512-x9Zu+tTnwxb9YdVr+zvX7LYzyBl1nieOr6lrSHbHsA22/RJK2m4Y525WIg5Mj4jWCmfL47v6f4hUzY7EIuwS5w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"lunr": "^2.3.9",
|
||||||
|
"marked": "^4.0.19",
|
||||||
|
"minimatch": "^5.1.0",
|
||||||
|
"shiki": "^0.11.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"typedoc": "bin/typedoc"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "4.6.x || 4.7.x || 4.8.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
|
||||||
@ -47,6 +131,18 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.2.0"
|
"node": ">=4.2.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vscode-oniguruma": {
|
||||||
|
"version": "1.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz",
|
||||||
|
"integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/vscode-textmate": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==",
|
||||||
|
"dev": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -56,17 +152,94 @@
|
|||||||
"integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==",
|
"integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"balanced-match": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"brace-expansion": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"balanced-match": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jsonc-parser": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"lunr": {
|
||||||
|
"version": "2.3.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
|
||||||
|
"integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"marked": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"minimatch": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"brace-expansion": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"version": "2.7.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"shiki": {
|
||||||
|
"version": "0.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.11.1.tgz",
|
||||||
|
"integrity": "sha512-EugY9VASFuDqOexOgXR18ZV+TbFrQHeCpEYaXamO+SZlsnT/2LxuLBX25GGtIrwaEVFXUAbUQ601SWE2rMwWHA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"jsonc-parser": "^3.0.0",
|
||||||
|
"vscode-oniguruma": "^1.6.1",
|
||||||
|
"vscode-textmate": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typedoc": {
|
||||||
|
"version": "0.23.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.23.15.tgz",
|
||||||
|
"integrity": "sha512-x9Zu+tTnwxb9YdVr+zvX7LYzyBl1nieOr6lrSHbHsA22/RJK2m4Y525WIg5Mj4jWCmfL47v6f4hUzY7EIuwS5w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"lunr": "^2.3.9",
|
||||||
|
"marked": "^4.0.19",
|
||||||
|
"minimatch": "^5.1.0",
|
||||||
|
"shiki": "^0.11.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.8.3",
|
"version": "4.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
|
||||||
"integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
|
"integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
},
|
||||||
|
"vscode-oniguruma": {
|
||||||
|
"version": "1.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz",
|
||||||
|
"integrity": "sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"vscode-textmate": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==",
|
||||||
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.7.18",
|
"@types/node": "^18.7.18",
|
||||||
"prettier": "^2.7.1",
|
"prettier": "^2.7.1",
|
||||||
|
"typedoc": "^0.23.15",
|
||||||
"typescript": "^4.8.3"
|
"typescript": "^4.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
applyTextColor,
|
applyTextColor,
|
||||||
wrapFormattedText,
|
wrapFormattedText,
|
||||||
} from '../utility/message-formatting';
|
} from '../utility/message-formatting';
|
||||||
|
import { IRCNickList } from '../utility/nicklist';
|
||||||
import { NickServValidator } from '../utility/nickserv-validator';
|
import { NickServValidator } from '../utility/nickserv-validator';
|
||||||
|
|
||||||
const bot = new IRCBot({
|
const bot = new IRCBot({
|
||||||
@ -18,6 +19,7 @@ const bot = new IRCBot({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const nickserv = new NickServValidator(bot);
|
const nickserv = new NickServValidator(bot);
|
||||||
|
const nicklist = new IRCNickList(bot);
|
||||||
|
|
||||||
bot.on('authenticated', () => {
|
bot.on('authenticated', () => {
|
||||||
console.log('Successful connection!');
|
console.log('Successful connection!');
|
||||||
@ -91,4 +93,6 @@ bot.on('disconnect', console.log);
|
|||||||
|
|
||||||
bot.on('names', console.log);
|
bot.on('names', console.log);
|
||||||
|
|
||||||
|
nicklist.on('update', () => console.log(JSON.stringify(nicklist.channels)));
|
||||||
|
|
||||||
bot.connect();
|
bot.connect();
|
||||||
|
@ -2,6 +2,7 @@ export * from './types/events';
|
|||||||
export * from './types/irc.interfaces';
|
export * from './types/irc.interfaces';
|
||||||
export * from './types/impl.interface';
|
export * from './types/impl.interface';
|
||||||
|
|
||||||
|
export * from './utility/nicklist';
|
||||||
export * from './utility/collector';
|
export * from './utility/collector';
|
||||||
export * from './utility/estimate-prefix';
|
export * from './utility/estimate-prefix';
|
||||||
export * from './utility/formatstr';
|
export * from './utility/formatstr';
|
||||||
|
@ -560,7 +560,7 @@ export class IRCConnectionWrapper
|
|||||||
public async whois(nick: string): Promise<WhoisResponse> {
|
public async whois(nick: string): Promise<WhoisResponse> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.useCollector(
|
this.useCollector(
|
||||||
new WhoisCollector((data) => resolve(parseWhois(data))),
|
new WhoisCollector((data) => resolve(parseWhois(data)), nick),
|
||||||
'WHOIS %s',
|
'WHOIS %s',
|
||||||
nick,
|
nick,
|
||||||
);
|
);
|
||||||
@ -575,7 +575,7 @@ export class IRCConnectionWrapper
|
|||||||
public async who(target: string): Promise<WhoResponse[]> {
|
public async who(target: string): Promise<WhoResponse[]> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.useCollector(
|
this.useCollector(
|
||||||
new WhoCollector((lines) => resolve(parseWho(lines))),
|
new WhoCollector((lines) => resolve(parseWho(lines)), target),
|
||||||
'WHO %s',
|
'WHO %s',
|
||||||
target,
|
target,
|
||||||
);
|
);
|
||||||
|
@ -66,10 +66,7 @@ export class MultiLineCollector implements IQueue<IIRCLine[]> {
|
|||||||
* `318` - End of WHOIS
|
* `318` - End of WHOIS
|
||||||
*/
|
*/
|
||||||
export class WhoisCollector extends MultiLineCollector {
|
export class WhoisCollector extends MultiLineCollector {
|
||||||
constructor(
|
constructor(resolve: (lines: IIRCLine[]) => void, target: string) {
|
||||||
resolve: (lines: IIRCLine[]) => void,
|
|
||||||
match?: (line: IIRCLine) => boolean,
|
|
||||||
) {
|
|
||||||
super(
|
super(
|
||||||
'318', // End of WHOIS
|
'318', // End of WHOIS
|
||||||
[
|
[
|
||||||
@ -86,7 +83,7 @@ export class WhoisCollector extends MultiLineCollector {
|
|||||||
'317', // Sign on time and idle time
|
'317', // Sign on time and idle time
|
||||||
],
|
],
|
||||||
resolve,
|
resolve,
|
||||||
match,
|
(line) => target === line.arguments[1],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,17 +96,14 @@ export class WhoisCollector extends MultiLineCollector {
|
|||||||
* `315` - end of WHO
|
* `315` - end of WHO
|
||||||
*/
|
*/
|
||||||
export class WhoCollector extends MultiLineCollector {
|
export class WhoCollector extends MultiLineCollector {
|
||||||
constructor(
|
constructor(resolve: (lines: IIRCLine[]) => void, target: string) {
|
||||||
resolve: (lines: IIRCLine[]) => void,
|
|
||||||
match?: (line: IIRCLine) => boolean,
|
|
||||||
) {
|
|
||||||
super(
|
super(
|
||||||
'315', // End of WHO
|
'315', // End of WHO
|
||||||
[
|
[
|
||||||
'352', // WHO line: <channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real_name>
|
'352', // WHO line: <channel> <user> <host> <server> <nick> <H|G>[*][@|+] :<hopcount> <real_name>
|
||||||
],
|
],
|
||||||
resolve,
|
resolve,
|
||||||
match,
|
(line) => line.arguments[1] === target || line.arguments[2] === target,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
src/utility/nicklist/index.ts
Normal file
3
src/utility/nicklist/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './nicklist.interfaces';
|
||||||
|
export * from './nicklist.events';
|
||||||
|
export * from './nicklist';
|
4
src/utility/nicklist/nicklist.events.ts
Normal file
4
src/utility/nicklist/nicklist.events.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export type NickListEvents = {
|
||||||
|
update: (channels?: string[]) => void;
|
||||||
|
channelRemoved: (channel: string) => void;
|
||||||
|
};
|
11
src/utility/nicklist/nicklist.interfaces.ts
Normal file
11
src/utility/nicklist/nicklist.interfaces.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export interface INicklistNick {
|
||||||
|
nickname: string;
|
||||||
|
modes: string[];
|
||||||
|
prefix?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INicklistChannel {
|
||||||
|
channel: string;
|
||||||
|
topic?: string;
|
||||||
|
nicks: INicklistNick[];
|
||||||
|
}
|
378
src/utility/nicklist/nicklist.ts
Normal file
378
src/utility/nicklist/nicklist.ts
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
import { IRCConnectionWrapper } from '../../irc';
|
||||||
|
import { modeFromPrefix } from '../mode-from-prefix';
|
||||||
|
import { TypedEventEmitter } from '../typed-event-emitter';
|
||||||
|
import { WhoResponse } from '../who-parser';
|
||||||
|
import { NickListEvents } from './nicklist.events';
|
||||||
|
import { INicklistChannel } from './nicklist.interfaces';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages a nicklist for your IRC connection, complete with modes and prefixes.
|
||||||
|
*/
|
||||||
|
export class IRCNickList extends TypedEventEmitter<NickListEvents> {
|
||||||
|
public channels: INicklistChannel[] = [];
|
||||||
|
|
||||||
|
constructor(public irc: IRCConnectionWrapper) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if (this.irc.authenticated) {
|
||||||
|
this.handlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.irc.on('authenticated', () => this.handlers());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get my nickname
|
||||||
|
*/
|
||||||
|
public get nickname() {
|
||||||
|
return this.irc.options.nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available user prefixes
|
||||||
|
*/
|
||||||
|
public get prefixes() {
|
||||||
|
return Object.values(this.irc.serverData.supportedModes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Available user modes with prefixes
|
||||||
|
*/
|
||||||
|
public get modes() {
|
||||||
|
return Object.keys(this.irc.serverData.supportedModes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of supported prefix modes
|
||||||
|
*/
|
||||||
|
public get supportedModes() {
|
||||||
|
return this.irc.serverData.supportedModes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does the provided nickname currently have a supported prefix.
|
||||||
|
* @param nick Nickname with a prefix
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
public nickHasPrefix(nick: string): boolean {
|
||||||
|
return this.prefixes.some((prefix) => nick.startsWith(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip prefix from nickname, if exists
|
||||||
|
* @param nick Nickname with a prefix
|
||||||
|
* @returns Nickname without prefix
|
||||||
|
*/
|
||||||
|
public stripPrefix(nick: string) {
|
||||||
|
if (this.nickHasPrefix(nick)) {
|
||||||
|
return nick.substring(1);
|
||||||
|
}
|
||||||
|
return nick;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a channel by name
|
||||||
|
* @param channel Channel name
|
||||||
|
* @returns Channel object or undefined
|
||||||
|
*/
|
||||||
|
public getChannelByName(channel: string) {
|
||||||
|
return this.channels.find((item) => item.channel === channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a user join event.
|
||||||
|
* @param channel Channel
|
||||||
|
* @param nickname Nickname
|
||||||
|
*/
|
||||||
|
public handleJoin(channel: string, nickname: string): void {
|
||||||
|
const channelObject = this.getChannelByName(channel);
|
||||||
|
if (nickname === this.nickname) {
|
||||||
|
if (!channelObject) {
|
||||||
|
this.channels.push({
|
||||||
|
channel,
|
||||||
|
nicks: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.irc.who(channel).then((response) => this.handleWho(response));
|
||||||
|
this.emit('update', [channel]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
channelObject?.nicks.push({
|
||||||
|
nickname,
|
||||||
|
modes: [],
|
||||||
|
});
|
||||||
|
this.emit('update', [channel]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle `NAMES` reply.
|
||||||
|
* @param channel Channel
|
||||||
|
* @param names Nick list
|
||||||
|
*/
|
||||||
|
public handleNames(channel: string, names: string[]): void {
|
||||||
|
const channelObject = this.getChannelByName(channel);
|
||||||
|
if (!channelObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const nick of names) {
|
||||||
|
const prefixed = this.nickHasPrefix(nick);
|
||||||
|
const stripped = this.stripPrefix(nick);
|
||||||
|
const umode = modeFromPrefix(nick, this.supportedModes);
|
||||||
|
const existsNick = channelObject.nicks.find(
|
||||||
|
(entry) => entry.nickname === stripped,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!existsNick) {
|
||||||
|
channelObject.nicks.push({
|
||||||
|
modes: [umode].filter((x) => x),
|
||||||
|
nickname: stripped,
|
||||||
|
prefix: prefixed ? nick.substring(0, 1) : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emit('update', [channel]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a WHO response list, sets hidden modes for nicks
|
||||||
|
* @param whoList WHO reply
|
||||||
|
*/
|
||||||
|
public handleWho(whoList: WhoResponse[]): void {
|
||||||
|
let channels = [];
|
||||||
|
for (const who of whoList) {
|
||||||
|
if (!who.channel || !who.nickname) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelObject = this.getChannelByName(who.channel);
|
||||||
|
if (!channelObject) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
channels.push(who.channel);
|
||||||
|
|
||||||
|
const providedModeList = who.modes || [];
|
||||||
|
const userModes = this.prefixes.reduce<string[]>((list, current) => {
|
||||||
|
const mode = providedModeList.includes(current)
|
||||||
|
? modeFromPrefix(current, this.supportedModes)
|
||||||
|
: undefined;
|
||||||
|
return mode ? [...list, mode] : list;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const inListNick = channelObject.nicks.find(
|
||||||
|
(item) => item.nickname === who.nickname,
|
||||||
|
);
|
||||||
|
if (inListNick) {
|
||||||
|
inListNick.modes = userModes;
|
||||||
|
inListNick.prefix = this.getPrefixFromModes(inListNick.modes);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
channelObject.nicks.push({
|
||||||
|
nickname: who.nickname,
|
||||||
|
modes: userModes,
|
||||||
|
prefix: this.getPrefixFromModes(userModes),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.emit('update', channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles `QUIT`, `PART` and `KICK` events (user left channel).
|
||||||
|
* @param nickname Nickname
|
||||||
|
* @param channel Optional channel, if missing, removes from all
|
||||||
|
*/
|
||||||
|
public handleLeave(nickname: string, channel?: string): void {
|
||||||
|
if (nickname === this.nickname) {
|
||||||
|
if (channel) {
|
||||||
|
this.channels.splice(
|
||||||
|
this.channels.findIndex((chan) => chan.channel === channel),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.emit('channelRemoved', channel);
|
||||||
|
this.emit('update', [channel]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allMap = this.channels.map((chan) => chan.channel);
|
||||||
|
this.channels = [];
|
||||||
|
|
||||||
|
allMap.forEach((x) => this.emit('channelRemoved', x));
|
||||||
|
this.emit('update', allMap);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!channel) {
|
||||||
|
let channels = [];
|
||||||
|
for (const chan of this.channels) {
|
||||||
|
if (this.removeNickIfExists(chan, nickname)) {
|
||||||
|
channels.push(chan.channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emit('update', channels);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelObject = this.getChannelByName(channel);
|
||||||
|
if (!channelObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.removeNickIfExists(channelObject, nickname);
|
||||||
|
this.emit('update', [channel]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle channel modes.
|
||||||
|
* @param channel Channel
|
||||||
|
* @param mode Mode
|
||||||
|
* @param modeTarget User
|
||||||
|
* @param method Add or remove (`+` or `-`)
|
||||||
|
*/
|
||||||
|
public handleMode(
|
||||||
|
channel: string,
|
||||||
|
mode: string,
|
||||||
|
modeTarget: string,
|
||||||
|
method: string,
|
||||||
|
): void {
|
||||||
|
if (!this.modes.includes(mode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const chan = this.getChannelByName(channel);
|
||||||
|
if (!chan) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nick = this.getNickFromChannel(chan, modeTarget);
|
||||||
|
if (!nick) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === '+') {
|
||||||
|
if (nick.modes.includes(mode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nick.modes.push(mode);
|
||||||
|
nick.prefix = this.getPrefixFromModes(nick.modes);
|
||||||
|
} else {
|
||||||
|
if (!nick.modes.includes(mode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nick.modes.splice(nick.modes.indexOf(mode), 1);
|
||||||
|
nick.prefix = this.getPrefixFromModes(nick.modes);
|
||||||
|
}
|
||||||
|
this.emit('update', [channel]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle nickname changes.
|
||||||
|
* @param oldNick Old nickname
|
||||||
|
* @param newNick New nickname
|
||||||
|
*/
|
||||||
|
public handleNick(oldNick: string, newNick: string) {
|
||||||
|
let channels = [];
|
||||||
|
|
||||||
|
for (const channel of this.channels) {
|
||||||
|
const nick = this.getNickFromChannel(channel, oldNick);
|
||||||
|
|
||||||
|
if (!nick) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
channels.push(channel.channel);
|
||||||
|
nick.nickname = newNick;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit('update', channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get nick object from channel, if exists.
|
||||||
|
* @param channel Channel object
|
||||||
|
* @param nickname Nickname
|
||||||
|
* @returns Nick object
|
||||||
|
*/
|
||||||
|
public getNickFromChannel(channel: INicklistChannel, nickname: string) {
|
||||||
|
return channel.nicks.find((item) => item.nickname === nickname);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a nickname from channel, if exists.
|
||||||
|
* @param channel Channel object
|
||||||
|
* @param nickname Nickname
|
||||||
|
*/
|
||||||
|
public removeNickIfExists(
|
||||||
|
channel: INicklistChannel,
|
||||||
|
nickname: string,
|
||||||
|
): boolean {
|
||||||
|
const inListNick = channel.nicks.findIndex(
|
||||||
|
(item) => item.nickname === nickname,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (inListNick > -1) {
|
||||||
|
channel.nicks.splice(inListNick, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the applicable prefix for a list of user channel modes.
|
||||||
|
* @param modes List of modes
|
||||||
|
* @returns Prefix or empty string
|
||||||
|
*/
|
||||||
|
public getPrefixFromModes(modes: string[]): string {
|
||||||
|
if (!modes?.length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const highestModeWeight = this.modes.reduce<string>(
|
||||||
|
(last, current, _, array) =>
|
||||||
|
modes.includes(current)
|
||||||
|
? last
|
||||||
|
? array.indexOf(current) < array.indexOf(last)
|
||||||
|
? current
|
||||||
|
: last
|
||||||
|
: current
|
||||||
|
: last,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!highestModeWeight) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.prefixes[this.modes.indexOf(highestModeWeight)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private handlers() {
|
||||||
|
this.irc.on('join', ({ nickname, channel }) => {
|
||||||
|
this.handleJoin(channel, nickname);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.irc.on(
|
||||||
|
'channel-mode',
|
||||||
|
({ type, mode, modeTarget, arguments: [channel] }) => {
|
||||||
|
this.handleMode(channel, mode, modeTarget, type);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.irc.on('leave', ({ nickname, channel }) => {
|
||||||
|
this.handleLeave(nickname, channel);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.irc.on('nick', ({ oldNick, newNick }) => {
|
||||||
|
this.handleNick(oldNick, newNick);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.irc.on('names', ({ channel, list }) => {
|
||||||
|
this.handleNames(channel, list);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user