add: Bump up the version and add RELLAYMSG for IRC.
This commit is contained in:
10
README.md
10
README.md
@@ -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
148
bot.js
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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
10
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user