add: Bump up the version and add RELLAYMSG for IRC.

This commit is contained in:
2025-09-28 13:53:56 +02:00
parent 6ac41d3215
commit 20060d1130
5 changed files with 118 additions and 56 deletions

View File

@@ -9,24 +9,24 @@ A crappy group chat to a IRC room bridge written in node </3
- TLS/Non-TLS IRC Support - Works with both secure (TLS) and standard IRC ports.
- Group chat bridges - Since this all works with a selfbot it means that you can bridge a Discord group chat to a IRC room.
- YAML Configuration - Simple and a human readable config.
- RELLAYMSG support - Can use RELLAYMSG on the IRC side.
# Warning:
I would not recommend using this in a active room since that might get the bot/userbot rate limited on Discord OR IRC which is not good !
I would not recommend using this in a active room since that might get the userbot rate limited on Discord OR IRC which is not good !
Also Using Userbots is against Discord's Terms of Service so your account/bot might get deactivated. This has not happen yet but it is still possible.
Also using Userbots is against Discord's Terms of Service so your account might get deactivated. This has not happen yet but it is still possible.
Also worth to mention. Discord nicknames only show up if you bridge a channel, in group chats the bridge will use the user's username and I don't think there is anything I can do about that.
Also this userbot does work with servers but it's better to only bridge groupchats (or DMs). If you want to bridge a server with an IRC room it's better to use something like Dis4irc.
## Features to be added:
- Webhooks for briding channels
- A docker compose file
- More options in the config.
- Relay MSG on the IRC side.
- Being able to configure the bot with commands instead of needing to config the commands.
- Storing debug logs and message logs in a txt file.
# What you need to run this:
- Node.js v18+

148
bot.js
View File

