typescript schmypescript
This commit is contained in:
216
dist/clients/irc.js
vendored
Normal file
216
dist/clients/irc.js
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
import _fs from "fs";
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname } from "path";
|
||||
import net from "net";
|
||||
import tls from "tls";
|
||||
import EventEmitter from "events";
|
||||
const fs = _fs.promises;
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const colors = {
|
||||
white: "00", black: "01", navy: "02", green: "03", red: "04",
|
||||
brown: "05", purple: "06", orange: "07", yellow: "08",
|
||||
lightgreen: "09", teal: "10", cyan: "11", blue: "12",
|
||||
magenta: "13", gray: "14", lightgray: "15"
|
||||
};
|
||||
const msgmodes = {
|
||||
normal: "PRIVMSG {recipient} :{msg}",
|
||||
action: "PRIVMSG {recipient} :\u0001ACTION {msg}\u0001",
|
||||
notice: "NOTICE {recipient} :{msg}",
|
||||
};
|
||||
const replaceColor = (match, color, text) => {
|
||||
return colors[color] ? `\x03${colors[color]}${text}\x0F` : text;
|
||||
};
|
||||
export default class irc extends EventEmitter {
|
||||
options;
|
||||
socket;
|
||||
_recachetime = 60 * 30;
|
||||
_cmd = new Map();
|
||||
server;
|
||||
constructor(options) {
|
||||
super();
|
||||
this.options = {
|
||||
channels: [],
|
||||
host: "127.0.0.1",
|
||||
port: 6667,
|
||||
ssl: false,
|
||||
selfSigned: false,
|
||||
sasl: false,
|
||||
network: "test",
|
||||
nickname: "test",
|
||||
username: "test",
|
||||
realname: "test",
|
||||
set: "all",
|
||||
...options
|
||||
};
|
||||
this.server = {
|
||||
set: this.options.set,
|
||||
motd: "",
|
||||
me: {},
|
||||
channel: new Map(),
|
||||
user: new Map()
|
||||
};
|
||||
return (async () => {
|
||||
await this.initialize();
|
||||
return this;
|
||||
})();
|
||||
}
|
||||
async initialize() {
|
||||
const dir = (await fs.readdir(`${__dirname}/irc`)).filter(f => f.endsWith(".js"));
|
||||
await Promise.all(dir.map(async (mod) => {
|
||||
return (await import(`${__dirname}/irc/${mod}`)).default(this);
|
||||
}));
|
||||
this.connect();
|
||||
}
|
||||
connect(reconnect = false) {
|
||||
if (reconnect)
|
||||
this.socket = undefined;
|
||||
if (this.options.ssl) {
|
||||
this.socket = tls.connect({
|
||||
host: this.options.host,
|
||||
port: this.options.port,
|
||||
rejectUnauthorized: !this.options.selfSigned,
|
||||
}, () => this.handleConnection());
|
||||
}
|
||||
else {
|
||||
this.socket = net.connect({
|
||||
host: this.options.host,
|
||||
port: this.options.port,
|
||||
}, () => this.handleConnection());
|
||||
}
|
||||
if (!this.socket)
|
||||
throw new Error("Socket konnte nicht initialisiert werden.");
|
||||
this.socket.setEncoding("utf-8");
|
||||
this.socket.on("data", (msg) => {
|
||||
console.log("Received data:", msg);
|
||||
this.handleData(msg);
|
||||
});
|
||||
this.socket.on("end", () => {
|
||||
this.handleDisconnect();
|
||||
});
|
||||
this.socket.on("error", (err) => {
|
||||
this.handleError(err);
|
||||
});
|
||||
}
|
||||
handleConnection() {
|
||||
this.send(`NICK ${this.options.nickname}`);
|
||||
this.send(`USER ${this.options.username} 0 * :${this.options.realname}`);
|
||||
if (this.options.sasl)
|
||||
this.send("CAP LS");
|
||||
this.emit("data", "[irc] connected!");
|
||||
}
|
||||
handleData(msg) {
|
||||
msg.split(/\r?\n|\r/).forEach((line) => {
|
||||
if (line.trim().length > 0) {
|
||||
const cmd = this.parse(line);
|
||||
if (this._cmd.has(cmd.command))
|
||||
this._cmd.get(cmd.command)?.(cmd);
|
||||
}
|
||||
});
|
||||
}
|
||||
handleDisconnect() {
|
||||
this.emit("data", ["error", "[irc] stream ended, reconnecting in progress"]);
|
||||
this.connect(true);
|
||||
}
|
||||
handleError(err) {
|
||||
this.emit("data", ["error", `[irc] socket error: ${err.message}`]);
|
||||
this.connect(true);
|
||||
}
|
||||
join(channels) {
|
||||
if (!Array.isArray(channels))
|
||||
channels = [channels];
|
||||
channels.forEach(e => {
|
||||
this.send(`JOIN ${e}`);
|
||||
});
|
||||
}
|
||||
part(channel, msg) {
|
||||
this.send(`PART ${channel} :${msg || ""}`);
|
||||
}
|
||||
whois(nick) {
|
||||
if (!Array.isArray(nick))
|
||||
nick = [nick];
|
||||
nick.forEach(e => {
|
||||
this.send(`WHOIS ${e}`);
|
||||
});
|
||||
}
|
||||
send(data) {
|
||||
if (this.socket)
|
||||
this.socket.write(`${data}\n`);
|
||||
else
|
||||
this.emit("data", ["info", `[irc] nope: ${data}`]);
|
||||
}
|
||||
sendmsg(mode, recipient, msg) {
|
||||
const messages = Array.isArray(msg) ? msg : msg.split(/\r?\n/);
|
||||
if (messages.length >= 5)
|
||||
this.emit("data", ["error", "[irc] too many lines"]);
|
||||
messages.forEach((e) => {
|
||||
const formatted = msgmodes[mode]
|
||||
.replace("{recipient}", recipient)
|
||||
.replace("{msg}", this.format(e));
|
||||
this.send(formatted);
|
||||
});
|
||||
}
|
||||
parse(data) {
|
||||
const [a, ...b] = data.split(/ +:/);
|
||||
const tmp = a.split(" ").concat(b);
|
||||
const prefix = data.charAt(0) === ":"
|
||||
? tmp.shift() ?? null
|
||||
: null;
|
||||
const command = tmp.shift();
|
||||
const params = command.toLowerCase() === "privmsg"
|
||||
? [tmp.shift(), tmp.join(" :")]
|
||||
: tmp;
|
||||
return { prefix, command, params };
|
||||
}
|
||||
format(msg) {
|
||||
return msg
|
||||
.replace(/\[b\](.*?)\[\/b\]/g, "\x02$1\x02")
|
||||
.replace(/\[i\](.*?)\[\/i\]/g, "\x1D$1\x1D")
|
||||
.replace(/\[color=(.*?)](.*?)\[\/color\]/g, replaceColor);
|
||||
}
|
||||
parsePrefix(prefix) {
|
||||
if (!prefix)
|
||||
return false;
|
||||
const parsed = /:?(.*)\!(.*)@(.*)/.exec(prefix);
|
||||
if (!parsed)
|
||||
return false;
|
||||
return {
|
||||
nick: parsed[1],
|
||||
username: parsed[2],
|
||||
hostname: parsed[3],
|
||||
};
|
||||
}
|
||||
reply(tmp) {
|
||||
return {
|
||||
type: "irc",
|
||||
network: this.options.network,
|
||||
channel: tmp.params[0],
|
||||
channelid: tmp.params[0],
|
||||
user: {
|
||||
...this.parsePrefix(tmp.prefix),
|
||||
account: (() => {
|
||||
const parsedPrefix = this.parsePrefix(tmp.prefix);
|
||||
return parsedPrefix && this.server.user.has(parsedPrefix.nick)
|
||||
? this.server.user.get(parsedPrefix.nick)?.account || false
|
||||
: false;
|
||||
})(),
|
||||
prefix: (tmp.prefix?.charAt(0) === ":"
|
||||
? tmp.prefix.substring(1)
|
||||
: tmp.prefix) + `@${this.options.network}`,
|
||||
},
|
||||
message: tmp.params[1].replace(/\u0002/g, ""),
|
||||
time: Math.floor(Date.now() / 1000),
|
||||
raw: tmp,
|
||||
reply: (msg) => this.sendmsg("normal", tmp.params[0], msg),
|
||||
replyAction: (msg) => this.sendmsg("action", tmp.params[0], msg),
|
||||
replyNotice: (msg) => this.sendmsg("notice", tmp.params[0], msg),
|
||||
self: this.server,
|
||||
_chan: this.server.channel.get(tmp.params[0]),
|
||||
_user: this.server.user,
|
||||
_cmd: this._cmd,
|
||||
join: (chan) => this.join(chan),
|
||||
part: (chan, msg) => this.part(chan, msg),
|
||||
whois: (user) => this.whois(user),
|
||||
write: (msg) => this.send(msg)
|
||||
};
|
||||
}
|
||||
}
|
20
dist/clients/irc/cap.js
vendored
Normal file
20
dist/clients/irc/cap.js
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
export default (bot) => {
|
||||
bot._cmd.set("CAP", (msg) => {
|
||||
switch (msg.params[1]) {
|
||||
case "LS":
|
||||
bot.send(`CAP REQ :${msg.params[2]}`);
|
||||
break;
|
||||
case "ACK":
|
||||
bot.send("AUTHENTICATE PLAIN");
|
||||
break;
|
||||
}
|
||||
});
|
||||
bot._cmd.set("AUTHENTICATE", (msg) => {
|
||||
if (msg.params[0].match(/\+/)) {
|
||||
bot.send(`AUTHENTICATE ${Buffer.from(bot.username + "\u0000" + bot.username + "\u0000" + bot.options.password).toString("base64")}`);
|
||||
}
|
||||
});
|
||||
bot._cmd.set("900", (msg) => {
|
||||
bot.send("CAP END");
|
||||
});
|
||||
};
|
12
dist/clients/irc/invite.js
vendored
Normal file
12
dist/clients/irc/invite.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
export default (bot) => {
|
||||
bot._cmd.set("INVITE", (msg) => {
|
||||
const user = bot.parsePrefix(msg.prefix);
|
||||
const channel = msg.params[1];
|
||||
if (!bot.server.channel.has(channel)) {
|
||||
bot.join(channel);
|
||||
setTimeout(() => {
|
||||
bot.send(`PRIVMSG ${channel} :Hi. Wurde von ${user.nick} eingeladen.`);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
};
|
5
dist/clients/irc/join.js
vendored
Normal file
5
dist/clients/irc/join.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
export default (bot) => {
|
||||
bot._cmd.set("JOIN", (msg) => {
|
||||
bot.send(`WHO ${msg.params[0]}`);
|
||||
});
|
||||
};
|
12
dist/clients/irc/motd.js
vendored
Normal file
12
dist/clients/irc/motd.js
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
export default (bot) => {
|
||||
bot._cmd.set("372", (msg) => {
|
||||
bot.server.motd += `${msg.params[1]}\n`;
|
||||
});
|
||||
bot._cmd.set("375", (msg) => {
|
||||
bot.server.motd = `${msg.params[1]}\n`;
|
||||
});
|
||||
bot._cmd.set("376", (msg) => {
|
||||
bot.server.motd += `${msg.params[1]}\n`;
|
||||
bot.emit("data", ["motd", bot.server.motd]);
|
||||
});
|
||||
};
|
13
dist/clients/irc/msg.js
vendored
Normal file
13
dist/clients/irc/msg.js
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
export default (bot) => {
|
||||
bot._cmd.set("PRIVMSG", (msg) => {
|
||||
if (msg.params[1] === "\u0001VERSION\u0001")
|
||||
return bot.emit("data", ["ctcp:version", bot.reply(msg)]);
|
||||
else if (msg.params[1].match(/^\u0001PING .*\u0001/i))
|
||||
return bot.emit("data", ["ctcp:ping", bot.reply(msg)]);
|
||||
else
|
||||
bot.emit("data", ["message", bot.reply(msg)]);
|
||||
});
|
||||
bot._cmd.set("NOTICE", (msg) => {
|
||||
bot.emit("data", ["notice", msg.params[1]]);
|
||||
});
|
||||
};
|
8
dist/clients/irc/nick.js
vendored
Normal file
8
dist/clients/irc/nick.js
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
export default (bot) => {
|
||||
bot._cmd.set("NICK", (msg) => {
|
||||
const prefix = bot.parsePrefix(msg.prefix);
|
||||
if (bot.server.user.has(prefix.nick))
|
||||
bot.server.user.delete(prefix.nick);
|
||||
bot.whois(msg.params[0], true);
|
||||
});
|
||||
};
|
5
dist/clients/irc/part.js
vendored
Normal file
5
dist/clients/irc/part.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
export default (bot) => {
|
||||
bot._cmd.set("PART", (msg) => {
|
||||
delete bot.server.user[msg.params[0]];
|
||||
});
|
||||
};
|
5
dist/clients/irc/ping.js
vendored
Normal file
5
dist/clients/irc/ping.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
export default (bot) => {
|
||||
bot._cmd.set("PING", (msg) => {
|
||||
bot.send(`PONG ${msg.params.join('')}`);
|
||||
});
|
||||
};
|
6
dist/clients/irc/pwdreq.js
vendored
Normal file
6
dist/clients/irc/pwdreq.js
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
export default (bot) => {
|
||||
bot._cmd.set("464", (msg) => {
|
||||
if (bot.options.password.length > 0 && !bot.options.sasl)
|
||||
bot.send(`PASS ${bot.options.password}`);
|
||||
});
|
||||
};
|
6
dist/clients/irc/welcome.js
vendored
Normal file
6
dist/clients/irc/welcome.js
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
export default (bot) => {
|
||||
bot._cmd.set("001", (msg) => {
|
||||
bot.join(bot.options.channels);
|
||||
bot.emit("data", ["connected", msg.params[1]]);
|
||||
});
|
||||
};
|
17
dist/clients/irc/who.js
vendored
Normal file
17
dist/clients/irc/who.js
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
const max = 400;
|
||||
let whois = [];
|
||||
let chan;
|
||||
export default (bot) => {
|
||||
bot._cmd.set("352", (msg) => {
|
||||
chan = msg.params[1];
|
||||
whois.push(msg.params[5]);
|
||||
});
|
||||
bot._cmd.set("315", (msg) => {
|
||||
bot.server.channel.set(chan, whois);
|
||||
whois = [...new Set(whois)];
|
||||
Array(Math.ceil(whois.length / 10)).fill(undefined).map(() => whois.splice(0, 10)).forEach((l) => {
|
||||
bot.whois(l);
|
||||
});
|
||||
whois = [];
|
||||
});
|
||||
};
|
62
dist/clients/irc/whois.js
vendored
Normal file
62
dist/clients/irc/whois.js
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
export default (bot) => {
|
||||
bot._cmd.set("307", (msg) => {
|
||||
let tmpuser = bot.server.user.get(msg.params[1]) || {};
|
||||
tmpuser.account = msg.params[1];
|
||||
tmpuser.registered = true;
|
||||
bot.server.user.set(msg.params[1], tmpuser);
|
||||
});
|
||||
bot._cmd.set("311", (msg) => {
|
||||
let tmpuser = bot.server.user.get(msg.params[1]) || {};
|
||||
tmpuser.nickname = msg.params[1];
|
||||
tmpuser.username = msg.params[2];
|
||||
tmpuser.hostname = msg.params[3];
|
||||
tmpuser.realname = msg.params[5];
|
||||
tmpuser.prefix = `${msg.params[1]}!${msg.params[2]}@${msg.params[3]}`;
|
||||
bot.server.user.set(msg.params[1], tmpuser);
|
||||
});
|
||||
bot._cmd.set("313", (msg) => {
|
||||
let tmpuser = bot.server.user.get(msg.params[1]) || {};
|
||||
tmpuser.oper = true;
|
||||
bot.server.user.set(msg.params[1], tmpuser);
|
||||
});
|
||||
bot._cmd.set("318", (msg) => {
|
||||
let tmpuser = bot.server.user.get(msg.params[1]) || {};
|
||||
tmpuser = {
|
||||
nick: tmpuser.nick || false,
|
||||
nickname: tmpuser.nickname || false,
|
||||
username: tmpuser.username || false,
|
||||
hostname: tmpuser.hostname || false,
|
||||
realname: tmpuser.realname || false,
|
||||
account: tmpuser.account || false,
|
||||
prefix: tmpuser.prefix || false,
|
||||
registered: tmpuser.registered || false,
|
||||
oper: tmpuser.oper || false,
|
||||
channels: tmpuser.channels || [],
|
||||
cached: Math.floor(Date.now() / 1000),
|
||||
};
|
||||
bot.server.user.set(msg.params[1], tmpuser);
|
||||
if (msg.params[0] === msg.params[1]) {
|
||||
bot.server.me = tmpuser;
|
||||
bot.server.user.delete(msg.params[1]);
|
||||
}
|
||||
});
|
||||
bot._cmd.set("319", (msg) => {
|
||||
let tmpchan = new Map();
|
||||
let tmpuser = bot.server.user.get(msg.params[1]) || {};
|
||||
if (tmpuser.channels)
|
||||
tmpchan = new Map(tmpuser.channels);
|
||||
const chans = msg.params[2].trim().split(" ");
|
||||
chans.forEach((chan) => {
|
||||
const [flags, name] = chan.split("#");
|
||||
tmpchan.set(`#${name}`, flags);
|
||||
});
|
||||
tmpuser.channels = tmpchan;
|
||||
bot.server.user.set(msg.params[1], tmpuser);
|
||||
});
|
||||
bot._cmd.set("330", (msg) => {
|
||||
let tmpuser = bot.server.user.get(msg.params[1]) || {};
|
||||
tmpuser.account = msg.params[2];
|
||||
tmpuser.registered = true;
|
||||
bot.server.user.set(msg.params[1], tmpuser);
|
||||
});
|
||||
};
|
200
dist/clients/slack.js
vendored
Normal file
200
dist/clients/slack.js
vendored
Normal file
@ -0,0 +1,200 @@
|
||||
import https from "node:https";
|
||||
import url from "node:url";
|
||||
import EventEmitter from "node:events";
|
||||
import fetch from "flumm-fetch";
|
||||
export default class slack extends EventEmitter {
|
||||
options;
|
||||
token;
|
||||
api = "https://slack.com/api";
|
||||
interval = null;
|
||||
server;
|
||||
constructor(options) {
|
||||
super();
|
||||
this.options = {
|
||||
set: "all",
|
||||
...options,
|
||||
};
|
||||
this.token = this.options.token;
|
||||
this.server = {
|
||||
set: this.options.set,
|
||||
channel: new Map(),
|
||||
user: new Map(),
|
||||
wss: {
|
||||
url: null,
|
||||
socket: null,
|
||||
},
|
||||
me: {},
|
||||
};
|
||||
return (async () => {
|
||||
await this.connect();
|
||||
return this;
|
||||
})();
|
||||
}
|
||||
async connect() {
|
||||
const response = await fetch(`${this.api}/rtm.start?token=${this.token}`);
|
||||
const res = await response.json();
|
||||
if (!res.ok) {
|
||||
this.emit("data", ["error", res.description || "Connection failed"]);
|
||||
return;
|
||||
}
|
||||
res.channels?.forEach(channel => {
|
||||
this.server.channel.set(channel.id, channel.name);
|
||||
});
|
||||
res.users?.forEach(user => {
|
||||
this.server.user.set(user.id, {
|
||||
account: user.name,
|
||||
nickname: user.real_name,
|
||||
});
|
||||
});
|
||||
if (res.url) {
|
||||
this.server.wss.url = url.parse(res.url);
|
||||
this.initializeWebSocket();
|
||||
}
|
||||
else
|
||||
this.emit("data", ["error", "No WebSocket URL provided"]);
|
||||
}
|
||||
initializeWebSocket() {
|
||||
https.get({
|
||||
hostname: this.server.wss.url?.host,
|
||||
path: this.server.wss.url?.path,
|
||||
port: 443,
|
||||
headers: {
|
||||
Upgrade: "websocket",
|
||||
Connection: "Upgrade",
|
||||
"Sec-WebSocket-Version": 13,
|
||||
"Sec-WebSocket-Key": Buffer.from(Array(16)
|
||||
.fill(0)
|
||||
.map(() => Math.round(Math.random() * 0xff))).toString("base64"),
|
||||
}
|
||||
}, () => { })
|
||||
.on("upgrade", (_, sock) => {
|
||||
this.server.wss.socket = sock;
|
||||
this.server.wss.socket.setEncoding("utf-8");
|
||||
this.handleWebSocketEvents();
|
||||
})
|
||||
.on("error", err => {
|
||||
this.emit("data", ["error", `Failed to establish WebSocket: ${err.message}`]);
|
||||
});
|
||||
}
|
||||
handleWebSocketEvents() {
|
||||
if (!this.server.wss.socket)
|
||||
return;
|
||||
this.interval = setInterval(async () => await this.ping(), 30000);
|
||||
this.server.wss.socket.on("data", async (data) => {
|
||||
try {
|
||||
const parsedData = this.parseData(data);
|
||||
if (parsedData?.type === "message") {
|
||||
await Promise.all([
|
||||
this.getChannel(parsedData.channel),
|
||||
this.getUser(parsedData.user),
|
||||
]);
|
||||
this.emit("data", ["message", this.reply(parsedData)]);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
this.emit("data", ["error", err]);
|
||||
}
|
||||
});
|
||||
this.server.wss.socket.on("end", async () => {
|
||||
this.emit("data", ["debug", "WebSocket stream ended"]);
|
||||
await this.reconnect();
|
||||
});
|
||||
this.server.wss.socket.on("error", async (err) => {
|
||||
this.emit("data", ["error", err.message]);
|
||||
await this.reconnect();
|
||||
});
|
||||
}
|
||||
async reconnect() {
|
||||
this.server.wss.url = null;
|
||||
this.server.wss.socket = null;
|
||||
if (this.interval)
|
||||
clearInterval(this.interval);
|
||||
this.emit("data", ["info", "reconnecting slack"]);
|
||||
await this.connect();
|
||||
}
|
||||
async getChannel(channelId) {
|
||||
if (this.server.channel.has(channelId))
|
||||
return this.server.channel.get(channelId);
|
||||
const res = await (await fetch(`${this.api}/conversations.info?channel=${channelId}&token=${this.token}`)).json();
|
||||
if (!res.channel)
|
||||
throw new Error("Channel not found");
|
||||
this.server.channel.set(channelId, res.channel.name);
|
||||
return res.channel.name;
|
||||
}
|
||||
async getUser(userId) {
|
||||
if (this.server.user.has(userId))
|
||||
return this.server.user.get(userId);
|
||||
const res = await (await fetch(`${this.api}/users.info?user=${userId}&token=${this.token}`)).json();
|
||||
if (!res.user)
|
||||
throw new Error("User not found");
|
||||
const user = { account: res.user.name, nickname: res.user.real_name };
|
||||
this.server.user.set(userId, user);
|
||||
return user;
|
||||
}
|
||||
async send(channel, text) {
|
||||
const message = Array.isArray(text) ? text.join("\n") : text;
|
||||
const formatted = message.includes("\n") ? "```" + message + "```" : message;
|
||||
await this.write({
|
||||
type: "message",
|
||||
channel: channel,
|
||||
text: this.format(formatted),
|
||||
});
|
||||
}
|
||||
async ping() {
|
||||
await this.write({ type: "ping" });
|
||||
}
|
||||
async write(json) {
|
||||
const msg = JSON.stringify(json);
|
||||
const payload = Buffer.from(msg);
|
||||
if (payload.length > 2 ** 14) {
|
||||
this.emit("data", ["error", "message too long, slack limit reached"]);
|
||||
return;
|
||||
}
|
||||
if (!this.server.wss.socket) {
|
||||
await this.reconnect();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.server.wss.socket.cork();
|
||||
this.server.wss.socket.write(payload);
|
||||
this.server.wss.socket.uncork();
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
await this.reconnect();
|
||||
}
|
||||
}
|
||||
reply(tmp) {
|
||||
return {
|
||||
type: "slack",
|
||||
network: "Slack",
|
||||
channel: this.server.channel.get(tmp.channel),
|
||||
channelid: tmp.channel,
|
||||
user: this.server.user.get(tmp.user),
|
||||
self: this.server,
|
||||
message: tmp.text,
|
||||
time: ~~(Date.now() / 1000),
|
||||
raw: tmp,
|
||||
reply: (msg) => this.send(tmp.channel, msg),
|
||||
replyAction: (msg) => this.send(tmp.channel, `[i]${msg}[/i]`),
|
||||
replyNotice: (msg) => this.send(tmp.channel, msg),
|
||||
};
|
||||
}
|
||||
parseData(data) {
|
||||
try {
|
||||
const json = JSON.parse(data.toString());
|
||||
return json;
|
||||
}
|
||||
catch (err) {
|
||||
this.emit("data", ["error", "failed to parse data"]);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
format(msg) {
|
||||
return msg.toString()
|
||||
.replace(/\[b\](.*?)\[\/b\]/g, "*$1*")
|
||||
.replace(/\[s\](.*?)\[\/s\]/g, "~$1~")
|
||||
.replace(/\[i\](.*?)\[\/i\]/g, "_$1_")
|
||||
.replace(/\[color=(.*?)](.*?)\[\/color\]/g, "$2");
|
||||
}
|
||||
}
|
159
dist/clients/tg.js
vendored
Normal file
159
dist/clients/tg.js
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
import fetch from "flumm-fetch";
|
||||
import EventEmitter from "events";
|
||||
const allowedFiles = ["audio", "video", "photo", "document"];
|
||||
export default class tg extends EventEmitter {
|
||||
options;
|
||||
token;
|
||||
api;
|
||||
lastUpdate = 0;
|
||||
lastMessage = 0;
|
||||
poller = null;
|
||||
server = {
|
||||
set: "",
|
||||
channel: new Map(),
|
||||
user: new Map(),
|
||||
me: {},
|
||||
};
|
||||
constructor(options) {
|
||||
super();
|
||||
this.options = {
|
||||
pollrate: 1000,
|
||||
set: "all",
|
||||
...options,
|
||||
};
|
||||
this.token = this.options.token;
|
||||
this.api = `https://api.telegram.org/bot${this.token}`;
|
||||
this.server.set = this.options.set;
|
||||
return (async () => {
|
||||
await this.connect();
|
||||
await this.poll();
|
||||
return this;
|
||||
})();
|
||||
}
|
||||
async connect() {
|
||||
const res = await (await fetch(`${this.api}/getMe`)).json();
|
||||
if (!res.ok)
|
||||
throw this.emit("data", ["error", res.description ?? "Unknown error"]);
|
||||
this.server.me = {
|
||||
nickname: res.result.first_name,
|
||||
username: res.result.username,
|
||||
account: res.result.id.toString(),
|
||||
prefix: `${res.result.username}!${res.result.id.toString()}`,
|
||||
id: res.result.id.toString(),
|
||||
};
|
||||
}
|
||||
async getFile(fileId) {
|
||||
const res = await (await fetch(`${this.api}/getFile?file_id=${fileId}`)).json();
|
||||
if (!res.ok)
|
||||
return false;
|
||||
return `https://api.telegram.org/file/bot${this.token}/${res.result?.file_path}`;
|
||||
}
|
||||
async poll() {
|
||||
try {
|
||||
const updates = await this.fetchUpdates();
|
||||
if (!updates || updates.length === 0)
|
||||
return;
|
||||
this.lastUpdate = updates[updates.length - 1].update_id + 1;
|
||||
for (const update of updates)
|
||||
await this.processUpdate(update);
|
||||
}
|
||||
catch (err) {
|
||||
await this.handlePollError(err);
|
||||
}
|
||||
finally {
|
||||
setTimeout(() => this.poll(), this.options.pollrate);
|
||||
}
|
||||
}
|
||||
async fetchUpdates() {
|
||||
const res = await (await fetch(`${this.api}/getUpdates?offset=${this.lastUpdate}&allowed_updates=message`)).json();
|
||||
if (!res.ok)
|
||||
throw new Error(res.description || "Failed to fetch updates");
|
||||
return res.result || [];
|
||||
}
|
||||
async processUpdate(update) {
|
||||
if (update.message)
|
||||
await this.handleMessage(update.message);
|
||||
else if (update.callback_query)
|
||||
this.emit("data", ["callback_query", this.reply(update.callback_query.message, update.callback_query)]);
|
||||
else if (update.inline_query)
|
||||
this.emit("data", ["inline_query", update.inline_query]);
|
||||
}
|
||||
async handleMessage(message) {
|
||||
if (message.date >= Math.floor(Date.now() / 1000) - 10 &&
|
||||
message.message_id !== this.lastMessage) {
|
||||
this.lastMessage = message.message_id;
|
||||
if (!this.server.user.has(message.from.username || message.from.first_name)) {
|
||||
this.server.user.set(message.from.username || message.from.first_name, {
|
||||
nick: message.from.first_name,
|
||||
username: message.from.username,
|
||||
account: message.from.id.toString(),
|
||||
prefix: `${message.from.username}!${message.from.id.toString()}@Telegram`,
|
||||
id: message.from.id,
|
||||
});
|
||||
}
|
||||
try {
|
||||
const fileKey = Object.keys(message).find(key => allowedFiles.includes(key));
|
||||
if (fileKey) {
|
||||
let media = message[fileKey];
|
||||
if (fileKey === "photo")
|
||||
media = message[fileKey][message[fileKey].length - 1];
|
||||
message.media = await this.getFile(media.file_id);
|
||||
message.text = message.caption;
|
||||
delete message[fileKey];
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
this.emit("data", ["message", this.reply(message)]);
|
||||
}
|
||||
}
|
||||
async handlePollError(err) {
|
||||
if (!err.type)
|
||||
this.emit("data", ["error", "tg timed out lol"]);
|
||||
else if (err.type === "tg")
|
||||
this.emit("data", ["error", err.message]);
|
||||
await this.connect();
|
||||
}
|
||||
reply(tmp, opt = {}) {
|
||||
return {
|
||||
type: "tg",
|
||||
network: "Telegram",
|
||||
channel: tmp.chat?.title,
|
||||
channelid: tmp.chat?.id,
|
||||
user: {
|
||||
prefix: `${tmp.from.username}!${tmp.from.id}@Telegram`,
|
||||
nick: tmp.from.first_name,
|
||||
username: tmp.from.username,
|
||||
account: tmp.from.id.toString(),
|
||||
},
|
||||
self: this.server,
|
||||
message: tmp.text,
|
||||
time: tmp.date,
|
||||
raw: tmp,
|
||||
media: tmp.media || null,
|
||||
reply: async (msg, opt = {}) => await this.send(tmp.chat.id, msg, tmp.message_id, opt),
|
||||
replyAction: async (msg, opt = {}) => await this.send(tmp.chat.id, `Action ${msg}`, tmp.message_id, opt),
|
||||
};
|
||||
}
|
||||
async send(chatId, msg, reply = null, opt = {}) {
|
||||
const body = {
|
||||
chat_id: chatId,
|
||||
text: this.format(msg),
|
||||
parse_mode: "HTML",
|
||||
...opt,
|
||||
};
|
||||
if (reply)
|
||||
body["reply_to_message_id"] = reply;
|
||||
const opts = { method: "POST", body };
|
||||
return await (await fetch(`${this.api}/sendMessage`, opts)).json();
|
||||
}
|
||||
format(msg) {
|
||||
return msg.toString()
|
||||
.split("&").join("&")
|
||||
.split("<").join("<")
|
||||
.split(">").join(">")
|
||||
.replace(/\[b\](.*?)\[\/b\]/gsm, "<b>$1</b>")
|
||||
.replace(/\[i\](.*?)\[\/i\]/gsm, "<i>$1</i>")
|
||||
.replace(/\[color=(.*?)](.*?)\[\/color\]/gsm, "$2")
|
||||
.replace(/\[pre\](.*?)\[\/pre\]/gsm, "<pre>$1</pre>");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user