Facebook
From TJ, 3 Weeks ago, written in JavaScript.
Embed
Download Paste or View Raw
Hits: 124
  1. const fs = require('fs');
  2. const Discord = require('discord.js');
  3. const config = require('./config.json');
  4. const storage = require('./storage.json');
  5.  
  6. // Write changes to storage.json
  7. const writeStorage = () => {
  8.     fs.writeFileSync('./storage.json', JSON.stringify(storage, null, 4));
  9. };
  10.  
  11. // Convert a permissions integer to an array of permission names
  12. const permIntToNames = perms => {
  13.     // Convert to integer
  14.     perms = parseInt(perms.bitfield ? perms.bitfield : perms);
  15.     // Loop through permission entries and compile a list
  16.     const entries = [];
  17.     for (const [ name, bit ] of Object.entries(Discord.PermissionFlagsBits)) {
  18.         entries.push({ name, value: parseInt(bit) });
  19.     }
  20.     // Sort the list in descending order
  21.     entries.sort((a, b) => b.value - a.value);
  22.     // Check each entry and add it to the list if it's greater than
  23.     // the remaining number
  24.     const result = [];
  25.     for (const entry of entries) {
  26.         if (perms >= entry.value) {
  27.             result.push(entry.name);
  28.             perms -= entry.value;
  29.         }
  30.     }
  31.     // Return the list
  32.     return result;
  33. }
  34.  
  35. const getRichTs = (date = new Date(), type = 'R') => {
  36.     const ts = Math.floor(date.getTime() / 1000);
  37.     return `<t:${ts}:${type}>`;
  38. };
  39.  
  40. // Read command files from these folders
  41. const dirs = [
  42.     './commands',
  43.     './context/message',
  44.     './context/user'
  45. ];
  46.  
  47. const embedChannelIds = [];
  48.  
  49. const bot = new Discord.Client({
  50.     intents: [
  51.         Discord.GatewayIntentBits.Guilds,
  52.         Discord.GatewayIntentBits.GuildMembers,
  53.         Discord.GatewayIntentBits.GuildMessages,
  54.         Discord.GatewayIntentBits.GuildMessageReactions,
  55.         Discord.GatewayIntentBits.MessageContent
  56.     ],
  57.     partials: [
  58.         Discord.Partials.User,
  59.         Discord.Partials.Channel,
  60.         Discord.Partials.Message,
  61.         Discord.Partials.Reaction
  62.     ]
  63. });
  64.  
  65. const sendLog = async(title, fields, type, color = '#77a4ff') => {
  66.     try {
  67.         const channelId = config.discord.log_channels[type];
  68.         const channel = bot.channels.cache.get(channelId) || await bot.channels.fetch(channelId);
  69.         channel.send({
  70.             embeds: [
  71.                 new Discord.EmbedBuilder()
  72.                     .setTitle(title)
  73.                     .setFields(fields)
  74.                     .setColor(color)
  75.             ]
  76.         });
  77.     } catch (error) {
  78.         console.error(`Failed to send log:`, error);
  79.         console.error(`Log info:`, JSON.stringify({
  80.             type, title, fields, color
  81.         }, null, 2));
  82.     }
  83. };
  84.  
  85. bot.on(Discord.Events.ClientReady, async () => {
  86.     console.log(`Logged in as ${bot.user.tag}`);
  87.     // Send embeds if they don't exist
  88.     const embedFiles = fs.readdirSync('./embeds');
  89.     for (const file of embedFiles) {
  90.         const filePath = `./embeds/${file}`;
  91.         // Clear require cache and require the file
  92.         delete require.cache[require.resolve(filePath)];
  93.         const data = require(filePath);
  94.         // Make sure the channel exists
  95.         const channel = bot.channels.cache.get(data.channel);
  96.         if (!channel) continue;
  97.         embedChannelIds.push(data.channel);
  98.         // Check if a message ID is saved and if it exists
  99.         const messageId = storage.embeds[filePath];
  100.         let msg;
  101.         if (messageId) {
  102.             try {
  103.                 msg = await channel.messages.fetch(messageId);
  104.             } catch (error) {
  105.                 console.log(`Error fetching message ${messageId}:`, error);
  106.             }
  107.         }
  108.         // If the message exists, edit it
  109.         if (msg) {
  110.             //msg.edit(data.message);
  111.         // Otherwise, send a new one
  112.         } else {
  113.             channel.send(data.message).then(msg => {
  114.                 storage.embeds[filePath] = msg.id;
  115.                 writeStorage();
  116.             });
  117.         }
  118.     }
  119.     // Run onLoad functions for each command
  120.     for (const dir of dirs) {
  121.         if (!fs.existsSync(dir)) continue;
  122.         const files = fs.readdirSync(dir);
  123.         for (const file of files) {
  124.             const data = require(`${dir}/${file}`);
  125.             if (data.onLoad) data.onLoad(bot);
  126.         }
  127.     }
  128.     // Send log
  129.     await sendLog('Bot started', [{
  130.         name: 'Started',
  131.         value: `${getRichTs()}`
  132.     }], 'system', '#7eff77');
  133. });
  134.  
  135. // Handle new messages
  136. bot.on(Discord.Events.MessageCreate, message => {
  137.     // If the message isn't in an embed channel, ignore it
  138.     if (!embedChannelIds.includes(message.channel.id)) return;
  139.     // If the message was sent by a bot, ignore it
  140.     if (message.author.bot) return;
  141.     // If the message was sent in an embed channel, delete it
  142.     if (embedChannelIds.includes(message.channel.id)) {
  143.         message.delete();
  144.     }
  145. });
  146.  
  147. // Log message deletions
  148. bot.on(Discord.Events.MessageDelete, message => {
  149.     if (message.type != Discord.MessageType.Default) return;
  150.     let content = message.content || '*no content*';
  151.     if (content.length > 1024) content = content.substring(0, 1021) + '...';
  152.     sendLog('Message deleted', [
  153.         { name: 'Message', value: content },
  154.         { name: 'Channel', value: `<#${message.channel.id}>`, inline: true },
  155.         { name: 'Author', value: `<@${message.author.id}>`, inline: true }
  156.     ], 'messages', '#ff77a3');
  157. });
  158.  
  159. // Log message edits
  160. bot.on(Discord.Events.MessageUpdate, (oldMessage, newMessage) => {
  161.     let oldContent = oldMessage.content || '*no content*';
  162.     let newContent = newMessage.content || '*no content*';
  163.     if (oldContent == newContent) return;
  164.     if (oldContent.length > 1024) oldContent = oldContent.substring(0, 1021) + '...';
  165.     if (newContent.length > 1024) newContent = newContent.substring(0, 1021) + '...';
  166.     sendLog('Message edited', [
  167.         { name: 'Channel', value: `<#${newMessage.channel.id}>`, inline: true },
  168.         { name: 'Author', value: `<@${newMessage.author.id}>`, inline: true },
  169.         { name: 'Old message', value: oldContent },
  170.         { name: 'New message', value: newContent }
  171.     ], 'messages');
  172. });
  173.  
  174. // Log user joins
  175. bot.on(Discord.Events.GuildMemberAdd, member => {
  176.     sendLog('User joined', [
  177.         { name: 'User', value: `<@${member.id}>`, inline: true },
  178.         { name: 'Account created', value: getRichTs(member.user.createdAt), inline: true }
  179.     ], 'join_leave', '#7eff77');
  180. });
  181.  
  182. // Log user leaves
  183. bot.on(Discord.Events.GuildMemberRemove, member => {
  184.     sendLog('User left', [
  185.         { name: 'User', value: `<@${member.id}> (\`${member.user.tag}\`)`, inline: true },
  186.         { name: 'Joined', value: getRichTs(member.joinedAt), inline: true },
  187.     ], 'join_leave', '#ff77a3');
  188. });
  189.  
  190. // Log channel creation
  191. bot.on(Discord.Events.ChannelCreate, channel => {
  192.     sendLog('Channel created', [
  193.         { name: 'Channel', value: `<#${channel.id}>`, inline: true }
  194.     ], 'channels', '#7eff77');
  195. });
  196.  
  197. // Log channel deletion
  198. bot.on(Discord.Events.ChannelDelete, channel => {
  199.     sendLog('Channel deleted', [
  200.         { name: 'Channel', value: `<#${channel.id}>`, inline: true },
  201.         { name: 'Created', value: getRichTs(channel.createdAt), inline: true }
  202.     ], 'channels', '#ff77a3');
  203. });
  204.  
  205. // Log channel edits
  206. bot.on(Discord.Events.ChannelUpdate, (oldChannel, newChannel) => {
  207.     const newName = newChannel.name;
  208.     const oldName = oldChannel.name;
  209.     const newTopic = newChannel.topic ? newChannel.topic.substring(0, 1021) + '...' : '*no topic*';
  210.     const oldTopic = oldChannel.topic ? oldChannel.topic.substring(0, 1021) + '...' : '*no topic*';
  211.     const fields = [
  212.         { name: 'Channel', value: `<#${newChannel.id}>` }
  213.     ];
  214.     if (newName !== oldName) {
  215.         fields.push(
  216.             { name: 'Old name', value: oldName, inline: true },
  217.             { name: 'New name', value: newName, inline: true }
  218.         );
  219.     }
  220.     if (newTopic !== oldTopic) {
  221.         fields.push(
  222.             { name: 'Old topic', value: oldTopic },
  223.             { name: 'New topic', value: newTopic }
  224.         );
  225.     }
  226.     sendLog('Channel edited', fields, 'channels');
  227. });
  228.  
  229. // Log role creation
  230. bot.on(Discord.Events.GuildRoleCreate, role => {
  231.     sendLog('Role created', [
  232.         { name: 'Role', value: `<@&${role.id}>`, inline: true }
  233.     ], 'roles', '#7eff77');
  234. });
  235.  
  236. // Log role deletion
  237. bot.on(Discord.Events.GuildRoleDelete, role => {
  238.     sendLog('Role deleted', [
  239.         { name: 'Name', value: role.name, inline: true },
  240.         { name: 'Created', value: getRichTs(role.createdAt), inline: true }
  241.     ], 'roles', '#ff77a3');
  242. });
  243.  
  244. // Log role edits
  245. bot.on(Discord.Events.GuildRoleUpdate, (oldRole, newRole) => {
  246.     const newName = newRole.name;
  247.     const oldName = oldRole.name;
  248.     const newColor = newRole.hexColor;
  249.     const oldColor = oldRole.hexColor;
  250.     const newHoisted = newRole.hoist;
  251.     const oldHoisted = oldRole.hoist;
  252.     const newMentionable = newRole.mentionable;
  253.     const oldMentionable = oldRole.mentionable;
  254.     const newPerms = permIntToNames(newRole.permissions);
  255.     const oldPerms = permIntToNames(oldRole.permissions);
  256.     const addedPerms = newPerms.filter(perm => !oldPerms.includes(perm));
  257.     const removedPerms = oldPerms.filter(perm => !newPerms.includes(perm));
  258.     const oldPos = oldRole.position;
  259.     const newPos = newRole.position;
  260.     const fields = [
  261.         { name: 'Role', value: `<@&${newRole.id}>` }
  262.     ];
  263.     if (newName !== oldName) {
  264.         fields.push(
  265.             { name: 'Old name', value: oldName, inline: true },
  266.             { name: 'New name', value: newName, inline: true }
  267.         );
  268.     }
  269.     if (newColor !== oldColor) {
  270.         fields.push(
  271.             { name: 'Color', value: `\`${oldColor}\` => \`${newColor}\`` }
  272.         );
  273.     }
  274.     if (newHoisted !== oldHoisted) {
  275.         fields.push(
  276.             { name: 'Separate members', value: newHoisted ? 'No => Yes' : 'Yes => No' }
  277.         );
  278.     }
  279.     if (newMentionable !== oldMentionable) {
  280.         fields.push(
  281.             { name: 'Mentionable', value: newMentionable ? 'No => Yes' : 'Yes => No' }
  282.         );
  283.     }
  284.     if (newPerms !== oldPerms) {
  285.         if (addedPerms.length) fields.push(
  286.             { name: 'Added permissions', value: `\`${addedPerms.join('`, `')}\``, inline: true }
  287.         );
  288.         if (removedPerms.length) fields.push(
  289.             { name: 'Removed permissions', value: `\`${removedPerms.join('`, `')}\``, inline: true }
  290.         );
  291.     }
  292.     if (newPos !== oldPos) {
  293.         fields.push(
  294.             { name: 'Position', value: `${oldPos} => ${oldPos}` }
  295.         );
  296.     }
  297.     if (fields.length == 1) return;
  298.     sendLog('Role edited', fields, 'roles');
  299. });
  300.  
  301. // Log user role list and nickname changes
  302. bot.on(Discord.Events.GuildMemberUpdate, (oldMember, newMember) => {
  303.     const oldRoles = oldMember.roles.cache;
  304.     const newRoles = newMember.roles.cache;
  305.     const addedRoles = newRoles.filter(role => !oldRoles.has(role.id));
  306.     const removedRoles = oldRoles.filter(role => !newRoles.has(role.id));
  307.     const oldNick = oldMember.nickname || '*no nickname*';
  308.     const newNick = newMember.nickname || '*no nickname*';
  309.     if (addedRoles.size || removedRoles.size) {
  310.         const fields = [
  311.             { name: 'User', value: `<@${newMember.id}>`, inline: true }
  312.         ];
  313.         if (addedRoles.size) {
  314.             fields.push(
  315.                 { name: 'Added roles', value: addedRoles.map(role => `<@&${role.id}>`).join(', '), inline: true }
  316.             );
  317.         }
  318.         if (removedRoles.size) {
  319.             fields.push(
  320.                 { name: 'Removed roles', value: removedRoles.map(role => `<@&${role.id}>`).join(', '), inline: true }
  321.             );
  322.         }
  323.         sendLog('User roles updated', fields, 'roles');
  324.     }
  325.     if (oldNick !== newNick) {
  326.         sendLog('User nickname changed', [
  327.             { name: 'User', value: `<@${newMember.id}>`, inline: true },
  328.             { name: 'Old nickname', value: oldNick, inline: true },
  329.             { name: 'New nickname', value: newNick, inline: true }
  330.         ], 'roles');
  331.     }
  332. });
  333.  
  334. // Handle interactions
  335. bot.on(Discord.Events.InteractionCreate, async interaction => {
  336.     // Handle Apps command on messages
  337.     if (interaction.isMessageContextMenuCommand()) {
  338.         const data = require(`./context/message/${interaction.commandName}.js`);
  339.         data.handler(interaction);
  340.     }
  341.     // Handle Apps command on users
  342.     if (interaction.isUserContextMenuCommand()) {
  343.         const data = require(`./context/user/${interaction.commandName}.js`);
  344.         data.handler(interaction);
  345.     }
  346.     // Handle slash commands
  347.     if (interaction.isChatInputCommand()) {
  348.         const data = require(`./commands/${interaction.commandName}.js`);
  349.         data.handler(interaction);
  350.     }
  351. });
  352.  
  353. // Handle RSS feeds
  354. const rssChecks = {};
  355. setTimeout(async() => {
  356.     const rssFiles = fs.readdirSync('./rss');
  357.     for (const file of rssFiles) {
  358.         const filePath = `./rss/${file}`;
  359.         const data = require(filePath);
  360.         if (rssChecks[file] && (Date.now()-rssChecks[file].lastCheck) < data.frequency_mins * 60 * 1000)
  361.             continue;
  362.         const parser = new rssParser();
  363.         const rss = await parser.parseURL(data.rss);
  364.         if (rssChecks[filePath]) {
  365.             const newEntries = rss.items.filter(item => item.link != rssChecks[filePath].lastItem.link);
  366.             newEntries.reverse();
  367.             const webhook = new Discord.WebhookClient({ url: data.webhook });
  368.             for (const entry of newEntries) {
  369.                 webhook.send({
  370.                     embeds: [
  371.                         new Discord.EmbedBuilder()
  372.                             .setAuthor({ name: entry.author })
  373.                             .setTitle(entry.title)
  374.                             .setURL(entry.link)
  375.                             .setDescription(entry.contentSnippet.split('\n')[0])
  376.                             .setFooter({ text: 'Click the title to read more...' })
  377.                             .setColor(data.embed_color)
  378.                     ]
  379.                 });
  380.             }
  381.         }
  382.         rssChecks[filePath] = {
  383.             lastCheck: Date.now(),
  384.             lastItem: rss.items[0]
  385.         };
  386.     }
  387. }, 1000);
  388.  
  389. bot.login(config.discord.token);