@@ -43,80 +43,114 @@ function logForward(...args) {
function createIRCClient(bridge) {
const ircConfig = bridge.irc;
const discordChannelId = bridge.discordChannelId;
const useRelayMsg = bridge.useRelayMsg === true;
const client = new IRC.Client();
client.connect({
host: ircConfig.server,
port: parseInt(ircConfig.port, 10),
nick: ircConfig.nick,
password: ircConfig.password || undefined,
tls: ircConfig.tls || false,
tlsOptions: {
servername: ircConfig.server,
rejectUnauthorized: false,
},
nick: ircConfig.nick,
password: ircConfig.password || undefined,
tls: ircConfig.tls || false,
tlsOptions: {
servername: ircConfig.server,
rejectUnauthorized: false,
},
});
client.state = {
hasOp: false,
relayMsgEnabled: useRelayMsg,
warnedRelayMsg: false,
};
client.on("connecting", () =>
logDebug(`Connecting to ${ircConfig.server}:${ircConfig.port} ...`)
logDebug(`Connecting to ${ircConfig.server}:${ircConfig.port} ...`)
);
client.on("connected", () =>
logDebug(`TCP connection established to ${ircConfig.server}`)
logDebug(`TCP connection established to ${ircConfig.server}`)
);
client.on("registered", () => {
console.log(
`Connected to IRC ${ircConfig.server} as ${ircConfig.nick}, joining ${ircConfig.channel}`
);
client.join(ircConfig.channel, () =>
logDebug(`Sent JOIN for ${ircConfig.channel}`)
logDebug(`Sent JOIN for ${ircConfig.channel}`)
);
});
client.on("join", (event) =>
logDebug(`Joined ${event.channel} as ${event.nick}`)
);
client.on("names", (event) => {
const myNick = ircConfig.nick;
const modes = event.users[myNick];
if (Array.isArray(modes) && modes.includes("@")) {
client.state.hasOp = true;
logDebug(`${myNick} detected as +o from NAMES list`);
}
});
client.on("mode", (event) => {
if (event.target === ircConfig.channel) {
for (const m of event.modes) {
if (m.mode === "+o" && m.param === ircConfig.nick) {
client.state.hasOp = true;
logDebug(`${ircConfig.nick} gained +o (channel operator)`);
} else if (m.mode === "-o" && m.param === ircConfig.nick) {
client.state.hasOp = false;
logDebug(`${ircConfig.nick} lost +o (channel operator)`);
}
}
}
});
client.on("error", (event) =>
console.error(`IRC error on ${ircConfig.server}:`, event)
console.error(`IRC error on ${ircConfig.server}:`, event)
);
client.on("socket close", () =>
logDebug(`Socket closed for ${ircConfig.server}`)
logDebug(`Socket closed for ${ircConfig.server}`)
);
client.on("socket error", (err) =>
console.error(`Socket error on ${ircConfig.server}:`, err)
console.error(`Socket error on ${ircConfig.server}:`, err)
);
// Handle IRC → Discord
client.on("message", (event) => {
if (event.nick === ircConfig.nick) return; // Skip self
if (event.nick === ircConfig.nick || event.nick.endsWith("/dc")) return;
const ircMessage = `<${event.nick}> ${event.message}`;
const discordChannel = discordClient.channels.cache.get(discordChannelId);
if (discordChannel) discordChannel.send(ircMessage).catch(console.error);
});
client.on("relaymsg", (event) => {
const discordChannel = discordClient.channels.cache.get(discordChannelId);
if (discordChannel)
discordChannel
.send(`[relay ${event.nick}] ${event.message}`)
.catch(console.error);
});
client.on("relaymsg", (event) => {
const discordChannel = discordClient.channels.cache.get(discordChannelId);
if (discordChannel)
discordChannel.send(`<${event.nick}> ${event.message}`).catch(console.error);
});
if (DEBUG) {
client.on("raw", (event) => console.log("RAW:", event.line));
}
if (DEBUG) {
client.on("raw", (event) => console.log("RAW:", event.line));
}
return { client, channel: ircConfig.channel, nick: ircConfig.nick, discordChannelId };
return {
client,
channel: ircConfig.channel,
nick: ircConfig.nick,
discordChannelId,
useRelayMsg,
};
}
// Initialize IRC clients
for (const bridge of bridges) {
const { client, channel, nick, discordChannelId } = createIRCClient(bridge);
ircClients.set(client, { channel, nick, discordChannelId });
const { client, channel, nick, discordChannelId, useRelayMsg } =
createIRCClient(bridge);
ircClients.set(client, { channel, nick, discordChannelId, useRelayMsg });
}
// Discord ready
discordClient.once("ready", () =>
console.log(`Logged in as ${discordClient.user.tag}`)
console.log(`Logged in as ${discordClient.user.tag}`)
);
// Forward Discord messages → IRC
@@ -145,16 +179,14 @@ discordClient.on("messageCreate", async (message) => {
const referencedMessage = await message.channel.messages.fetch(
message.reference.messageId
);
if (referencedMessage.content) {
let refAuthor = referencedMessage.author.username;
if (referencedMessage.guild) {
const member = await referencedMessage.guild.members
.fetch(referencedMessage.author.id)
.catch(() => null);
.fetch(referencedMessage.author.id)
.catch(() => null);
if (member) refAuthor = member.displayName;
}
const originalText = referencedMessage.content.replace(/\n/g, " ");
quote = `<${refAuthor}> said: ${originalText} | `;
}
@@ -163,17 +195,35 @@ discordClient.on("messageCreate", async (message) => {
}
}
// Build message text
let discordMessage = `${quote}<${nickname}> ${message.content}`;
// Append attachment URLs
// Build message
let baseMessage = message.content;
if (quote) baseMessage = `${quote}${baseMessage}`;
if (message.attachments.size > 0) {
const urls = message.attachments.map((att) => att.url).join(" ");
discordMessage += ` [Attachments: ${urls}]`;
baseMessage += ` [Attachments: ${urls}]`;
}
const privmsgMessage = `<${nickname}> ${baseMessage}`;
logForward(`Forwarding Discord → IRC [${info.channel}]: ${discordMessage}`);
ircClient.say(info.channel, discordMessage);
logForward(`Forwarding Discord → IRC [${info.channel}]: ${privmsgMessage}`);
// --- RELAYMSG logic ---
if (info.useRelayMsg) {
if (ircClient.state.hasOp) {
const relayNick = `${nickname}/dc`;
ircClient.raw(`RELAYMSG ${info.channel} ${relayNick} :${baseMessage}`);
} else {
if (!ircClient.state.warnedRelayMsg) {
ircClient.say(
info.channel,
`[Bridge] Missing +o, falling back to PRIVMSG (RELAYMSG disabled)`
);
ircClient.state.warnedRelayMsg = true;
}
ircClient.say(info.channel, privmsgMessage);
}
} else {
ircClient.say(info.channel, privmsgMessage);
}
}
});
@@ -205,7 +255,15 @@ discordClient.on("messageReactionAdd", async (reaction, user) => {
const reactionMessage = `<${nickname}> reacted with ${emoji} to "${originalContent}"`;
logForward(`Reaction → IRC [${info.channel}]: ${reactionMessage}`);
ircClient.say(info.channel, reactionMessage);
if (info.useRelayMsg && ircClient.state.hasOp) {
const relayNick = `${nickname}/dc`;
ircClient.raw(
`RELAYMSG ${info.channel} ${relayNick} :${reactionMessage}`
);
} else {
ircClient.say(info.channel, reactionMessage);
}
}
} catch (err) {
logDebug("Failed to handle reaction:", err);
@@ -214,5 +272,5 @@ discordClient.on("messageReactionAdd", async (reaction, user) => {
// Login to Discord
discordClient.login(DISCORD_TOKEN).catch((err) =>
console.error("Failed to login to Discord:", err)
console.error("Failed to login to Discord:", err)
);

View File

@@ -1,3 +1,5 @@
# WARNING YAML is very sensitive so make sure everything is formatted like in this example configuration file :D
discord:
token: "YOUR_DISCORD_TOKEN" # Insert your bot or user token here.
@@ -15,6 +17,7 @@ bridges:
nick: "MyBot"
password: null # or "yourpassword" if required
channel: "#test"
useRelayMsg: false
# Adding more then one bridge
- discordChannelId: "987654321098765432"
@@ -25,3 +28,4 @@ bridges:
nick: "OtherBot"
password: null
channel: "#another"
useRelayMsg: true

10
package-lock.json generated
View File

@@ -1,13 +1,13 @@
{
"name": "bridge",
"version": "1.0.0",
"name": "discord-irc-bridge",
"version": "2.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bridge",
"version": "1.0.0",
"license": "ISC",
"name": "discord-irc-bridge",
"version": "2.0.1",
"license": "Apache-2.0",
"dependencies": {
"discord.js-selfbot-v13": "^3.4.5",
"irc": "^0.5.2",

View File

@@ -1,6 +1,6 @@
{
"name": "discord-irc-bridge",
"version": "2.0.0",
"version": "2.0.1",
"description": "A Discord <-> IRC bridge using selfbot and irc-framework",
"main": "bot.js",
"scripts": {