JustPaste.it
User avatar
@anonymous · Jan 18, 2025
const {

  Client,

  GatewayIntentBits,
  Partials,
  Collection,
  EmbedBuilder,
  PermissionsBitField,
  AttachmentBuilder,
} = require('discord.js');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const { createCanvas, loadImage } = require('canvas');

const config = require('../config.json');
require('dotenv').config();

// Initialize Discord client
const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent,
    GatewayIntentBits.GuildMembers,
    GatewayIntentBits.GuildMessageReactions,
    GatewayIntentBits.GuildBans,
  ],
  partials: [
    Partials.Channel,
    Partials.Message,
    Partials.User,
    Partials.GuildMember,
    Partials.Reaction,
  ],
});

// Initialize collections and properties
client.commands = new Collection();
client.aliases = new Collection();
client.slashCommands = new Collection();
client.buttons = new Collection();
client.prefix = config.prefix;

// Export client for use in other modules
module.exports = client;

// Load command handlers
fs.readdirSync('src/handlers').forEach((handler) => {
  require(`./handlers/${handler}`)(client);
});

// Load users to skip
const doNotDetectTheseUsersId = fs
  .readFileSync('configs/doNotDetectTheseUsersId.txt', 'utf-8')
  .split('\n')
  .map((id) => id.trim())
  .filter((id) => id !== '');

const virusTotalApiKey = process.env.VIRUS_TOTAL_KEY;
const inviteRegex =
  /(?:https?:\/\/)?(?:www\.)?(?:discord\.(gg|com|io|me|li|net)\/(?:invite\/)?[a-zA-Z0-9\-]+|discordapp\.com\/invite\/[a-zA-Z0-9\-]+)/;

const userMessageCounts = {};
const userMessages = {}; // Track all messages from a user
const spamThreshold = config.moderation.spam.threshold; // Number of messages allowed before blocking
const spamTimeFrame = config.moderation.spam.timeFrame; // Time frame for spam detection (5 seconds)
const blockDuration = config.moderation.spam.blockDuration; // Duration to block the user (1 minute)
const spamWarningSent = {}; // Track users who have already received a spam warning

const lastInviteWarning = {}; // Track last invite warning sent time
const inviteWarningCooldown = config.moderation.spam.warningCooldown; // Cooldown for invite warnings (10 seconds)
const lastBotMessageSent = {}; // Track last message sent time for each channel
const botMessageCooldown = config.moderation.spam.botMessageCooldown; // Cooldown for bot messages (15 seconds)

client.once('ready', async () => {
  // Fetch the log channel
  const logChannel = await client.channels.fetch(
    config.moderation.channels.logChannelId,
  );

  if (logChannel) {
    try {
      // Check if a webhook already exists
      const webhooks = await logChannel.fetchWebhooks();
      let webhook = webhooks.find((wh) => wh.name === 'Moderation Logs');

      if (!webhook) {
        // Create a new webhook if it doesn't exist
        webhook = await logChannel.createWebhook({
          name: config.moderation.webhook.name,
          avatar: config.moderation.webhook.avatar,
        });
        console.log('Webhook created successfully');
      } else {
        console.log('Webhook already exists');
      }

      // Store the webhook for later use
      client.moderationWebhook = webhook;
    } catch (error) {
      console.error('Error handling webhook:', error);
    }
  } else {
    console.error('Log channel not found');
  }
});

