Ganti Scraper

javascript 240 views May 3, 2026
Raw
javascript 314 lines ยท 9,103 chars
import fs from "fs";
import path from "path";
import { pathToFileURL } from "url";
import { hotReloadPlugin } from "../../src/lib/ourin-plugins.js";
import te from "../../src/lib/ourin-error.js";

const pluginConfig = {
  name: "gantiscraper",
  alias: ["replacescraper", "updatescraper", "gantiscrape"],
  category: "owner",
  description: "Ganti code scraper yang sudah ada di src/scraper",
  usage: ".gantiscraper [namafile]",
  example: ".gantiscraper ig",
  isOwner: true,
  isPremium: false,
  isGroup: false,
  isPrivate: false,
  cooldown: 5,
  energi: 0,
  isEnabled: true,
};

const SCRAPER_DIR = path.join(process.cwd(), "src", "scraper");
const PLUGINS_DIR = path.join(process.cwd(), "plugins");

function listScrapers() {
  if (!fs.existsSync(SCRAPER_DIR)) return [];
  return fs
    .readdirSync(SCRAPER_DIR)
    .filter((f) => f.endsWith(".js"))
    .map((f) => f.replace(".js", ""));
}

function findScraperFile(name) {
  const files = fs.readdirSync(SCRAPER_DIR).filter((f) => f.endsWith(".js"));

  for (const file of files) {
    if (file.replace(".js", "").toLowerCase() === name.toLowerCase()) {
      return { file, path: path.join(SCRAPER_DIR, file) };
    }
  }
  return null;
}

function walkJsFiles(dir) {
  if (!fs.existsSync(dir)) return [];

  const entries = fs.readdirSync(dir, { withFileTypes: true });
  const files = [];

  for (const entry of entries) {
    const entryPath = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      files.push(...walkJsFiles(entryPath));
      continue;
    }
    if (entry.isFile() && entry.name.endsWith(".js")) {
      files.push(entryPath);
    }
  }

  return files;
}

