236 lines
6.3 KiB
JavaScript
236 lines
6.3 KiB
JavaScript
var NickMap = require("./nickmap");
|
|
var axios = require("axios");
|
|
var process = require("process");
|
|
|
|
module.exports = class Forwarder {
|
|
constructor(clientIRC, clientMatrix, mappingI2M, mappingM2I) {
|
|
this.clientIRC = clientIRC;
|
|
this.clientMatrix = clientMatrix;
|
|
this.mappingI2M = mappingI2M;
|
|
this.mappingM2I = mappingM2I;
|
|
this.nickMap = new NickMap("./nickmap.json");
|
|
}
|
|
|
|
joinIRCRooms() {
|
|
for (const room of Object.keys(this.mappingI2M)) {
|
|
this.clientIRC.join(room);
|
|
}
|
|
}
|
|
|
|
start() {
|
|
this.joinIRCRooms();
|
|
console.log("Starting relay");
|
|
this.clientIRC.on("close", this.onClose.bind(this));
|
|
this.clientIRC.on("message", this.onIRCMessage.bind(this));
|
|
this.clientMatrix.on("Room.timeline", this.onMatrixMessage.bind(this));
|
|
// Ugly: ensure rooms are joined
|
|
// the "registered" event seems to be unreliable for reconnection
|
|
setInterval(this.joinIRCRooms.bind(this), 20 * 1000);
|
|
}
|
|
|
|
onClose() {
|
|
console.log("IRC connection closed and failed to reconnect; exitting...");
|
|
process.exit(-1);
|
|
}
|
|
|
|
onIRCMessage(event) {
|
|
if (!event.target) {
|
|
// Not something we care about
|
|
return;
|
|
}
|
|
|
|
if (!this.mappingI2M[event.target]) {
|
|
// Not a mapped channel
|
|
return;
|
|
}
|
|
|
|
let content = null;
|
|
if (event.type == "action") {
|
|
content = {
|
|
msgtype: "m.text",
|
|
body: `* ${event.nick} ${event.message}`
|
|
}
|
|
} else {
|
|
content = {
|
|
msgtype: "m.text",
|
|
body: `[${event.nick}] ${event.message}`
|
|
}
|
|
}
|
|
|
|
content['net.typeblog.i2m.irc_nick'] = event.nick;
|
|
|
|
this.clientMatrix.sendMessage(this.mappingI2M[event.target], content);
|
|
}
|
|
|
|
stripMatrixName(name) {
|
|
let _name = name.replace(" (Perigram)", "");
|
|
if (_name.length < 16) {
|
|
return _name;
|
|
} else {
|
|
return _name.slice(0, 16);
|
|
}
|
|
}
|
|
|
|
processMatrixName(userId, name) {
|
|
let mappedName = this.nickMap.get(userId);
|
|
if (mappedName) {
|
|
return mappedName;
|
|
} else {
|
|
return this.stripMatrixName(name);
|
|
}
|
|
}
|
|
|
|
isMatrixCommand(msg) {
|
|
return msg.startsWith("!");
|
|
}
|
|
|
|
shouldUsePastebin(txt) {
|
|
return txt.length >= 160 || txt.split("\n").length > 3;
|
|
}
|
|
|
|
async pastebin(txt) {
|
|
let resp = await axios.request({
|
|
url: "https://fars.ee/",
|
|
method: "post",
|
|
headers: {
|
|
"Content-Type": "application/json"
|
|
},
|
|
data: {
|
|
content: txt,
|
|
filename: "message.txt"
|
|
}
|
|
});
|
|
|
|
if (resp.status != 200) {
|
|
throw resp.statusText;
|
|
} else {
|
|
return resp.data.url;
|
|
}
|
|
}
|
|
|
|
handleMatrixCommand(sender, msg) {
|
|
let splitted = msg.split(" ");
|
|
let cmd = splitted[0].slice(1);
|
|
|
|
switch (cmd) {
|
|
case "nick":
|
|
if (splitted.length < 2) {
|
|
this.nickMap.set(sender.userId, null);
|
|
this.clientMatrix.sendMessage(sender.roomId, {
|
|
msgtype: "m.text",
|
|
body: `Nickname of '${sender.name}' cleared`
|
|
});
|
|
} else if (splitted.length >= 3 || splitted[1].length > 16) {
|
|
this.clientMatrix.sendMessage(sender.roomId, {
|
|
msgtype: "m.text",
|
|
body: "Invalid nickname format (too long or contains space)"
|
|
});
|
|
} else {
|
|
this.nickMap.set(sender.userId, splitted[1]);
|
|
this.clientMatrix.sendMessage(sender.roomId, {
|
|
msgtype: "m.text",
|
|
body: `Nickname of '${sender.name}' changed to '${splitted[1]}'`
|
|
});
|
|
}
|
|
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async onMatrixMessage(event, room, toStartOfTimeline, removed, data) {
|
|
this.clientMatrix.sendReadReceipt(event);
|
|
if (toStartOfTimeline) {
|
|
return; // Ignore pagniation
|
|
}
|
|
|
|
if (!this.mappingM2I[room.roomId]) {
|
|
return; // Unmapped room
|
|
}
|
|
|
|
if (event.sender.userId == this.clientMatrix.getUserId()) {
|
|
return; // Prevent loop
|
|
}
|
|
|
|
if (Date.now() - event.getTs() >= 2 * 60 * 1000) {
|
|
// Ignore events older than 2 minutes
|
|
return;
|
|
}
|
|
|
|
if (!data.liveEvent) {
|
|
// Ignore non-live events
|
|
return;
|
|
}
|
|
|
|
let content = event.getContent();
|
|
|
|
if (event.getType() == "m.room.message" && this.isMatrixCommand(content.body)) {
|
|
if (this.handleMatrixCommand(event.sender, content.body)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
let replyUsername = null;
|
|
if (content['m.relates_to'] && content['m.relates_to']['m.in_reply_to']) {
|
|
let replyEvId = content['m.relates_to']['m.in_reply_to']['event_id'];
|
|
try {
|
|
let tl = await this.clientMatrix.getEventTimeline(room.getUnfilteredTimelineSet(), replyEvId);
|
|
let replyEv = tl.getEvents().filter((v) => v.getId() == replyEvId);
|
|
if (replyEv.length > 0) {
|
|
replyUsername = this.processMatrixName(replyEv[0].sender.userId, replyEv[0].sender.name);
|
|
if (replyEv[0].getContent()['net.typeblog.i2m.irc_nick']) {
|
|
replyUsername = replyEv[0].getContent()['net.typeblog.i2m.irc_nick'];
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.log(err);
|
|
}
|
|
}
|
|
|
|
let replyTxt = "";
|
|
if (replyUsername) {
|
|
replyTxt = replyUsername + ": "
|
|
}
|
|
|
|
let msgTxt = null;
|
|
switch (event.getType()) {
|
|
case "m.sticker":
|
|
msgTxt = `${content.body} ${this.clientMatrix.mxcUrlToHttp(content.url)}`;
|
|
break;
|
|
case "m.room.message":
|
|
switch (content.msgtype) {
|
|
case "m.image":
|
|
case "m.audio":
|
|
case "m.file":
|
|
case "m.video":
|
|
msgTxt = `${content.body} ${this.clientMatrix.mxcUrlToHttp(content.url)}`;
|
|
break;
|
|
default:
|
|
// Get rid of the reply quote generated by Element client
|
|
msgTxt = content.body.replace(/> <(.*)> (.*)\n\n/, "");
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (msgTxt != null) {
|
|
if (this.shouldUsePastebin(msgTxt)) {
|
|
try {
|
|
msgTxt = "Long Msg: " + await this.pastebin(msgTxt);
|
|
} catch (err) {
|
|
console.log(err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
let name = this.processMatrixName(event.sender.userId, event.sender.name);
|
|
if (content.msgtype == "m.emote") {
|
|
// Special format for emote
|
|
this.clientIRC.say(this.mappingM2I[room.roomId], `* ${name} ${msgTxt}`);
|
|
} else {
|
|
this.clientIRC.say(this.mappingM2I[room.roomId], `[${name}] ${replyTxt}${msgTxt}`);
|
|
}
|
|
}
|
|
}
|
|
} |