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. - 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. - 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. - YAML Configuration - Simple and a human readable config.
- RELLAYMSG support - Can use RELLAYMSG on the IRC side.
# Warning: # 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 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: ## Features to be added:
- Webhooks for briding channels
- A docker compose file - A docker compose file
- More options in the config. - 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. - Storing debug logs and message logs in a txt file.
# What you need to run this: # What you need to run this:
- Node.js v18+ - Node.js v18+

148
bot.js
View File

@@ -43,80 +43,114 @@ function logForward(...args) {
function createIRCClient(bridge) { function createIRCClient(bridge) {
const ircConfig = bridge.irc; const ircConfig = bridge.irc;
const discordChannelId = bridge.discordChannelId; const discordChannelId = bridge.discordChannelId;
const useRelayMsg = bridge.useRelayMsg === true;
const client = new IRC.Client(); const client = new IRC.Client();
client.connect({ client.connect({
host: ircConfig.server, host: ircConfig.server,
port: parseInt(ircConfig.port, 10), port: parseInt(ircConfig.port, 10),
nick: ircConfig.nick, nick: ircConfig.nick,
password: ircConfig.password || undefined, password: ircConfig.password || undefined,
tls: ircConfig.tls || false, tls: ircConfig.tls || false,
tlsOptions: { tlsOptions: {
servername: ircConfig.server, servername: ircConfig.server,
rejectUnauthorized: false, rejectUnauthorized: false,
}, },
}); });
client.state = {
hasOp: false,
relayMsgEnabled: useRelayMsg,
warnedRelayMsg: false,
};
client.on("connecting", () => client.on("connecting", () =>
logDebug(`Connecting to ${ircConfig.server}:${ircConfig.port} ...`) logDebug(`Connecting to ${ircConfig.server}:${ircConfig.port} ...`)
); );
client.on("connected", () => client.on("connected", () =>
logDebug(`TCP connection established to ${ircConfig.server}`) logDebug(`TCP connection established to ${ircConfig.server}`)
); );
client.on("registered", () => { client.on("registered", () => {
console.log( console.log(
`Connected to IRC ${ircConfig.server} as ${ircConfig.nick}, joining ${ircConfig.channel}` `Connected to IRC ${ircConfig.server} as ${ircConfig.nick}, joining ${ircConfig.channel}`
); );
client.join(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) => client.on("error", (event) =>
console.error(`IRC error on ${ircConfig.server}:`, event) console.error(`IRC error on ${ircConfig.server}:`, event)
); );
client.on("socket close", () => client.on("socket close", () =>
logDebug(`Socket closed for ${ircConfig.server}`) logDebug(`Socket closed for ${ircConfig.server}`)
); );
client.on("socket error", (err) => 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) => { 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 ircMessage = `<${event.nick}> ${event.message}`;
const discordChannel = discordClient.channels.cache.get(discordChannelId); const discordChannel = discordClient.channels.cache.get(discordChannelId);
if (discordChannel) discordChannel.send(ircMessage).catch(console.error); if (discordChannel) discordChannel.send(ircMessage).catch(console.error);
}); });
client.on("relaymsg", (event) => { client.on("relaymsg", (event) => {
const discordChannel = discordClient.channels.cache.get(discordChannelId); const discordChannel = discordClient.channels.cache.get(discordChannelId);
if (discordChannel) if (discordChannel)
discordChannel discordChannel.send(`<${event.nick}> ${event.message}`).catch(console.error);
.send(`[relay ${event.nick}] ${event.message}`) });
.catch(console.error);
});
if (DEBUG) { if (DEBUG) {
client.on("raw", (event) => console.log("RAW:", event.line)); 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 // Initialize IRC clients
for (const bridge of bridges) { for (const bridge of bridges) {
const { client, channel, nick, discordChannelId } = createIRCClient(bridge); const { client, channel, nick, discordChannelId, useRelayMsg } =
ircClients.set(client, { channel, nick, discordChannelId }); createIRCClient(bridge);
ircClients.set(client, { channel, nick, discordChannelId, useRelayMsg });
} }
// Discord ready // Discord ready
discordClient.once("ready", () => discordClient.once("ready", () =>
console.log(`Logged in as ${discordClient.user.tag}`) console.log(`Logged in as ${discordClient.user.tag}`)
); );
// Forward Discord messages → IRC // Forward Discord messages → IRC
@@ -145,16 +179,14 @@ discordClient.on("messageCreate", async (message) => {
const referencedMessage = await message.channel.messages.fetch( const referencedMessage = await message.channel.messages.fetch(
message.reference.messageId message.reference.messageId
); );
if (referencedMessage.content) { if (referencedMessage.content) {
let refAuthor = referencedMessage.author.username; let refAuthor = referencedMessage.author.username;
if (referencedMessage.guild) { if (referencedMessage.guild) {
const member = await referencedMessage.guild.members const member = await referencedMessage.guild.members
.fetch(referencedMessage.author.id) .fetch(referencedMessage.author.id)
.catch(() => null); .catch(() => null);
if (member) refAuthor = member.displayName; if (member) refAuthor = member.displayName;
} }
const originalText = referencedMessage.content.replace(/\n/g, " "); const originalText = referencedMessage.content.replace(/\n/g, " ");
quote = `<${refAuthor}> said: ${originalText} | `; quote = `<${refAuthor}> said: ${originalText} | `;
} }
@@ -163,17 +195,35 @@ discordClient.on("messageCreate", async (message) => {
} }
} }
// Build message text // Build message
let discordMessage = `${quote}<${nickname}> ${message.content}`; let baseMessage = message.content;
if (quote) baseMessage = `${quote}${baseMessage}`;
// Append attachment URLs
if (message.attachments.size > 0) { if (message.attachments.size > 0) {
const urls = message.attachments.map((att) => att.url).join(" "); 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}`); logForward(`Forwarding Discord → IRC [${info.channel}]: ${privmsgMessage}`);
ircClient.say(info.channel, discordMessage);
// --- 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}"`; const reactionMessage = `<${nickname}> reacted with ${emoji} to "${originalContent}"`;
logForward(`Reaction → IRC [${info.channel}]: ${reactionMessage}`); 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) { } catch (err) {
logDebug("Failed to handle reaction:", err); logDebug("Failed to handle reaction:", err);
@@ -214,5 +272,5 @@ discordClient.on("messageReactionAdd", async (reaction, user) => {
// Login to Discord // Login to Discord
discordClient.login(DISCORD_TOKEN).catch((err) => 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: discord:
token: "YOUR_DISCORD_TOKEN" # Insert your bot or user token here. token: "YOUR_DISCORD_TOKEN" # Insert your bot or user token here.
@@ -15,6 +17,7 @@ bridges:
nick: "MyBot" nick: "MyBot"
password: null # or "yourpassword" if required password: null # or "yourpassword" if required
channel: "#test" channel: "#test"
useRelayMsg: false
# Adding more then one bridge # Adding more then one bridge
- discordChannelId: "987654321098765432" - discordChannelId: "987654321098765432"
@@ -25,3 +28,4 @@ bridges:
nick: "OtherBot" nick: "OtherBot"
password: null password: null
channel: "#another" channel: "#another"
useRelayMsg: true

10
package-lock.json generated
View File

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

View File

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