function escapeRegex(value) {
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function findDependentPluginFiles(scraperName) {
  const escapedName = escapeRegex(scraperName);
  const patterns = [
    new RegExp(`src[\\/]scraper[\\/]${escapedName}\\.js`, "i"),
    new RegExp(`scraper[\\/].*${escapedName}\\.js`, "i"),
  ];

  return walkJsFiles(PLUGINS_DIR).filter((filePath) => {
    const content = fs.readFileSync(filePath, "utf8");
    return patterns.some((pattern) => pattern.test(content));
  });
}

async function hotReloadScraperModule(filePath) {
  const fileUrl = pathToFileURL(path.resolve(filePath)).href;
  await import(`${fileUrl}?t=${Date.now()}`);
  return { success: true };
}

async function hotReloadDependentPlugins(scraperName) {
  const dependentFiles = findDependentPluginFiles(scraperName);
  const results = [];

  for (const dependentFile of dependentFiles) {
    const reloadResult = await hotReloadPlugin(dependentFile);
    results.push({
      file: path.relative(process.cwd(), dependentFile),
      success: !!reloadResult?.success,
      error: reloadResult?.error || null,
    });
  }

  return results;
}

async function handler(m, { sock }) {
  const quoted = m.quoted;
  const args = m.args;

  if (args[0]?.toLowerCase() === "list") {
    const scrapers = listScrapers();
    if (!scrapers.length) {
      return m.reply(`๐Ÿ“‚ Folder src/scraper kosong`);
    }

    let text = `๐Ÿ“‚ *DAFTAR SCRAPER*\n\n` + `โ•ญโ”€ใ€” *src/scraper* ใ€•โ”€โ”€โ”€โฌฃ\n`;

    scrapers.forEach((s, i) => {
      const stat = fs.statSync(path.join(SCRAPER_DIR, `${s}.js`));
      text += `โ”‚ ${i + 1}. \`${s}.js\` (${(stat.size / 1024).toFixed(1)} KB)\n`;
    });

    text +=
      `โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โฌก\n\n` +
      `Total: ${scrapers.length} scraper\n\n` +
      `> Gunakan \`${m.prefix}gantiscraper <nama>\` dengan reply code`;

    return m.reply(text);
  }

  if (!quoted) {
    return m.reply(
      `๐Ÿ”„ *GANTI SCRAPER*\n\n` +
        `Reply code scraper baru dengan caption:\n` +
        `\`${m.prefix}gantiscraper\` - Auto detect dari export\n` +
        `\`${m.prefix}gantiscraper namafile\` - Custom nama file\n\n` +
        `๐Ÿ“‹ *Lihat daftar scraper:*\n` +
        `\`${m.prefix}gantiscraper list\`\n\n` +
        `โš ๏ธ *PERINGATAN:*\nCode lama akan di-backup sebelum diganti`,
    );
  }

  let code = quoted.text || quoted.body || "";

  if (
    quoted.mimetype === "application/javascript" ||
    quoted.filename?.endsWith(".js")
  ) {
    try {
      code = (await quoted.download()).toString();
    } catch (e) {
      return m.reply(`โŒ *GAGAL*\n\nGagal download file`);
    }
  }

  if (!code || code.length < 30) {
    return m.reply(`โŒ *GAGAL*\n\nCode terlalu pendek atau tidak valid`);
  }

  const hasExport =
    code.includes("module.exports") ||
    code.includes("export ") ||
    code.includes("export default") ||
    code.includes("export {");

  if (!hasExport) {
    return m.reply(
      `โŒ *GAGAL*\n\nCode bukan format scraper yang valid\nHarus ada export`,
    );
  }

  let fileName = args[0];

  if (!fileName) {
    const defaultMatch = code.match(
      /export\s+default\s+(?:async\s+)?function\s+(\w+)|export\s+default\s+(\w+)/,
    );
    if (defaultMatch) {
      fileName = defaultMatch[1] || defaultMatch[2];
    } else {
      const namedExport = code.match(
        /export\s+(?:async\s+)?function\s+(\w+)|export\s+const\s+(\w+)\s*=/,
      );
      if (namedExport) {
        fileName = namedExport[1] || namedExport[2];
      }
    }
  }

  if (!fileName) {
    return m.reply(
      `โŒ *GAGAL*\n\nTidak bisa mendeteksi nama scraper\nGunakan \`${m.prefix}gantiscraper <namafile>\``,
    );
  }

  fileName = fileName.toLowerCase().replace(/[^a-z0-9\-_]/g, "");

  if (!fileName) {
    return m.reply(`โŒ *GAGAL*\n\nNama file tidak valid`);
  }

  await m.react("๐Ÿ••");

  try {
    if (!fs.existsSync(SCRAPER_DIR)) {
      fs.mkdirSync(SCRAPER_DIR, { recursive: true });
    }

    const existing = findScraperFile(fileName);
    let filePath = path.join(SCRAPER_DIR, `${fileName}.js`);
    let isNewFile = !existing;
    let backupPath = null;
    let oldSize = 0;

    if (existing) {
      filePath = existing.path;
      oldSize = fs.statSync(filePath).size;

      const backupDir = path.join(process.cwd(), "backup", "scraper");
      if (!fs.existsSync(backupDir)) {
        fs.mkdirSync(backupDir, { recursive: true });
      }

      const timestamp = new Date()
        .toISOString()
        .replace(/[:.]/g, "-")
        .slice(0, 19);
      backupPath = path.join(backupDir, `${fileName}_${timestamp}.js`);
      fs.copyFileSync(filePath, backupPath);
    }

    fs.writeFileSync(filePath, code);

    let scraperReload = { success: false };
    let pluginReloads = [];

    try {
      scraperReload = await hotReloadScraperModule(filePath);
      pluginReloads = await hotReloadDependentPlugins(fileName);
    } catch (reloadError) {
      if (backupPath && fs.existsSync(backupPath)) {
        fs.copyFileSync(backupPath, filePath);
      } else if (isNewFile && fs.existsSync(filePath)) {
        fs.unlinkSync(filePath);
      }
      throw reloadError;
    }

    await m.react("โœ…");

    let replyText =
      `โœ… *SCRAPER ${isNewFile ? "DITAMBAH" : "DIGANTI"}*\n\n` +
      `โ•ญโ”€ใ€” *DETAIL* ใ€•โ”€โ”€โ”€โฌก\n` +
      `โ”‚ File: \`${fileName}.js\`\n` +
      `โ”‚ Folder: \`src/scraper\`\n` +
      `โ”‚ Size: \`${code.length} bytes\`\n`;

    if (!isNewFile) {
      replyText += `โ”‚ Old Size: \`${oldSize} bytes\`\n`;
    }

    replyText += `โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โฌก\n\n`;

    if (backupPath) {
      const relBackup = path.relative(process.cwd(), backupPath);
      replyText += `๐Ÿ’พ *Backup:*\n\`${relBackup}\`\n\n`;
    }

    const reloadSuccess = pluginReloads.filter((item) => item.success);
    const reloadFailed = pluginReloads.filter((item) => !item.success);

    replyText +=
      `๐Ÿ”„ *Hot Reload:*\n` +
      `- Scraper: ${scraperReload.success ? "โœ… Sukses" : "โš ๏ธ Pending"}\n` +
      `- Plugin Terdeteksi: ${pluginReloads.length}\n` +
      `- Plugin Berhasil Reload: ${reloadSuccess.length}\n` +
      `- Plugin Gagal Reload: ${reloadFailed.length}\n\n`;

    if (reloadSuccess.length) {
      replyText += `โœ… *Plugin Reloaded:*\n`;
      replyText += reloadSuccess
        .slice(0, 10)
        .map((item) => `- \`${item.file}\``)
        .join("\n");
      replyText += `\n\n`;
    }

    if (reloadFailed.length) {
      replyText += `โš ๏ธ *Plugin Gagal Reload:*\n`;
      replyText += reloadFailed
        .slice(0, 10)
        .map(
          (item) => `- \`${item.file}\`${item.error ? ` (${item.error})` : ""}`,
        )
        .join("\n");
      replyText += `\n\n`;
    }

    if (!pluginReloads.length) {
      replyText += `โ„น๏ธ Tidak ada plugin yang langsung mengimpor scraper ini.\n\n`;
    }

    if (reloadFailed.length > 0) {
      replyText += `โš ๏ธ Sebagian reload gagal, restart bot mungkin diperlukan`;
    } else {
      replyText += `Scraper sudah aktif dan siap digunakan!`;
    }

    return m.reply(replyText);
  } catch (error) {
    await m.react("โ˜ข");
    await m.reply(te(m.prefix, m.command, m.pushName));
  }
}

export { pluginConfig as config, handler };