← back to browse
goat command

update

No description

0
views
0
likes
0
installs

Aliases: gitupdate, selfupdate

raw source
const fs = require("fs-extra");
const path = require("path");
const {
  checkAndSelfUpdate,
  getPendingUpdate,
  applyPendingUpdate
} = require("../../includes/rX/autoUpdate.js");
const { startBackgroundUpdateChecker, CONFIRM_EMOJIS } = require("../../includes/rX/updateNotifier.js");

module.exports = {
  config: {
    name: "update",
    aliases: ["gitupdate", "selfupdate"],
    version: "2.0",
    author: "EryXenX",
    countDown: 10,
    role: 1,
    category: "owner",
    description: {
      vi: "Kiểm tra và tự động cập nhật bot từ GitHub repo (config.json > gitUpdate)",
      en: "Check config.json > gitUpdate.url on GitHub and self-update the bot if a newer version exists"
    },
    guide: {
      vi: "   {pn}: Áp dụng bản cập nhật đã tải sẵn (nếu có), hoặc kiểm tra + cập nhật ngay",
      en: "   {pn}: Applies an already-downloaded update if one is staged, otherwise checks for and applies the latest update right away"
    }
  },

  langs: {
    vi: {
      notAdmin: "🚫 | Chỉ admin bot mới có thể dùng lệnh này.",
      checking: "🔍 | Đang kiểm tra bản cập nhật...",
      applyingStaged: "📦 | Đã có bản cập nhật v%1 tải sẵn. Đang áp dụng...",
      upToDate: "✅ | Bot đã ở phiên bản mới nhất (v%1).",
      noRepo: "⚠ | Chưa cấu hình repo GitHub. Vui lòng đặt config.json > gitUpdate.url.",
      error: "💥 | Kiểm tra cập nhật thất bại:\n%1",
      reactionApplying: "📦 | Đã xác nhận! Đang áp dụng bản cập nhật v%1...",
      reactionNotAdmin: "🚫 | Chỉ admin bot mới có thể xác nhận cập nhật.",
    },
    en: {
      notAdmin: "🚫 | This command is restricted to bot admins only.",
      checking: "🔍 | Checking for updates...",
      applyingStaged: "📦 | Version v%1 was already downloaded in the background. Applying it now...",
      upToDate: "✅ | Bot is already up to date (v%1).",
      noRepo: "⚠ | No GitHub repo configured. Set config.json > gitUpdate.url first.",
      error: "💥 | Update check failed:\n%1",
      reactionApplying: "📦 | Confirmed! Applying update v%1 now...",
      reactionNotAdmin: "🚫 | Only bot admins can confirm an update.",
    }
  },

  onLoad({ api }) {
    const tmpDir = path.join(__dirname, "tmp");
    if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir);

    const pathFile = path.join(tmpDir, "update.txt");
    if (fs.existsSync(pathFile)) {
      setTimeout(() => {
        try {
          const [tid, , fromVersion, toVersion] = fs.readFileSync(pathFile, "utf-8").split(" ");
          api.sendMessage(`✅ | Update applied: v${fromVersion} → v${toVersion}.\n🔄 | Bot restarted successfully and is back online.`, tid);
          fs.unlinkSync(pathFile);
        } catch (e) {
          console.error("Update notify error:", e);
        }
      }, 2000);
    }

    // Kick off the silent background update checker. This is fire-and-forget
    // (setTimeout/setInterval only) so it never delays bot boot/login.
    startBackgroundUpdateChecker({ api });
  },

  onStart: async function ({ message, event, getLang }) {
    const admins = global.GoatBot?.config?.adminBot || [];
    if (!admins.includes(event.senderID)) return message.reply(getLang("notAdmin"));

    // Fast path: a version was already silently downloaded in the
    // background — apply it directly instead of re-downloading.
    const pending = getPendingUpdate(process.cwd());
    if (pending) {
      await message.reply(getLang("applyingStaged", pending.remoteVersion));
      const result = await applyPendingUpdate(process.cwd(), { notifyThreadID: event.threadID });
      // If applyPendingUpdate succeeded it already called process.exit(2);
      // this line only runs if something went wrong.
      if (!result?.applied) return message.reply(getLang("error", "Failed to apply the staged update."));
      return;
    }

    await message.reply(getLang("checking"));

    const result = await checkAndSelfUpdate(process.cwd(), {
      notifyThreadID: event.threadID
    });

    // If an update was found and applied, checkAndSelfUpdate already wrote
    // the restart marker and called process.exit(2) — this line is never
    // reached in that case, the onLoad hook above reports back instead.
    if (result?.error) return message.reply(getLang("error", result.error));
    if (result?.noRepo) return message.reply(getLang("noRepo"));

    return message.reply(getLang("upToDate", result?.localVersion || "?"));
  },

  // Fired when someone reacts to the "new version available" DM sent by the
  // background checker (includes/rX/updateNotifier.js). Reacting ✅ (or a
  // similar thumbs-up/confirm emoji) applies the already-staged update.
  onReaction: async function ({ message, event, Reaction, getLang }) {
    if (!Reaction || Reaction.type !== "confirmStagedUpdate") return;

    const admins = global.GoatBot?.config?.adminBot || [];
    if (!admins.includes(event.userID)) return message.reply(getLang("reactionNotAdmin"));

    if (!CONFIRM_EMOJIS.includes(event.reaction)) {
      // Any other reaction (e.g. ❌) is treated as "not now" — just drop it.
      global.GoatBot.onReaction?.delete(event.messageID);
      return;
    }

    global.GoatBot.onReaction?.delete(event.messageID);

    const pending = getPendingUpdate(process.cwd());
    if (!pending) return; // Nothing staged anymore (already applied or cleared)

    await message.reply(getLang("reactionApplying", pending.remoteVersion));
    await applyPendingUpdate(process.cwd(), { notifyThreadID: event.threadID });
  }
};

View raw file