client.on('messageCreate', async (message) => {
  if (message.author.bot) return;

  // Check if the user is in the doNotDetectTheseUsersId list
  if (doNotDetectTheseUsersId.includes(message.author.id)) {
    console.log(`Skipping message from whitelisted user: ${message.author.id}`);
    return; // Exit the function early for whitelisted users
  }

  const userId = message.author.id;
  const currentTime = Date.now();

  // Initialize user tracking
  if (!userMessages[userId]) userMessages[userId] = [];
  if (!userMessageCounts[userId]) userMessageCounts[userId] = 0;

  // Add current message to the list of messages for the user
  userMessages[userId].push({
    id: message.id,
    timestamp: currentTime,
  });

  // Remove messages that are outside the time frame
  userMessages[userId] = userMessages[userId].filter(
    (msg) => currentTime - msg.timestamp <= spamTimeFrame,
  );

  // Update message count
  userMessageCounts[userId] = userMessages[userId].length;
  console.log(
    `User ${userId} (${message.author.username}) message count: ${userMessageCounts[userId]}`,
  );

  // Check if user exceeds spam threshold within the time frame
  if (userMessageCounts[userId] > spamThreshold) {
    const timeDiff = currentTime - (userMessages[userId][0]?.timestamp || 0);

    console.log(`Time difference: ${timeDiff}ms`);

    if (timeDiff <= spamTimeFrame) {
      const member = message.member;

      // Check if the bot has permission to manage messages
      if (
        message.guild.members.me.permissions.has(
          PermissionsBitField.Flags.ManageMessages,
        )
      ) {
        try {
          // Send a single message if not already sent
          if (!spamWarningSent[userId]) {
            const spamWarningMessage = await message.channel.send(
              `${message.author} has been blocked from sending messages due to spamming.`,
            );
            setTimeout(
              () => spamWarningMessage.delete().catch(console.error),
              5000,
            );
            spamWarningSent[userId] = true;
          }

          if (
            member &&
            member.roles.highest.position <=
              message.guild.members.me.roles.highest.position
          ) {
            if (
              message.guild.members.me.permissions.has(
                PermissionsBitField.Flags.ModerateMembers,
              )
            ) {
              await member.timeout(blockDuration, 'spamming');
            } else {
              console.error('Bot lacks permissions to timeout members.');
            }
          }
          console.log(`User ${message.author.username} blocked for spamming.`);
        } catch (error) {
          if (error.code !== 50013) {
            console.error('Error managing user:', error);
          }
        }

        // Collect message IDs to delete
        const messageIds = userMessages[userId].map((msg) => msg.id);

        // Bulk delete messages
        try {
          await message.channel.bulkDelete(messageIds, true);
          userMessages[userId] = []; // Clear tracked messages
          userMessageCounts[userId] = 0; // Reset message counts
        } catch (error) {
          if (error.code === 10008) {
            console.warn('Error deleting messages: Unknown Message. Skipping.');
          } else {
            console.error('Error deleting messages:', error);
          }
        }

        // Clear message counts
        userMessageCounts[userId] = 0;
      } else {
        console.error('Bot lacks permissions to manage messages.');
      }
    }
  }

  // Check if the message contains an invite link and handle accordingly
  if (inviteRegex.test(message.content)) {
    const lastWarningTime = lastInviteWarning[message.author.id] || 0;
    const timeSinceLastWarning = currentTime - lastWarningTime;

    if (timeSinceLastWarning >= inviteWarningCooldown) {
      if (
        !message.guild.members.me.permissions.has(
          PermissionsBitField.Flags.ManageMessages,
        )
      ) {
        console.error('Missing permissions to manage messages.');
        return;
      }

      try {
        // Channel specific cooldown check
        const channelId = message.channel.id;
        const lastMessageTime = lastBotMessageSent[channelId] || 0;
        const timeSinceLastMessage = currentTime - lastMessageTime;

        if (timeSinceLastMessage >= botMessageCooldown) {
          await message.delete();
          const warningMessage = await message.channel.send(
            `${message.author}, posting invite links is not allowed!`,
          );
          setTimeout(() => warningMessage.delete().catch(console.error), 5000);

          // Update last warning time and last bot message sent time
          lastInviteWarning[message.author.id] = currentTime;
          lastBotMessageSent[channelId] = currentTime;
        } else {
          console.log('Bot message suppressed due to cooldown.');
        }
      } catch (error) {
        console.error('Error handling message:', error);
      }
    }
  }

  const urlRegex = /(https?:\/\/[^\s]+)/g;
  const urls = message.content.match(urlRegex);

  if (urls) {
    for (const url of urls) {
      try {
        const encodedUrl = Buffer.from(url)
          .toString('base64')
          .replace(/\+/g, '-')
          .replace(/\//g, '_')
          .replace(/=+$/, '');
        console.log('Original URL:', url);
        console.log('Encoded URL:', encodedUrl);

        const response = await axios.get(
          `https://www.virustotal.com/api/v3/urls/${encodedUrl}`,
          {
            headers: { 'x-apikey': virusTotalApiKey },
          },
        );

        if (response.data.data.attributes.last_analysis_stats.malicious > 0) {
          await message.delete();
          await message.channel.send(
            `${message.author}, your message contained a malicious link and was deleted.`,
          );

          if (client.moderationWebhook) {
            const embed = new EmbedBuilder()
              .setAuthor({
                name: message.author.username,
                iconURL: message.author.displayAvatarURL(),
              })
              .setTitle('Malicious Link Detected!')
              .setColor('#FF0000')
              .addFields(
                {
                  name: 'User',
                  value: `${message.author.tag} (${message.author.id})`,
                  inline: true,
                },
                { name: 'Message', value: `\`\`\`${message.content}\`\`\`` },
                { name: 'Link', value: `\`\`\`${url}\`\`\`` },
                { name: 'Timestamp', value: new Date().toISOString() },
              )
              .setFooter({
                text: client.user.tag,
                iconURL: client.user.displayAvatarURL(),
              });
            await client.moderationWebhook.send({ embeds: [embed] });
          }

          break;
        }
      } catch (error) {
        if (
          error.response &&
          error.response.data.error.code === 'NotFoundError'
        ) {
          console.log('URL not found in VirusTotal database:', url);
        } else {
          console.error(
            'Error checking URL with VirusTotal API:',
            error.response ? error.response.data : error.message,
          );
        }
      }
    }
  }
});

const sharp = require('sharp');

client.on('guildMemberAdd', async (member) => {
  try {
    const guild = member.guild;

    // Welcome message in a specified channel
    const welcomeChannel = client.channels.cache.get(
      config.moderation.channels.welcomeChannelId,
    );

    if (!welcomeChannel) {
      throw new Error('Welcome channel not found.');
    }

    // Load the background image for the welcome message
    const imagePath = path.resolve(__dirname, '../images/red.png');
    console.log('Loading image from path:', imagePath);

    let background;
    try {
      background = await loadImage(imagePath);
      console.log('Image loaded successfully');
    } catch (error) {
      console.error('Error loading image:', error.message);
      return; // Exit the function if the image fails to load
    }

    const canvas = createCanvas(1280, 720);
    const ctx = canvas.getContext('2d');
    ctx.drawImage(background, 0, 0, canvas.width, canvas.height);

    // Fetch the user's avatar in PNG format
    const defaultAvatarUrl = 'https://cdn.discordapp.com/embed/avatars/0.png'; // Default Discord avatar
    const avatarUrl =
      member.user.displayAvatarURL({ format: 'png', size: 2048 }) ||
      defaultAvatarUrl;

    // Force PNG format by appending `.png` to the URL
    const pngAvatarUrl = avatarUrl.replace(/\.webp$/, '.png');
    console.log('Avatar URL:', pngAvatarUrl); // Log the modified avatar URL

    if (!pngAvatarUrl) {
      throw new Error('Failed to fetch user avatar URL.');
    }

    let avatarBuffer;
    try {
      const avatarResponse = await axios.get(pngAvatarUrl, {
        responseType: 'arraybuffer',
      });
      avatarBuffer = Buffer.from(avatarResponse.data);

      // Log the avatar buffer size
      console.log('Avatar buffer size:', avatarBuffer.length);

      // Verify the sharp instance
      if (!sharp) {
        throw new Error('Sharp is not initialized correctly.');
      }

      // Use sharp to process the avatar
      const processedAvatar = await sharp(avatarBuffer)
        .resize(345, 345) // Resize to fit the circle
        .toFormat('png') // Ensure output is in PNG format
        .toBuffer();

      // Log the processed avatar buffer size
      console.log('Processed avatar buffer size:', processedAvatar.length);

      // Load the processed avatar into canvas
      const avatar = await loadImage(processedAvatar);

      // Draw the avatar with stroke
      const circleCenterX = 640; // X center of the circle
      const circleCenterY = 485; // Y center of the circle
      const circleRadius = 150; // Radius of the circle
      const avatarSize = 345; // Size of the avatar

      // Draw the stroke
      ctx.save();
      ctx.lineWidth = 10; // Thickness of the stroke
      ctx.strokeStyle = config.colors.red; // Color of the stroke

      ctx.beginPath();
      ctx.arc(
        circleCenterX,
        circleCenterY,
        circleRadius + 5,
        0,
        Math.PI * 2,
        false,
      );
      ctx.closePath();
      ctx.stroke();

      ctx.restore();
      ctx.save();

      // Draw the circular clipping path for the avatar
      ctx.beginPath();
      ctx.arc(circleCenterX, circleCenterY, circleRadius, 0, Math.PI * 2, true);
      ctx.closePath();
      ctx.clip();

      // Center the avatar in the circle
      const avatarX = circleCenterX - avatarSize / 2;
      const avatarY = circleCenterY - avatarSize / 2;

      // Draw the avatar
      ctx.drawImage(avatar, avatarX, avatarY, avatarSize, avatarSize);
      ctx.restore();

      // Add text to the image
      ctx.font = 'bold 76px Arial'; // Use Arial instead of Mont
      ctx.fillStyle = 'red';
      ctx.textAlign = 'center';
      ctx.shadowColor = config.colors.red;
      ctx.shadowBlur = 8;

      ctx.fillText(
        `Welcome ${member.user.username || 'Guest'}`,
        canvas.width / 2,
        160,
      );
      ctx.font = 'bold 58px Arial'; // Use Arial instead of Mont
      ctx.fillText(`To ${guild.name || 'Server'}`, canvas.width / 2, 240);
      ctx.font = 'bold 38px Arial'; // Use Arial instead of Mont
      ctx.fillText(
        `You are member number ${guild.memberCount}`,
        canvas.width / 2,
        300,
      );

      // Convert canvas to buffer
      const buffer = canvas.toBuffer('image/png');
      if (!buffer || buffer.length === 0) {
        throw new Error('Failed to create buffer from canvas.');
      }

      // Log the buffer size
      console.log('Canvas buffer size:', buffer.length);

      // Save the buffer to a temporary file
      const tempFilePath = path.resolve(__dirname, `welcome-${member.id}.png`);
      fs.writeFileSync(tempFilePath, buffer);

      // Create an attachment using the temporary file
      const attachment = new AttachmentBuilder(tempFilePath);

      // Log the attachment details
      console.log('Attachment created:', attachment);

      // Send welcome message with the image
      await welcomeChannel.send({
        content: `** :wave: Hello ${member}, Welcome To ${guild.name}! **`,
        files: [attachment],
      });

      // Delete the temporary file after sending
      fs.unlinkSync(tempFilePath);

      // Optionally remove the new role (e.g., if it's a verification role)
      const newRoleId = '1260326684763099289'; // Update to the role you want to remove
      const roleToRemove = guild.roles.cache.get(newRoleId);
      if (roleToRemove) {
        await member.roles.remove(roleToRemove);
      }
    } catch (error) {
      console.error('Error processing avatar:', error);
      return; // Exit if the avatar fails to load
    }
  } catch (error) {
    console.error('Error handling guildMemberAdd event:', error);
  }
});

client.login(process.env.TOKEN);