From 64d90f92c2511a27fe1dd0d3ff7db63ca9e13e78 Mon Sep 17 00:00:00 2001 From: ~purplebored Date: Sat, 20 Sep 2025 17:15:24 +0200 Subject: [PATCH 1/7] Small error in the readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 083a7bc..484d2ce 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Copy the `example.config.yaml` file, rename it to config.yaml, and set it up to To start the bridge, run: ``` -docker run -v "$(pwd)/config.yaml:/app/config.yaml:ro" foundry.fsky.io/purplebored/discord-userbot-to-irc:latest +docker run -d -v "$(pwd)/config.yaml:/app/config.yaml:ro" foundry.fsky.io/purplebored/discord-userbot-to-irc:latest ``` The /app/config.yaml file in the container must be mounted to the filesystem. -- 2.54.0 From a7f19fbb4f8f5e1128b45713602c1294176df24d Mon Sep 17 00:00:00 2001 From: ~purplebored Date: Sun, 21 Sep 2025 20:30:26 +0200 Subject: [PATCH 2/7] Add: a new Contributing.md file, Update: the readme and license --- CONTRIBUTING.md | 53 ++++++++++++++ LICENSE | 191 ++++++++++++++++++++++++++++++++++++++++++++++-- README.md | 16 ++++ package.json | 2 +- 4 files changed, 256 insertions(+), 6 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2d2ac2b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# Contributing to this project. + +Okay, so first of all big thanks for taking your time to contribute by either reporting issues or actually making changes for the code. I really apreciate this <3 + +## How Can I Contribute? + +### Reporting Bugs: +- Use the issue tab to report any bugs or to report issues in general +- Give as much info as possible: + - Steps to reproduce + - Expected behavior + - Actual bechvior + - And logs or error messeges. + +### Requesting a feature: + - Open a issue with the feature-request lable + - Cleary explain the problem your feature would solve (Or cleary explain why you want that feature.) + +### Submiting the code: +1. Fork the repository +2. Create a new branch for your change: + ```bash + git checkout -b feature/my-epic-change + ``` +3. Make your changes and commit them: +```bash +git commit -m "Add: Epic changes" +``` +4. Push to your fork and add a pull request. + +5. (Optional) PGP or SSH sign your commits. +This is an optional thing but I would really apreciate it if you signed your commits with either SSH or PGP. + +### Commit Message Conventions: + +Use simple and discrpive commits. (I know that I am not doing that but I am really trying Date: Sun, 21 Sep 2025 20:37:44 +0200 Subject: [PATCH 3/7] Update: I udpated the readme again I needed to delete a duplicate thingy and added a node badge at the top --- README.md | 6 +----- docker-compose.yaml | 12 ++++++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 docker-compose.yaml diff --git a/README.md b/README.md index c3ade70..70427ca 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +![Node](https://img.shields.io/badge/node-20%2B-brightgreen) # Discord-userbot-to-irc A crappy group chat to a IRC room bridge written in node Date: Sun, 21 Sep 2025 20:40:34 +0200 Subject: [PATCH 4/7] Fix: Oops this was not suposued to be here, This was not suposed to be here this is a bad docker compose file Signed-off-by: Alek Nikovich --- docker-compose.yaml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 docker-compose.yaml diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index fda5cf8..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,12 +0,0 @@ -version: "3.8" - -services: - discord-irc-bridge: - build: . - container_name: discord-irc-bridge - restart: unless-stopped - volumes: - - ./config.json:/app/config.json:ro - environment: - - NODE_ENV=production - tty: true -- 2.54.0 From 6ac41d321597ae85e3b48cbd0908d1fc5e81ccad Mon Sep 17 00:00:00 2001 From: ~purplebored Date: Wed, 24 Sep 2025 12:37:26 +0200 Subject: [PATCH 5/7] Fix: I fixed some of the spelling mistakes to make the readme file better. --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 70427ca..8930ef4 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ A crappy group chat to a IRC room bridge written in node Date: Sun, 28 Sep 2025 13:53:56 +0200 Subject: [PATCH 6/7] add: Bump up the version and add RELLAYMSG for IRC. --- README.md | 10 +-- bot.js | 148 ++++++++++++++++++++++++++++++-------------- example.config.yaml | 4 ++ package-lock.json | 10 +-- package.json | 2 +- 5 files changed, 118 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 8930ef4..72d7fad 100644 --- a/README.md +++ b/README.md @@ -9,24 +9,24 @@ A crappy group chat to a IRC room bridge written in node - 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) ); diff --git a/example.config.yaml b/example.config.yaml index 59ef9b1..8ff525c 100644 --- a/example.config.yaml +++ b/example.config.yaml @@ -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 diff --git a/package-lock.json b/package-lock.json index 7ee57b0..361f0c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 6f98076..ee08059 100644 --- a/package.json +++ b/package.json @@ -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": { -- 2.54.0 From efa1ec235c52315da0b9f371fea3a97e36b4dcd7 Mon Sep 17 00:00:00 2001 From: ~purplebored Date: Fri, 3 Oct 2025 22:41:38 +0200 Subject: [PATCH 7/7] Fix: Fixed a bug where rellaymsg from a user with a nick with special chars or a special font would not send over to the IRC side. --- bot.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/bot.js b/bot.js index 35ab8ea..205b1b6 100644 --- a/bot.js +++ b/bot.js @@ -39,6 +39,11 @@ function logForward(...args) { if (LOG_FORWARD) console.log(...args); } +// Utility to check ASCII +function isASCII(str) { + return /^[\x00-\x7F]*$/.test(str); +} + // Create IRC client for a bridge function createIRCClient(bridge) { const ircConfig = bridge.irc; @@ -206,22 +211,18 @@ discordClient.on("messageCreate", async (message) => { 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); - } + // RELAYMSG logic with proper fallback for non-ASCII + if (info.useRelayMsg && ircClient.state.hasOp && isASCII(nickname)) { + const relayNick = `${nickname}/dc`; + ircClient.raw(`RELAYMSG ${info.channel} ${relayNick} :${baseMessage}`); } else { + if (info.useRelayMsg && !ircClient.state.hasOp && !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); } } @@ -256,7 +257,7 @@ discordClient.on("messageReactionAdd", async (reaction, user) => { logForward(`Reaction → IRC [${info.channel}]: ${reactionMessage}`); - if (info.useRelayMsg && ircClient.state.hasOp) { + if (info.useRelayMsg && ircClient.state.hasOp && isASCII(nickname)) { const relayNick = `${nickname}/dc`; ircClient.raw( `RELAYMSG ${info.channel} ${relayNick} :${reactionMessage}` -- 2.54.0