← back to browse
goat command

theme

Create and apply AI themes for chat group with image previews

0
views
0
likes
0
installs

Aliases: aitheme, changetheme

raw source
const { getStreamFromURL } = global.utils;

module.exports = {
  config: {
    name: "theme",
    aliases: ["aitheme", "changetheme"],
    version: "2.1",
    author: "Neoaz ゐ",
    countDown: 5,
    role: 1,
    description: "Create and apply AI themes for chat group with image previews",
    category: "box chat",
    guide: "{pn}: View current theme\n{pn} list: List available themes\n{pn} <description>: Create AI theme\n{pn} apply <ID>: Apply theme by ID"
  },

  langs: {
    en: {
      missingPrompt: "Enter a description or theme ID\nExamples:\n• ocean sunset\n• apply 739785333579430",
      generating: "Generating...",
      preview: "Generated %1 theme(s)! %2\n\n%3\nReply with number 1-%1",
      themeInfo: "%1. ID: %2\nColor: %3",
      applying: "Applying...",
      applied: "Applied!",
      error: "Error: %1",
      applyError: "Apply error: %1",
      noThemes: "No themes found",
      invalidSelection: "Choose 1-%1",
      notAuthor: "Only requester can select",
      missingThemeId: "Enter theme ID\nExample: apply 739785333579430",
      applyingById: "Applying %1...",
      appliedById: "Applied %1!",
      currentTheme: "Current: ID %1, Color %2\nUse apply <ID> to change",
      fetchingCurrent: "Fetching...",
      noCurrentTheme: "Using default theme",
      showingPreviews: "Previews:",
      previousTheme: "Previous: ID %1, Color %2",
      listingThemes: "Available themes:\n\n%1",
      themeListItem: "%1. ID: %2\nName: %3\nColor: %4\n"
    }
  },

  onStart: async function ({ args, message, event, api, getLang, commandName }) {
    const command = args[0];

    if (command === "id") {
      try {
        const threadInfo = await api.getThreadInfo(event.threadID);
        const themeId = threadInfo?.threadTheme?.id || threadInfo?.color || "Unknown";
        return message.reply(`Current Theme ID: ${themeId}`);
      } catch (error) {
        return message.reply(getLang("error", error.message || error));
      }
    }

    if (command === "apply" || command === "set") {
      const themeId = args[1];
      if (!themeId) return message.reply(getLang("missingThemeId"));
      try {
        message.reply(getLang("applyingById", themeId));
        await api.changeThreadColor(themeId, event.threadID);
        return message.reply(getLang("appliedById", themeId));
      } catch (error) {
        return message.reply(getLang("applyError", error.message || error));
      }
    }

    if (command === "list") {
      try {
        message.reply(getLang("fetchingCurrent"));
        const themes = await api.getTheme(event.threadID);
        if (!themes || themes.length === 0) return message.reply("No themes available.");

        let themeList = "";
        let counter = 1;

        for (const t of themes) {
          try {
            const themeInfo = await api.getThemeInfo(t.id);
            const name = themeInfo?.accessibility_label || themeInfo?.name || "Unknown";
            const color = themeInfo?.primary_color || "Unknown";
            themeList += getLang("themeListItem", counter++, t.id, name, color);

            if (themeInfo?.alternative_themes?.length > 0) {
              for (const alt of themeInfo.alternative_themes) {
                themeList += getLang("themeListItem", counter++, alt.id, alt.accessibility_label || "Variant", alt.primary_color || "Unknown");
              }
            }
          } catch (err) {
            themeList += `${counter++}. ID: ${t.id}\nName: Unknown\nColor: Unknown\n`;
          }
        }
        return message.reply(getLang("listingThemes", themeList.trim()));
      } catch (error) {
        return message.reply(getLang("error", error.message || error));
      }
    }

    const prompt = args.join(" ");
    if (!prompt) {
      try {
        message.reply(getLang("fetchingCurrent"));
        const threadInfo = await api.getThreadInfo(event.threadID);
        const theme = threadInfo.threadTheme;
        if (!theme) return message.reply(getLang("noCurrentTheme"));

        const themeId = theme.id || theme.theme_fbid || "Unknown";
        let colorInfo = threadInfo.color || theme.accessibility_label || "Unknown";
        const attachments = [];

        const extractUrl = (obj) => obj?.uri || obj?.url || (typeof obj === 'string' ? obj : null);

        try {
          const currentThemeData = await api.getThemeInfo(themeId);
          if (currentThemeData) {
            if (currentThemeData.name) colorInfo = currentThemeData.name;
            const bgUrl = extractUrl(currentThemeData.backgroundImage);
            if (bgUrl) {
              const stream = await getStreamFromURL(bgUrl, "current_theme.png");
              if (stream) attachments.push(stream);
            }
          }
        } catch (err) {}

        const body = attachments.length > 0 ? `${getLang("currentTheme", themeId, colorInfo)}\n\n${getLang("showingPreviews")}` : getLang("currentTheme", themeId, colorInfo);
        return message.reply({ body, attachment: attachments.length > 0 ? attachments : undefined });
      } catch (error) {
        return message.reply(getLang("error", error.message || error));
      }
    }

    try {
      message.reply(getLang("generating"));
      const themes = await api.createAITheme(prompt, 5);
      if (!themes || themes.length === 0) return message.reply(getLang("noThemes"));

      let themeList = "";
      const attachments = [];
      const extractUrl = (obj) => obj?.uri || obj?.url || (typeof obj === 'string' ? obj : null);

      for (let i = 0; i < themes.length; i++) {
        const theme = themes[i];
        const colorInfo = theme.accessibility_label || theme.gradient_colors?.join(" → ") || theme.primary_color || "AI Generated";
        themeList += getLang("themeInfo", i + 1, theme.id, colorInfo) + "\n\n";

        let imageUrls = [];
        if (theme.preview_image_urls) {
          const light = extractUrl(theme.preview_image_urls.light_mode);
          const dark = extractUrl(theme.preview_image_urls.dark_mode);
          if (light) imageUrls.push({ url: light, name: `theme_${i + 1}_l.png` });
          if (dark && dark !== light) imageUrls.push({ url: dark, name: `theme_${i + 1}_d.png` });
        }

        if (imageUrls.length === 0) {
          const bg = extractUrl(theme.background_asset?.image);
          if (bg) imageUrls.push({ url: bg, name: `theme_${i + 1}_bg.png` });
        }

        for (const img of imageUrls) {
          try {
            const stream = await getStreamFromURL(img.url, img.name);
            if (stream) attachments.push(stream);
          } catch (err) {}
        }
      }

      const replyBody = getLang("preview", themes.length, prompt, themeList.trim());
      message.reply({ body: replyBody, attachment: attachments.length > 0 ? attachments : undefined }, (err, info) => {
        if (err) {
          message.reply(replyBody, (rErr, rInfo) => {
            if (rErr) return;
            global.GoatBot.onReply.set(rInfo.messageID, { commandName, author: event.senderID, themes });
          });
        } else {
          global.GoatBot.onReply.set(info.messageID, { commandName, author: event.senderID, themes });
        }
      });
    } catch (error) {
      message.reply(getLang("error", error.message || JSON.stringify(error)));
    }
  },

  onReply: async function ({ message, Reply, event, api, getLang }) {
    const { author, themes, messageID } = Reply;
    if (event.senderID !== author) return message.reply(getLang("notAuthor"));

    const selection = parseInt(event.body.trim());
    if (isNaN(selection) || selection < 1 || selection > themes.length) {
      return message.reply(getLang("invalidSelection", themes.length));
    }

    const selectedTheme = themes[selection - 1];
    try {
      const threadInfo = await api.getThreadInfo(event.threadID);
      const current = threadInfo.threadTheme;
      const cId = current?.id || current?.theme_fbid || "Default";
      const cCol = threadInfo.color || current?.accessibility_label || "Default";

      message.reply(getLang("applying"));
      await api.changeThreadColor(selectedTheme.id, event.threadID);
      message.reply(`${getLang("applied")}\n\n${getLang("previousTheme", cId, cCol)}`);
      api.unsendMessage(messageID);
    } catch (error) {
      message.reply(getLang("applyError", error.message || error));
    }
  }
};

View raw file