Files
Discord-userbot-to-irc/bot.js

219 lines
6.6 KiB
JavaScript

const fs = require("fs");
const YAML = require("yaml");
const { Client, Intents } = require("discord.js-selfbot-v13");
const IRC = require("irc-framework");
// Load config.yaml
let config;
try {
const file = fs.readFileSync("config.yaml", "utf8");
config = YAML.parse(file);
} catch (e) {
console.error("Failed to load or parse config.yaml:", e);
process.exit(1);
}
const DISCORD_TOKEN = config.discord.token;
const DEBUG = config.debug === true;
const LOG_FORWARD = config.logForward === true;
const bridges = config.bridges;
// Discord client
const discordClient = new Client({
checkUpdate: false,
intents: [
Intents.FLAGS.GUILDS,
Intents.FLAGS.GUILD_MESSAGES,
Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
],
});
// Map IRC clients
const ircClients = new Map();
// Logging helpers
function logDebug(...args) {
if (DEBUG) console.log(...args);
}
function logForward(...args) {
if (LOG_FORWARD) console.log(...args);
}
// Create IRC client for a bridge
function createIRCClient(bridge) {
const ircConfig = bridge.irc;
const discordChannelId = bridge.discordChannelId;
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,
},
});
client.on("connecting", () =>
logDebug(`🔌 Connecting to ${ircConfig.server}:${ircConfig.port} ...`)
);
client.on("connected", () =>
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}`)
);
});
client.on("join", (event) =>
logDebug(`🎉 Joined ${event.channel} as ${event.nick}`)
);
client.on("error", (event) =>
console.error(`IRC error on ${ircConfig.server}:`, event)
);
client.on("socket close", () =>
logDebug(`❌ Socket closed for ${ircConfig.server}`)
);
client.on("socket error", (err) =>
console.error(`⚠️ Socket error on ${ircConfig.server}:`, err)
);
client.on("message", (event) => {
if (event.nick === ircConfig.nick) return; // Skip self
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);
});
if (DEBUG) {
client.on("raw", (event) => console.log("RAW:", event.line));
}
return { client, channel: ircConfig.channel, nick: ircConfig.nick, discordChannelId };
}
// Initialize IRC clients
for (const bridge of bridges) {
const { client, channel, nick, discordChannelId } = createIRCClient(bridge);
ircClients.set(client, { channel, nick, discordChannelId });
}
// Discord ready
discordClient.once("ready", () =>
console.log(`Logged in as ${discordClient.user.tag}`)
);
// Forward Discord messages → IRC
discordClient.on("messageCreate", async (message) => {
if (message.author.bot) return;
for (const [ircClient, info] of ircClients.entries()) {
if (message.channel.id !== info.discordChannelId) continue;
if (message.author.id === discordClient.user.id) return;
// Fetch nickname
let nickname = message.author.username;
if (message.guild) {
try {
const member = await message.guild.members.fetch(message.author.id);
nickname = member ? member.displayName : message.author.username;
} catch (err) {
nickname = message.author.username;
}
}
// Handle quoted replies
let quote = "";
if (message.reference && message.reference.messageId) {
try {
const referencedMessage = await message.channel.messages.fetch(
message.reference.messageId
);
if (referencedMessage) {
const refMember =
referencedMessage.guild &&
(await referencedMessage.guild.members.fetch(
referencedMessage.author.id
).catch(() => null));
const refAuthor = refMember
? refMember.displayName
: referencedMessage.author.username;
const refContent = referencedMessage.content || "[Embed/Attachment]";
const quotedLines = refContent
.split("\n")
.map((line) => `> ${line}`)
.join("\n");
quote = `> ${refAuthor} said:\n${quotedLines}\n`;
}
} catch (err) {
logDebug("Failed to fetch referenced message:", err);
}
}
// Build message text
let discordMessage = `${quote}<${nickname}> ${message.content}`;
// Append attachment URLs (images/files)
if (message.attachments.size > 0) {
const urls = message.attachments.map((att) => att.url).join(" ");
discordMessage += ` [Attachments: ${urls}]`;
}
logForward(`Forwarding Discord → IRC [${info.channel}]: ${discordMessage}`);
ircClient.say(info.channel, discordMessage);
}
});
// Forward reactions → IRC
discordClient.on("messageReactionAdd", async (reaction, user) => {
if (user.bot) return;
try {
if (reaction.partial) await reaction.fetch();
const message = reaction.message;
const channelId = message.channel.id;
for (const [ircClient, info] of ircClients.entries()) {
if (channelId !== info.discordChannelId) continue;
// Get nickname
let nickname = user.username;
if (message.guild) {
try {
const member = await message.guild.members.fetch(user.id);
nickname = member ? member.displayName : user.username;
} catch (err) {
nickname = user.username;
}
}
const emoji = reaction.emoji.name;
const originalContent = message.content || "[Embed/Attachment]";
const reactionMessage = `<${nickname}> reacted with ${emoji} to "${originalContent}"`;
logForward(`Reaction → IRC [${info.channel}]: ${reactionMessage}`);
ircClient.say(info.channel, reactionMessage);
}
} catch (err) {
logDebug("Failed to handle reaction:", err);
}
});
// Login to Discord
discordClient.login(DISCORD_TOKEN).catch((err) =>
console.error("Failed to login to Discord:", err)
);