Initial commit
This commit is contained in:
commit
2a4b104129
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/node_modules/
|
||||
/.out/
|
||||
deployment.json
|
||||
*.js
|
||||
*.d.ts
|
||||
*.tsbuildinfo
|
150
irc/format.ts
Normal file
150
irc/format.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { thousandsSeparator, toHHMMSS, timeSince } from '@squeebot/core/lib/common';
|
||||
import { approximateB16Color, Formatter } from '@squeebot/core/lib/types';
|
||||
|
||||
export class IRCFormatter extends Formatter {
|
||||
public formatting = {
|
||||
bold: {start: '\u0002', end: '\u000F'},
|
||||
italic: {start: '\u0016', end: '\u000F'},
|
||||
emphasis: {start: '\u0016', end: '\u000F'},
|
||||
underline: {start: '\u001F', end: '\u000F'},
|
||||
};
|
||||
|
||||
public colors: {[key: string]: string} = {
|
||||
black: '\u00031',
|
||||
darkblue: '\u00032',
|
||||
green: '\u00033',
|
||||
red: '\u00034',
|
||||
brown: '\u00035',
|
||||
purple: '\u00036',
|
||||
gold: '\u00037',
|
||||
yellow: '\u00038',
|
||||
limegreen: '\u00039',
|
||||
cyan: '\u000310',
|
||||
lightblue: '\u000311',
|
||||
blue: '\u000312',
|
||||
pink: '\u000313',
|
||||
darkgray: '\u000314',
|
||||
gray: '\u000315',
|
||||
grey: '\u000315',
|
||||
white: '\u00030',
|
||||
};
|
||||
|
||||
public color(color: string, msg: string): string {
|
||||
// Approximate a hex color to one of IRC colors
|
||||
if (color.indexOf('#') === 0) {
|
||||
color = approximateB16Color(color);
|
||||
}
|
||||
|
||||
if (!this.supportColors) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
if (!this.colors[color]) {
|
||||
return msg;
|
||||
}
|
||||
|
||||
return this.colors[color] + msg + this.colorEscape;
|
||||
}
|
||||
|
||||
public strip(msg: string): string {
|
||||
return msg.replace(/(\x03\d{0,2}(,\d{0,2})?)/g, '').replace(/[\x0F\x02\x16\x1F]/g, '');
|
||||
}
|
||||
|
||||
compose(objs: any): any {
|
||||
const str = [];
|
||||
|
||||
for (const i in objs) {
|
||||
const elem = objs[i];
|
||||
|
||||
const elemType = elem[0];
|
||||
let elemValue = elem[1];
|
||||
const elemParams = elem[2];
|
||||
|
||||
if (!elemValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let valueColor = null;
|
||||
|
||||
// Special types
|
||||
if (elemParams && elemParams.type) {
|
||||
switch (elemParams.type) {
|
||||
case 'time':
|
||||
elemValue = new Date(elemValue).toString();
|
||||
break;
|
||||
case 'metric':
|
||||
elemValue = thousandsSeparator(elemValue);
|
||||
break;
|
||||
case 'timesince':
|
||||
elemValue = timeSince(elemValue);
|
||||
break;
|
||||
case 'duration':
|
||||
elemValue = toHHMMSS(elemValue);
|
||||
break;
|
||||
case 'description':
|
||||
valueColor = 'blue';
|
||||
elemValue = `"${elemValue}"`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Currently, disregard image fields.
|
||||
if (elemType === 'image') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Bold value
|
||||
if (elemType === 'bold' || elemType === 'b' || elemType === 'strong') {
|
||||
elemValue = this.format('bold', elemValue);
|
||||
}
|
||||
|
||||
// Italic value
|
||||
if (elemType === 'italic' || elemType === 'i' || elemType === 'em' || elemType === 'emphasis') {
|
||||
elemValue = this.format('italic', elemValue);
|
||||
}
|
||||
|
||||
// Underlined value
|
||||
if (elemType === 'underline' || elemType === 'ul') {
|
||||
elemValue = this.format('underline', elemValue);
|
||||
}
|
||||
|
||||
// Colorize the value
|
||||
if (elemParams && elemParams.color) {
|
||||
valueColor = elemParams.color;
|
||||
}
|
||||
|
||||
// Add the label, if present
|
||||
if (elemParams && elemParams.label) {
|
||||
// Set label color to default
|
||||
let labelColor = 'green';
|
||||
if (elemParams.color) {
|
||||
labelColor = elemParams.color;
|
||||
}
|
||||
|
||||
if (!valueColor) {
|
||||
valueColor = 'blue';
|
||||
}
|
||||
|
||||
// Handle array label
|
||||
// Prefer the icon over the text version.
|
||||
let label = elemParams.label;
|
||||
if (typeof label === 'object') {
|
||||
label = label[0];
|
||||
} else {
|
||||
label = label + ':';
|
||||
}
|
||||
|
||||
if (valueColor && valueColor === labelColor) {
|
||||
str.push(this.color(valueColor, label + ' ' + elemValue));
|
||||
} else {
|
||||
str.push(this.color(labelColor, label) + ' ' + (valueColor ? this.color(valueColor, elemValue) : elemValue));
|
||||
}
|
||||
} else {
|
||||
str.push(valueColor ? this.color(valueColor, elemValue) : elemValue);
|
||||
}
|
||||
}
|
||||
|
||||
// May return an object, but your protocol must support it.
|
||||
return str.join(' ');
|
||||
}
|
||||
}
|
425
irc/irc.ts
Normal file
425
irc/irc.ts
Normal file
@ -0,0 +1,425 @@
|
||||
import util from 'util';
|
||||
import tls, { TLSSocket } from 'tls';
|
||||
import net, { Socket } from 'net';
|
||||
import { IIRCLine, parse } from './parser';
|
||||
import { EventEmitter } from 'events';
|
||||
const MAXMSGLEN = 512;
|
||||
|
||||
export interface IIRCOptions {
|
||||
nick: string;
|
||||
host: string;
|
||||
username?: string;
|
||||
hostname?: string;
|
||||
port?: number;
|
||||
password?: string | null;
|
||||
sasl?: boolean;
|
||||
ssl?: boolean;
|
||||
channels: string[];
|
||||
nickserv: {[key: string]: any};
|
||||
}
|
||||
|
||||
export interface IIRCMessage {
|
||||
message: string;
|
||||
to: string;
|
||||
nickname: string;
|
||||
raw: IIRCLine;
|
||||
}
|
||||
|
||||
declare type ConnectSocket = TLSSocket | Socket;
|
||||
|
||||
export class IRC extends EventEmitter {
|
||||
public alive = false;
|
||||
private authenticated = false;
|
||||
private serverData: {[key: string]: any} = {
|
||||
name: '',
|
||||
supportedModes: {},
|
||||
serverSupports: {},
|
||||
};
|
||||
|
||||
private queue: any[] = [];
|
||||
private channels: string[] = [];
|
||||
private nickservStore: {[key: string]: any} = {};
|
||||
|
||||
private socket: ConnectSocket | null = null;
|
||||
|
||||
constructor(public options: IIRCOptions) {
|
||||
super();
|
||||
if (!this.options.username) {
|
||||
this.options.username = this.options.nick;
|
||||
}
|
||||
}
|
||||
|
||||
// Chop message into pieces recursively, splitting them at lenoffset
|
||||
public static truncate(msg: string, lenoffset: number): string[] {
|
||||
let pieces: string[] = [];
|
||||
if (msg.length <= lenoffset) {
|
||||
pieces.push(msg);
|
||||
} else {
|
||||
const m1 = msg.substring(0, lenoffset);
|
||||
const m2 = msg.substring(lenoffset);
|
||||
pieces.push(m1);
|
||||
if (m2.length > lenoffset) {
|
||||
pieces = pieces.concat(IRC.truncate(m2, lenoffset));
|
||||
} else {
|
||||
pieces.push(m2);
|
||||
}
|
||||
}
|
||||
return pieces;
|
||||
}
|
||||
|
||||
private authenticate(): void {
|
||||
if (this.options.sasl) {
|
||||
this.write('CAP REQ :sasl');
|
||||
}
|
||||
|
||||
if (this.options.password && !this.options.sasl) {
|
||||
this.write('PASS %s', this.options.password);
|
||||
}
|
||||
|
||||
this.write('USER %s 8 * :Squeebot 3.0 Core', this.options.username);
|
||||
this.write('NICK %s', this.options.nick);
|
||||
|
||||
this.on('authenticated', () => {
|
||||
this.joinMissingChannels(this.options.channels);
|
||||
});
|
||||
|
||||
this.on('testnick', (data) => {
|
||||
if (this.nickservStore[data.nickname] != null) {
|
||||
if (this.nickservStore[data.nickname].result === true) {
|
||||
data.func(true);
|
||||
return;
|
||||
} else {
|
||||
if (this.nickservStore[data.nickname].checked < Date.now() - 1800000) { // 30 minutes
|
||||
delete this.nickservStore[data.nickname];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.nickserv && this.options.nickserv.enabled && this.options.nickserv.command) {
|
||||
this.queue.push({
|
||||
await: 'NOTICE',
|
||||
from: 'NickServ',
|
||||
do: (line: IIRCLine) => {
|
||||
const splitline = line.trailing!.split(' ');
|
||||
if (splitline![1] !== '0') {
|
||||
this.nickservStore[data.nickname] = {
|
||||
result: true,
|
||||
checked: Date.now(),
|
||||
};
|
||||
data.func(true);
|
||||
} else {
|
||||
this.nickservStore[data.nickname] = {
|
||||
result: false,
|
||||
checked: Date.now(),
|
||||
};
|
||||
data.func(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.write('PRIVMSG nickserv :%s %s', this.options.nickserv.command, data.nickname);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public disconnect(): void {
|
||||
if (!this.alive) {
|
||||
return;
|
||||
}
|
||||
this.write('QUIT :%s', 'Squeebot 3.0 Core - IRC Service');
|
||||
this.alive = false;
|
||||
}
|
||||
|
||||
public write(...args: any[]): void {
|
||||
const data = util.format.apply(null, [args[0], ...args.slice(1)]);
|
||||
if (!this.alive) {
|
||||
return;
|
||||
}
|
||||
this.socket!.write(data + '\r\n');
|
||||
}
|
||||
|
||||
private joinMissingChannels(arr: string[]): void {
|
||||
if (arr) {
|
||||
for (const i in arr) {
|
||||
let chan = arr[i];
|
||||
if (chan.indexOf('#') !== 0) {
|
||||
chan = '#' + chan;
|
||||
}
|
||||
|
||||
if (this.channels.indexOf(chan) === -1) {
|
||||
this.write('JOIN %s', chan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleServerLine(line: IIRCLine): void {
|
||||
if (this.queue.length) {
|
||||
let skipHandling = false;
|
||||
for (const i in this.queue) {
|
||||
const entry = this.queue[i];
|
||||
if (entry.await && line.command === entry.await) {
|
||||
if (entry.from && line.user.nickname.toLowerCase() === entry.from.toLowerCase()) {
|
||||
if (entry.do) {
|
||||
skipHandling = true;
|
||||
this.queue.splice(parseInt(i, 10), 1);
|
||||
entry.do(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (skipHandling) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (line.command.toLowerCase()) {
|
||||
case 'cap':
|
||||
if (line.trailing === 'sasl' && line.arguments![1] === 'ACK' && !this.authenticated) {
|
||||
this.write('AUTHENTICATE PLAIN');
|
||||
}
|
||||
break;
|
||||
case '+':
|
||||
case ':+':
|
||||
if (this.authenticated) {
|
||||
return;
|
||||
}
|
||||
|
||||
const authline = Buffer.from(this.options.nick + '\x00' + this.options.username + '\x00' + this.options.password)
|
||||
.toString('base64');
|
||||
this.write('AUTHENTICATE %s', authline);
|
||||
break;
|
||||
case '904':
|
||||
this.emit('error', {
|
||||
error: new Error(line.trailing),
|
||||
fatal: true
|
||||
});
|
||||
break;
|
||||
case '903':
|
||||
this.write('CAP END');
|
||||
break;
|
||||
case 'notice':
|
||||
case 'privmsg':
|
||||
if (!line.user.nickname || line.user.nickname === '') {
|
||||
return;
|
||||
}
|
||||
this.emit('message', {
|
||||
message: line.trailing,
|
||||
to: line.arguments![0],
|
||||
nickname: line.user.nickname,
|
||||
raw: line
|
||||
});
|
||||
break;
|
||||
case '001':
|
||||
this.serverData.name = line.user.hostname;
|
||||
this.authenticated = true;
|
||||
|
||||
// Set nick to what the server actually thinks is our nick
|
||||
this.options.nick = line.arguments![0];
|
||||
this.emit('authenticated', true);
|
||||
|
||||
// Send a whois request for self in order to reliably fetch hostname of self
|
||||
this.write('WHOIS %s', this.options.nick);
|
||||
break;
|
||||
case '005':
|
||||
const argv = line.arguments!.slice(1);
|
||||
for (const a in argv) {
|
||||
let t: any = argv[a];
|
||||
if (t.indexOf('=') !== -1) {
|
||||
t = t.split('=');
|
||||
if (t[0] === 'PREFIX') {
|
||||
const d = t[1].match(/\((\w+)\)(.*)/);
|
||||
const r = d![1].split('');
|
||||
const aa = d![2].split('');
|
||||
for (const b in r) {
|
||||
this.serverData.supportedModes[r[b]] = aa[b];
|
||||
}
|
||||
} else if (t[0] === 'NETWORK') {
|
||||
this.serverData.network = t[1];
|
||||
} else if (t[0] === 'CHANNELLEN') {
|
||||
this.serverData.maxChannelLength = parseInt(t[1], 10);
|
||||
}
|
||||
|
||||
if (!isNaN(parseInt(t[1], 10))) {
|
||||
t[1] = parseInt(t[1], 10);
|
||||
}
|
||||
this.serverData.serverSupports[t[0]] = t[1];
|
||||
} else {
|
||||
this.serverData.serverSupports[t] = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// Set hostname from 396 (non-standard)
|
||||
case '396':
|
||||
this.options.hostname = line.arguments![1];
|
||||
break;
|
||||
// Set hostname from self-whois
|
||||
case '311':
|
||||
if (line.arguments![1] !== this.options.nick) {
|
||||
return;
|
||||
}
|
||||
this.options.hostname = line.arguments![3];
|
||||
break;
|
||||
case 'quit':
|
||||
if (line.user.nickname !== this.options.nick) {
|
||||
if (this.nickservStore[line.user.nickname]) {
|
||||
delete this.nickservStore[line.user.nickname];
|
||||
}
|
||||
|
||||
this.emit('leave', {
|
||||
nickname: line.user.nickname
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'nick':
|
||||
if (line.user.nickname === this.options.nick) {
|
||||
this.options.nick = line.arguments![0];
|
||||
} else if (this.nickservStore[line.user.nickname]) {
|
||||
delete this.nickservStore[line.user.nickname];
|
||||
}
|
||||
this.emit('nick', {
|
||||
oldNick: line.user.nickname,
|
||||
newNick: line.arguments![0]
|
||||
});
|
||||
break;
|
||||
case 'join':
|
||||
if (line.user.nickname === this.options.nick && line.trailing) {
|
||||
this.channels.push(line.trailing);
|
||||
}
|
||||
|
||||
this.emit('join', {
|
||||
nickname: line.user.nickname,
|
||||
channel: line.trailing
|
||||
});
|
||||
break;
|
||||
case 'part':
|
||||
case 'kick':
|
||||
if (line.user.nickname === this.options.nick) {
|
||||
const indexAt = this.channels.indexOf(line.arguments![0]);
|
||||
if (indexAt !== -1) {
|
||||
this.channels.splice(indexAt, 1);
|
||||
}
|
||||
}
|
||||
this.emit('leave', {
|
||||
nickname: line.user.nickname,
|
||||
channel: line.arguments![0]
|
||||
});
|
||||
break;
|
||||
case 'error':
|
||||
this.emit('error', { fatal: true, error: new Error(line.raw) });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Send a message with the max bytelength of 512 in mind for trailing
|
||||
public cmd(command: string, argv: string[], trailing: string): void {
|
||||
const args = argv.join(' ');
|
||||
let resolution: string[] = [];
|
||||
|
||||
// Prevent newline messages from being sent as a command
|
||||
const fs = trailing.split('\n');
|
||||
|
||||
// Predict the length the server is going to split at
|
||||
// :nickname!username@hostname command args :trailing\r\n
|
||||
const header = this.options.nick.length +
|
||||
this.options.hostname!.length +
|
||||
this.options.username!.length + 4 + 2;
|
||||
const offset = command.length + args.length + 3 + header;
|
||||
|
||||
// Split the message up into chunks
|
||||
for (const i in fs) {
|
||||
const msg = fs[i];
|
||||
if (msg.length > MAXMSGLEN - offset) {
|
||||
resolution = resolution.concat(IRC.truncate(msg, MAXMSGLEN - offset));
|
||||
} else {
|
||||
resolution.push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
for (const i in resolution) {
|
||||
// Add delay to writes to prevent RecvQ overflow
|
||||
setTimeout(() => {
|
||||
this.write('%s %s :%s', command, args, resolution[i]);
|
||||
}, 1000 * parseInt(i, 10));
|
||||
}
|
||||
}
|
||||
|
||||
public message(target: string, message: string): void {
|
||||
this.cmd('PRIVMSG', [target], message);
|
||||
}
|
||||
|
||||
public notice(target: string, message: string): void {
|
||||
this.cmd('NOTICE', [target], message);
|
||||
}
|
||||
|
||||
public connect(): void {
|
||||
if (!this.options.host || !this.options.port) {
|
||||
this.emit('error', {
|
||||
error: new Error('No host or port specified!'),
|
||||
fatal: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const opts = {
|
||||
port: this.options.port,
|
||||
host: this.options.host,
|
||||
rejectUnauthorized: false
|
||||
};
|
||||
|
||||
let connection: ConnectSocket;
|
||||
const connfn = () => {
|
||||
this.alive = true;
|
||||
this.authenticate();
|
||||
};
|
||||
|
||||
// For some reason, tls.connect and net.connect are not
|
||||
// compatible according to TypeScript..
|
||||
if (this.options.ssl) {
|
||||
connection = tls.connect(opts, connfn);
|
||||
} else {
|
||||
connection = net.connect(opts, connfn);
|
||||
}
|
||||
|
||||
this.socket = connection;
|
||||
|
||||
let buffer: any = '';
|
||||
this.socket!.on('data', (chunk) => {
|
||||
buffer += chunk;
|
||||
const data = buffer.split('\r\n');
|
||||
buffer = data.pop();
|
||||
|
||||
data.forEach((line: string) => {
|
||||
if (line.indexOf('PING') === 0) {
|
||||
this.socket!.write('PONG' + line.substring(4) + '\r\n');
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit line as raw
|
||||
this.emit('raw', line);
|
||||
|
||||
// Parse the line
|
||||
const parsed = parse(line);
|
||||
|
||||
// Emit the parsed line
|
||||
this.emit('line', parsed);
|
||||
|
||||
// Handle the line
|
||||
this.handleServerLine(parsed);
|
||||
});
|
||||
});
|
||||
|
||||
this.socket.on('close', (data) => {
|
||||
this.alive = false;
|
||||
this.emit('disconnect', { type: 'sock_closed', raw: data, message: 'Connection closed.' });
|
||||
|
||||
this.authenticated = false;
|
||||
});
|
||||
|
||||
this.socket.on('error', (data) => {
|
||||
this.alive = false;
|
||||
this.emit('error', { fatal: true, error: new Error(data) });
|
||||
|
||||
this.authenticated = false;
|
||||
});
|
||||
}
|
||||
}
|
77
irc/parser.ts
Normal file
77
irc/parser.ts
Normal file
@ -0,0 +1,77 @@
|
||||
|
||||
// :nickname!username@hostname command arg ume nts :trailing
|
||||
// or
|
||||
// :hostname command arg ume nts :trailing
|
||||
|
||||
export interface IIRCUser {
|
||||
nickname: string;
|
||||
username: string;
|
||||
hostname: string;
|
||||
}
|
||||
|
||||
export interface IIRCLine {
|
||||
user: IIRCUser;
|
||||
command: string;
|
||||
arguments?: string[];
|
||||
trailing?: string;
|
||||
raw: string;
|
||||
}
|
||||
|
||||
function parseERROR(line: string[]): IIRCLine {
|
||||
let final: IIRCLine = {
|
||||
user: { nickname: '', username: '', hostname: '' },
|
||||
command: 'ERROR',
|
||||
trailing: '',
|
||||
raw: line.join(' ')
|
||||
};
|
||||
|
||||
let pass1 = line.slice(1).join(' ');
|
||||
if (pass1.indexOf(':') === 0) {
|
||||
pass1 = pass1.substring(1);
|
||||
}
|
||||
|
||||
final.trailing = pass1;
|
||||
|
||||
return final;
|
||||
}
|
||||
|
||||
export function parse(rawline: string): IIRCLine {
|
||||
let final: IIRCLine = {
|
||||
user: {
|
||||
nickname: '',
|
||||
username: '',
|
||||
hostname: ''
|
||||
},
|
||||
command: '',
|
||||
arguments: [],
|
||||
trailing: '',
|
||||
raw: rawline
|
||||
};
|
||||
|
||||
let pass1 = (rawline.indexOf(':') === 0 ? rawline.substring(1).split(' ') : rawline.split(' '));
|
||||
if (pass1[0] === 'ERROR') {
|
||||
return parseERROR(pass1);
|
||||
}
|
||||
|
||||
if (pass1[0].indexOf('!') !== -1) {
|
||||
let nickuser = pass1[0].split('!');
|
||||
final.user.nickname = nickuser[0];
|
||||
let userhost = nickuser[1].split('@');
|
||||
final.user.username = userhost[0];
|
||||
final.user.hostname = userhost[1];
|
||||
} else {
|
||||
final.user.hostname = pass1[0];
|
||||
}
|
||||
|
||||
final.command = pass1[1];
|
||||
|
||||
let pass2 = pass1.slice(2).join(' ');
|
||||
if (pass2.indexOf(':') !== -1) {
|
||||
final.arguments = pass2.substring(0, pass2.indexOf(' :')).split(' ');
|
||||
final.trailing = pass2.substring(pass2.indexOf(':') + 1);
|
||||
} else {
|
||||
final.arguments = pass2.split(' ');
|
||||
}
|
||||
|
||||
return final
|
||||
}
|
9
irc/plugin.json
Normal file
9
irc/plugin.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"main": "plugin.js",
|
||||
"name": "irc",
|
||||
"description": "IRC Service for Squeebot 3",
|
||||
"tags": ["service", "irc"],
|
||||
"version": "1.0.0",
|
||||
"dependencies": [],
|
||||
"npmDependencies": []
|
||||
}
|
273
irc/plugin.ts
Normal file
273
irc/plugin.ts
Normal file
@ -0,0 +1,273 @@
|
||||
import util from 'util';
|
||||
|
||||
import {
|
||||
Plugin,
|
||||
EventListener,
|
||||
Configurable,
|
||||
InjectService,
|
||||
Auto
|
||||
} from '@squeebot/core/lib/plugin';
|
||||
|
||||
import { EMessageType, Formatter, IMessage, IMessageTarget, Protocol } from '@squeebot/core/lib/types';
|
||||
|
||||
import { logger } from '@squeebot/core/lib/core';
|
||||
import { IIRCMessage, IRC } from './irc';
|
||||
|
||||
import { IRCFormatter } from './format';
|
||||
|
||||
class IRCMessage implements IMessage {
|
||||
public time: Date = new Date();
|
||||
public resolved = false;
|
||||
public direct = false;
|
||||
|
||||
constructor(
|
||||
public type: EMessageType,
|
||||
public data: any,
|
||||
public source: Protocol,
|
||||
public sender: IMessageTarget,
|
||||
public target?: IMessageTarget,
|
||||
public guest = true) {}
|
||||
|
||||
public resolve(...args: any[]): void {
|
||||
this.resolved = true;
|
||||
this.source.resolve(this, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
class IRCProtocol extends Protocol {
|
||||
public format: Formatter = new IRCFormatter(true, true);
|
||||
public type = 'IRCProtocol';
|
||||
|
||||
private irc: IRC = new IRC(this.config.irc);
|
||||
private eventsAttached = false;
|
||||
|
||||
public start(...args: any[]): void {
|
||||
this.runEvents();
|
||||
|
||||
this.irc.connect();
|
||||
|
||||
this.running = true;
|
||||
this.emit('running');
|
||||
}
|
||||
|
||||
public stop(force = false): void {
|
||||
if (!this.running) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.irc.alive) {
|
||||
this.irc.disconnect();
|
||||
}
|
||||
|
||||
this.running = false;
|
||||
this.stopped = true;
|
||||
|
||||
if (force) {
|
||||
this.failed = true;
|
||||
}
|
||||
|
||||
this.emit('stopped');
|
||||
}
|
||||
|
||||
private validateNick(nickname: string, cb: Function): void {
|
||||
if (!this.config.nickserv || !this.config.nickserv.enabled) {
|
||||
return cb(true); // Assume the user is authentic
|
||||
}
|
||||
|
||||
let stop = false;
|
||||
const promiseTimeout = setTimeout(() => {
|
||||
stop = true;
|
||||
cb(false);
|
||||
}, 4000);
|
||||
|
||||
this.irc.emit('testnick', {
|
||||
nickname,
|
||||
func: (result: boolean) => {
|
||||
clearTimeout(promiseTimeout);
|
||||
if (stop) {
|
||||
return;
|
||||
}
|
||||
cb(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private runEvents(): void {
|
||||
if (this.eventsAttached) {
|
||||
return;
|
||||
}
|
||||
this.eventsAttached = true;
|
||||
|
||||
this.irc.on('authenticated', () => {
|
||||
logger.log('[%s] Instance started successfully.', this.fullName);
|
||||
});
|
||||
|
||||
this.irc.on('error', (errdat) => {
|
||||
if (errdat.fatal) {
|
||||
this.stop();
|
||||
}
|
||||
logger.error('[%s] Instance error:', this.fullName, errdat.error.message);
|
||||
});
|
||||
|
||||
this.irc.on('disconnect', (data) => {
|
||||
logger.warn('[%s] Instance disconnected:', this.fullName, data.message);
|
||||
this.stop();
|
||||
});
|
||||
|
||||
// Pass events from IRC to the main channel, where it will then be routed
|
||||
// to the list of handler plugins within a configured channel.
|
||||
|
||||
this.irc.on('leave', (data) => {
|
||||
const left = data.channel ? { id: data.channel, name: data.channel } : undefined;
|
||||
const newMessage = new IRCMessage(
|
||||
EMessageType.roomLeave,
|
||||
{},
|
||||
this,
|
||||
{ id: data.nickname, name: data.nickname },
|
||||
left);
|
||||
this.plugin.stream.emitTo('channel', 'event', newMessage);
|
||||
});
|
||||
|
||||
this.irc.on('join', (data) => {
|
||||
const newMessage = new IRCMessage(
|
||||
EMessageType.roomJoin,
|
||||
{},
|
||||
this,
|
||||
{ id: data.nickname, name: data.nickname },
|
||||
{ id: data.channel, name: data.channel });
|
||||
this.plugin.stream.emitTo('channel', 'event', newMessage);
|
||||
});
|
||||
|
||||
this.irc.on('nick', (data) => {
|
||||
const newMessage = new IRCMessage(
|
||||
EMessageType.nameChange,
|
||||
data.oldNick,
|
||||
this,
|
||||
{ id: data.newNick, name: data.newNick });
|
||||
this.plugin.stream.emitTo('channel', 'event', newMessage);
|
||||
});
|
||||
|
||||
this.irc.on('message', (msg: IIRCMessage) => {
|
||||
this.validateNick(msg.nickname, (valid: boolean) => {
|
||||
const to = msg.to === this.irc.options.nick ? msg.nickname : msg.to;
|
||||
const newMessage = new IRCMessage(
|
||||
EMessageType.message,
|
||||
msg.message,
|
||||
this,
|
||||
{ id: msg.nickname, name: msg.nickname },
|
||||
{ id: to, name: to },
|
||||
valid === false);
|
||||
|
||||
if (msg.to === this.irc.options.nick) {
|
||||
newMessage.direct = true;
|
||||
}
|
||||
|
||||
this.plugin.stream.emitTo('channel', 'message', newMessage);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public resolve(message: IMessage, ...data: any[]): void {
|
||||
let response = util.format(data[0], ...data.slice(1));
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(data[0])) {
|
||||
try {
|
||||
response = this.format.compose(data[0]);
|
||||
} catch (e) {
|
||||
logger.error('[%s] Failed to compose message:', this.fullName, e.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.irc.alive) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.irc.message(message.target!.id, response);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: Control system
|
||||
Temporary documentation:
|
||||
{
|
||||
name: 'service-name',
|
||||
restart: false,
|
||||
irc: {
|
||||
nick: 'Squeebot',
|
||||
host: 'localhost',
|
||||
port: 6667,
|
||||
password: null,
|
||||
sasl: false,
|
||||
ssl: false,
|
||||
channels: [],
|
||||
nickserv: {
|
||||
enabled: false,
|
||||
command: 'STATUS'
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@InjectService(IRCProtocol)
|
||||
@Configurable({ instances: [] })
|
||||
class IRCServicePlugin extends Plugin {
|
||||
@Auto()
|
||||
initialize(): void {
|
||||
const protoList = this.validateConfiguration();
|
||||
this.startAll(protoList);
|
||||
}
|
||||
|
||||
private startAll(list: any[]): void {
|
||||
for (const ins of list) {
|
||||
const newProto = new IRCProtocol(this, ins);
|
||||
logger.log('[%s] Starting IRC service "%s".', this.name, ins.name);
|
||||
this.service?.use(newProto, true);
|
||||
this.monitor(newProto);
|
||||
}
|
||||
}
|
||||
|
||||
private monitor(proto: Protocol): void {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private validateConfiguration(): any[] {
|
||||
if (!this.config.config.instances) {
|
||||
throw new Error('Configuration incomplete!');
|
||||
}
|
||||
|
||||
const instances = this.config.config.instances;
|
||||
const runnables: any[] = [];
|
||||
for (const ins of instances) {
|
||||
if (!ins.name || !ins.irc) {
|
||||
throw new Error('Invalid instance configuration!');
|
||||
}
|
||||
|
||||
const irc = ins.irc;
|
||||
if (!irc.nick || !irc.host) {
|
||||
logger.warn('[%s] Instance named %s was skipped for invalid configuration.',
|
||||
this.name, ins.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
runnables.push(ins);
|
||||
}
|
||||
|
||||
return runnables;
|
||||
}
|
||||
|
||||
@EventListener('pluginUnload')
|
||||
unloadEventHandler(plugin: string | Plugin): void {
|
||||
if (plugin === this.name || plugin === this) {
|
||||
logger.debug('[%s]', this.name, 'shutting down..');
|
||||
this.config.save().then(() =>
|
||||
this.service!.stopAll().then(() =>
|
||||
this.emit('pluginUnloaded', this)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IRCServicePlugin;
|
9
ircprototest/plugin.json
Normal file
9
ircprototest/plugin.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"main": "plugin.js",
|
||||
"name": "ircprototest",
|
||||
"description": "IRC Service Test",
|
||||
"tags": ["service", "irc", "test"],
|
||||
"version": "1.0.0",
|
||||
"dependencies": [],
|
||||
"npmDependencies": []
|
||||
}
|
35
ircprototest/plugin.ts
Normal file
35
ircprototest/plugin.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import {
|
||||
Plugin,
|
||||
EventListener,
|
||||
Configurable,
|
||||
InjectService,
|
||||
Auto
|
||||
} from '@squeebot/core/lib/plugin';
|
||||
|
||||
import { EMessageType, IMessage, IMessageTarget, Protocol } from '@squeebot/core/lib/types';
|
||||
|
||||
import { logger } from '@squeebot/core/lib/core';
|
||||
|
||||
class MyPlugin extends Plugin {
|
||||
@Auto()
|
||||
initialize(): void {
|
||||
}
|
||||
|
||||
@EventListener('message')
|
||||
messageHandler(msg: IMessage): void {
|
||||
if (msg.data.indexOf('Squeebot') !== -1) {
|
||||
msg.resolve('Hello %s!', msg.sender!.name);
|
||||
}
|
||||
}
|
||||
|
||||
@EventListener('pluginUnload')
|
||||
unloadEventHandler(plugin: string | Plugin): void {
|
||||
if (plugin === this.name || plugin === this) {
|
||||
logger.debug('[%s]', this.name, 'shutting down..');
|
||||
this.config.save().then(() =>
|
||||
this.emit('pluginUnloaded', this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MyPlugin;
|
460
package-lock.json
generated
Normal file
460
package-lock.json
generated
Normal file
@ -0,0 +1,460 @@
|
||||
{
|
||||
"name": "service-irc",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@squeebot/core": {
|
||||
"version": "file:../core",
|
||||
"requires": {
|
||||
"dateformat": "^4.0.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"semver": "^7.3.2",
|
||||
"tar": "^6.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
|
||||
"integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
|
||||
"requires": {
|
||||
"@babel/highlight": "^7.10.4"
|
||||
}
|
||||
},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
|
||||
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw=="
|
||||
},
|
||||
"@babel/highlight": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
|
||||
"integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.10.4",
|
||||
"chalk": "^2.0.0",
|
||||
"js-tokens": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/dateformat": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/dateformat/-/dateformat-3.0.1.tgz",
|
||||
"integrity": "sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g=="
|
||||
},
|
||||
"@types/fs-extra": {
|
||||
"version": "9.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.4.tgz",
|
||||
"integrity": "sha512-50GO5ez44lxK5MDH90DYHFFfqxH7+fTqEEnvguQRzJ/tY9qFrMSHLiYHite+F3SNmf7+LHC1eMXojuD+E3Qcyg==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/minipass": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/minipass/-/minipass-2.2.0.tgz",
|
||||
"integrity": "sha512-wuzZksN4w4kyfoOv/dlpov4NOunwutLA/q7uc00xU02ZyUY+aoM5PWIXEKBMnm0NHd4a+N71BMjq+x7+2Af1fg==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.14.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.9.tgz",
|
||||
"integrity": "sha512-JsoLXFppG62tWTklIoO4knA+oDTYsmqWxHRvd4lpmfQRNhX6osheUOWETP2jMoV/2bEHuMra8Pp3Dmo/stBFcw=="
|
||||
},
|
||||
"@types/semver": {
|
||||
"version": "7.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz",
|
||||
"integrity": "sha512-+nVsLKlcUCeMzD2ufHEYuJ9a2ovstb6Dp52A5VsoKxDXgvE051XgHI/33I1EymwkRGQkwnA0LkhnUzituGs4EQ=="
|
||||
},
|
||||
"@types/tar": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/tar/-/tar-4.0.4.tgz",
|
||||
"integrity": "sha512-0Xv+xcmkTsOZdIF4yCnd7RkOOyfyqPaqJ7RZFKnwdxfDbkN3eAAE9sHl8zJFqBz4VhxolW9EErbjR1oyH7jK2A==",
|
||||
"requires": {
|
||||
"@types/minipass": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
"integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"builtin-modules": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
|
||||
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8="
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"dateformat": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.0.0.tgz",
|
||||
"integrity": "sha512-zpKyDYpeePyYGJp2HhRxLHlA+jZQNjt+MwmcVmLxCIECeC4Ks3TI3yk/CSMKylbnCJ5htonfOugYtRRTpyoHow=="
|
||||
},
|
||||
"diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A=="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz",
|
||||
"integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==",
|
||||
"requires": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"requires": {
|
||||
"minipass": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
|
||||
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
|
||||
},
|
||||
"has": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"is-core-module": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.1.0.tgz",
|
||||
"integrity": "sha512-YcV7BgVMRFRua2FqQzKtTDMz8iCuLEyGKjr70q8Zm1yy2qKcurbFEd79PAdHV77oL3NrAaOVQIbMmiHQCHB7ZA==",
|
||||
"requires": {
|
||||
"has": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
|
||||
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||
},
|
||||
"minipass": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
|
||||
"integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"requires": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"path-parse": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.19.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz",
|
||||
"integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==",
|
||||
"requires": {
|
||||
"is-core-module": "^2.1.0",
|
||||
"path-parse": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"tar": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz",
|
||||
"integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==",
|
||||
"requires": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^3.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"tslint": {
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz",
|
||||
"integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"builtin-modules": "^1.1.1",
|
||||
"chalk": "^2.3.0",
|
||||
"commander": "^2.12.1",
|
||||
"diff": "^4.0.1",
|
||||
"glob": "^7.1.1",
|
||||
"js-yaml": "^3.13.1",
|
||||
"minimatch": "^3.0.4",
|
||||
"mkdirp": "^0.5.3",
|
||||
"resolve": "^1.3.2",
|
||||
"semver": "^5.3.0",
|
||||
"tslib": "^1.13.0",
|
||||
"tsutils": "^2.29.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"tsutils": {
|
||||
"version": "2.29.0",
|
||||
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
|
||||
"integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
|
||||
"requires": {
|
||||
"tslib": "^1.8.1"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz",
|
||||
"integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ=="
|
||||
},
|
||||
"universalify": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
|
||||
"integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug=="
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.2.tgz",
|
||||
"integrity": "sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ=="
|
||||
}
|
||||
}
|
||||
}
|
17
package.json
Normal file
17
package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "service-irc",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc -w"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@squeebot/core": "file:../core",
|
||||
"typescript": "^4.1.2"
|
||||
}
|
||||
}
|
14
squeebot.repo.json
Normal file
14
squeebot.repo.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "service-irc",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "irc",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
{
|
||||
"name": "ircprototest",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
],
|
||||
"typescript": true
|
||||
}
|
1
tsconfig.json
Normal file
1
tsconfig.json
Normal file
@ -0,0 +1 @@
|
||||
{"compilerOptions":{"downlevelIteration":true,"esModuleInterop":true,"experimentalDecorators":true,"forceConsistentCasingInFileNames":true,"skipLibCheck":true,"sourceMap":false,"strict":true,"target":"es5"}}
|
153
tslint.json
Normal file
153
tslint.json
Normal file
@ -0,0 +1,153 @@
|
||||
{
|
||||
"extends": "tslint:recommended",
|
||||
"rules": {
|
||||
"align": {
|
||||
"options": [
|
||||
"parameters",
|
||||
"statements"
|
||||
]
|
||||
},
|
||||
"array-type": false,
|
||||
"arrow-return-shorthand": true,
|
||||
"curly": true,
|
||||
"deprecation": {
|
||||
"severity": "warning"
|
||||
},
|
||||
"eofline": true,
|
||||
"import-blacklist": [
|
||||
true,
|
||||
"rxjs/Rx"
|
||||
],
|
||||
"import-spacing": true,
|
||||
"indent": {
|
||||
"options": [
|
||||
"spaces"
|
||||
]
|
||||
},
|
||||
"max-classes-per-file": false,
|
||||
"max-line-length": [
|
||||
true,
|
||||
140
|
||||
],
|
||||
"member-ordering": [
|
||||
true,
|
||||
{
|
||||
"order": [
|
||||
"static-field",
|
||||
"instance-field",
|
||||
"static-method",
|
||||
"instance-method"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-empty": false,
|
||||
"no-inferrable-types": [
|
||||
true,
|
||||
"ignore-params"
|
||||
],
|
||||
"no-non-null-assertion": false,
|
||||
"no-redundant-jsdoc": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-var-requires": false,
|
||||
"object-literal-key-quotes": [
|
||||
true,
|
||||
"as-needed"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"semicolon": {
|
||||
"options": [
|
||||
"always"
|
||||
]
|
||||
},
|
||||
"space-before-function-paren": {
|
||||
"options": {
|
||||
"anonymous": "never",
|
||||
"asyncArrow": "always",
|
||||
"constructor": "never",
|
||||
"method": "never",
|
||||
"named": "never"
|
||||
}
|
||||
},
|
||||
"typedef": [
|
||||
true,
|
||||
"call-signature"
|
||||
],
|
||||
"forin": false,
|
||||
"ban-types": {
|
||||
"function": false
|
||||
},
|
||||
"typedef-whitespace": {
|
||||
"options": [
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
},
|
||||
{
|
||||
"call-signature": "onespace",
|
||||
"index-signature": "onespace",
|
||||
"parameter": "onespace",
|
||||
"property-declaration": "onespace",
|
||||
"variable-declaration": "onespace"
|
||||
}
|
||||
]
|
||||
},
|
||||
"variable-name": {
|
||||
"options": [
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"allow-pascal-case"
|
||||
]
|
||||
},
|
||||
"whitespace": {
|
||||
"options": [
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type",
|
||||
"check-typecast"
|
||||
]
|
||||
},
|
||||
"component-class-suffix": true,
|
||||
"contextual-lifecycle": true,
|
||||
"directive-class-suffix": true,
|
||||
"no-conflicting-lifecycle": true,
|
||||
"no-host-metadata-property": true,
|
||||
"no-input-rename": true,
|
||||
"no-inputs-metadata-property": true,
|
||||
"no-output-native": true,
|
||||
"no-output-on-prefix": true,
|
||||
"no-output-rename": true,
|
||||
"no-outputs-metadata-property": true,
|
||||
"template-banana-in-box": true,
|
||||
"template-no-negated-async": true,
|
||||
"use-lifecycle-interface": true,
|
||||
"use-pipe-transform-interface": true,
|
||||
"directive-selector": [
|
||||
true,
|
||||
"attribute",
|
||||
"app",
|
||||
"camelCase"
|
||||
],
|
||||
"component-selector": [
|
||||
true,
|
||||
"element",
|
||||
"app",
|
||||
"kebab-case"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user