← back to browse
goat command

setting

Bot settings

0
views
0
likes
0
installs

Aliases: settings

raw source
const fs = require("fs");
const path = require("path");

function getPath(obj, keys) {
	return keys.reduce((o, k) => (o == null ? o : o[k]), obj);
}

function setPath(obj, keys, value) {
	let cur = obj;
	for (let i = 0; i < keys.length - 1; i++) {
		cur[keys[i]] = cur[keys[i]] || {};
		cur = cur[keys[i]];
	}
	cur[keys[keys.length - 1]] = value;
}

function status(val) {
	return val ? "ON ✦" : "OFF β—Œ";
}

function wrapItem(text, maxWidth) {
	const words = text.split(" ");
	const lines = [];
	let cur = "";
	for (const w of words) {
		const test = cur ? `${cur} ${w}` : w;
		if (test.length > maxWidth && cur) {
			lines.push(cur);
			cur = w;
		} else {
			cur = test;
		}
	}
	if (cur) lines.push(cur);
	return lines;
}

function renderBox(headerIcon, headerTitle, subtitleLines, boxLabel, itemLines, footer) {
	const lines = [`╭─ ${headerIcon} ${headerTitle.toUpperCase()}`];
	subtitleLines.forEach(l => lines.push(`β”œ ${l}`));
	lines.push("");
	lines.push(`β”Œβ”€ ${boxLabel.toUpperCase()} ─┐`);
	itemLines.forEach(item => {
		wrapItem(item, 22).forEach(w => lines.push(`β”‚ ${w}`));
	});
	lines.push("β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜");
	lines.push("");
	lines.push(`╰─ ${footer}`);
	return lines.join("\n");
}

function masked(val) {
	if (val === undefined || val === null || val === "" || val === " ") return "Not set βœ•";
	return "Set ✦ (" + String(val).length + " chars)";
}

const BOOL_GROUPS = {
	hideNoti: {
		title: "πŸ”• Hide Notification Messages",
		path: ["hideNotiMessage"],
		items: [
			["commandNotFound", "Command Not Found"],
			["adminOnly", "Admin Only"],
			["threadBanned", "Thread Banned"],
			["userBanned", "User Banned"],
			["needRoleToUseCmd", "Need Role To Use Cmd"],
			["needRoleToUseCmdOnReply", "…On Reply"],
			["needRoleToUseCmdOnReaction", "…On Reaction"]
		]
	},
	logEvents: {
		title: "πŸ“ Log Events",
		path: ["logEvents"],
		items: [
			["disableAll", "Disable All"],
			["message", "Message"],
			["message_reaction", "Message Reaction"],
			["message_unsend", "Message Unsend"],
			["message_reply", "Message Reply"],
			["event", "Event"],
			["read_receipt", "Read Receipt"],
			["typ", "Typing"],
			["presence", "Presence"]
		]
	},
	fcaOptions: {
		title: "🧩 FCA Options",
		path: ["optionsFca"],
		items: [
			["forceLogin", "Force Login"],
			["listenEvents", "Listen Events"],
			["updatePresence", "Update Presence"],
			["listenTyping", "Listen Typing"],
			["selfListen", "Self Listen"],
			["selfListenEvent", "Self Listen Event"],
			["autoMarkDelivery", "Auto Mark Delivery"],
			["autoReconnect", "Auto Reconnect"]
		],
		afterToggle: (api, key, val) => {
			try { api.setOptions({ [key]: val }); } catch (e) { }
		}
	},
	notiMqttGmail: {
		title: "πŸ“§ MQTT Error Notify β€” Gmail",
		path: ["notiWhenListenMqttError", "gmail"],
		items: [["enable", "Enable"]]
	},
	notiMqttTelegram: {
		title: "πŸ“¨ MQTT Error Notify β€” Telegram",
		path: ["notiWhenListenMqttError", "telegram"],
		items: [["enable", "Enable"]]
	},
	notiMqttDiscord: {
		title: "πŸ“’ MQTT Error Notify β€” Discord",
		path: ["notiWhenListenMqttError", "discordHook"],
		items: [["enable", "Enable"]]
	}
};

function renderBoolGroupMenu(config, groupKey) {
	const group = BOOL_GROUPS[groupKey];
	const sub = getPath(config, group.path) || {};
	const itemLines = group.items.map(([key, label], i) => `${i + 1}. ${label} β€” ${status(sub[key])}`);
	return renderBox("βš™οΈ", group.title.replace(/^[^\w]*/, "").trim(), [`Items : ${group.items.length}`], "toggles", itemLines, "Reply a number to toggle, or 0 to go back");
}

const CATEGORIES = [
	{
		title: "πŸ” Account & Security",
		items: [
			{ num: 1, label: "Facebook Account" },
			{ num: 17, label: "Credentials (Gmail / reCAPTCHA)" }
		]
	},
	{
		title: "πŸ€– Bot Behavior",
		items: [
			{ num: 2, label: "Bot Behavior" },
			{ num: 5, label: "No Prefix" },
			{ num: 7, label: "Nickname" },
			{ num: 9, label: "Typing Indicator" }
		]
	},
	{
		title: "πŸ‘₯ Access Control",
		items: [
			{ num: 3, label: "Admin Manage" },
			{ num: 4, label: "Whitelist Manage" },
			{ num: 6, label: "React Unsend" }
		]
	},
	{
		title: "🧩 Connection (FCA / MQTT)",
		items: [
			{ num: 8, label: "FCA Options" },
			{ num: 13, label: "Restart Listen MQTT" },
			{ num: 14, label: "MQTT Error Notify" },
			{ num: 18, label: "E2EE" }
		]
	},
	{
		title: "πŸ—„οΈ Database & Storage",
		items: [
			{ num: 10, label: "Database" }
		]
	},
	{
		title: "πŸ“Š Dashboard & Monitoring",
		items: [
			{ num: 11, label: "Dashboard & Uptime" },
			{ num: 12, label: "Auto Load Scripts" }
		]
	},
	{
		title: "πŸ“ Logging",
		items: [
			{ num: 15, label: "Hide Notification Messages" },
			{ num: 16, label: "Log Events" }
		]
	}
];

function renderCategoryList() {
	const itemLines = CATEGORIES.map((cat, i) => `${i + 1}. ${cat.title.replace(/^[^\w]*/, "").trim()}`);
	return renderBox("βš™οΈ", "Bot Settings", [`Categories : ${CATEGORIES.length}`, "Author : π“†©π‘…π‘œπ’·π’Ύπ“ƒπ“†ͺ π’œπ“π’Ύ"], "categories", itemLines, `Reply 1-${CATEGORIES.length} to enter a category`);
}

function renderCategoryItems(catIndex) {
	const cat = CATEGORIES[catIndex];
	const itemLines = cat.items.map((item, i) => `${i + 1}. ${item.label}`);
	return renderBox("🧩", cat.title.replace(/^[^\w]*/, "").trim(), [`Items : ${cat.items.length}`], "items", itemLines, "Reply a number, or 0 to go back to categories");
}

module.exports = {
	config: {
		name: "setting",
		aliases: ["settings"],
		version: "8.0.0",
		author: "π“†©π‘…π‘œπ’·π’Ύπ“ƒπ“†ͺ π’œπ“π’Ύ",
		countDown: 5,
		role: 2,
		shortDescription: "Bot settings",
		longDescription: "Control every bot setting from chat, organized by category",
		category: "admin",
		guide: "{prefix}setting"
	},

	onStart: async function ({ event, message }) {
		const sent = await message.reply(renderCategoryList());
		global.GoatBot.onReply.set(sent.messageID, {
			commandName: "setting",
			messageID: sent.messageID,
			author: event.senderID,
			state: "categories"
		});
	},

	onReply: async function ({ api, event, Reply, message }) {
		const { author, state } = Reply;
		if (event.senderID !== author) return;

		const configPath = path.join(process.cwd(), "config.json");
		const config = JSON.parse(fs.readFileSync(configPath, "utf8"));
		const input = event.body.trim();
		let num = parseInt(input);

		function saveConfig() {
			fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
		}

		async function sendAndListen(text, newState, extra = {}) {
			const sent = await message.reply(text);
			global.GoatBot.onReply.set(sent.messageID, {
				commandName: "setting",
				messageID: sent.messageID,
				author,
				state: newState,
				...extra
			});
		}

		async function backToCategories() {
			return await sendAndListen(renderCategoryList(), "categories");
		}

		if (state === "categories") {
			const cat = CATEGORIES[num - 1];
			if (!cat) return message.reply(`𝗫 Invalid selection. Reply 1-${CATEGORIES.length}.`);
			return await sendAndListen(renderCategoryItems(num - 1), "categoryItems", { catIndex: num - 1 });
		}

		if (state === "categoryItems") {
			if (num === 0) return backToCategories();
			const cat = CATEGORIES[Reply.catIndex];
			const item = cat && cat.items[num - 1];
			if (!item) return message.reply("𝗫 Invalid selection.");
			num = item.num; // fall through to the existing item logic below
		}

		if (BOOL_GROUPS[state]) {
			const group = BOOL_GROUPS[state];
			if (num === 0) return backToCategories();
			const item = group.items[num - 1];
			if (!item) return message.reply("𝗫 Invalid selection. Reply a number from the list, or 0 to go back.");
			const [key] = item;
			const sub = getPath(config, group.path) || {};
			sub[key] = !sub[key];
			setPath(config, group.path, sub);
			saveConfig();
			if (group.afterToggle) group.afterToggle(api, key, sub[key]);
			return await sendAndListen(renderBoolGroupMenu(config, state), state);
		}

		if (state === "categoryItems" || state === "itemMenu") {
			if (num === 1) {
				const fb = config.facebookAccount || {};
				const menu = renderBox("πŸ‘€", "Facebook Account", [], "fields", [
					`1. ${"Email"} β€” ${fb.email || "Not set βœ•"}`,
					`2. ${"Password"} β€” ${masked(fb.password)}`,
					`3. ${"2FA Secret"} β€” ${masked(fb["2FASecret"])}`,
					`4. ${"i_user"} β€” ${masked(fb.i_user)}`,
					`5. ${"Proxy"} β€” ${fb.proxy || "Not set βœ•"}`,
					`6. ${"User Agent"} β€” ${fb.userAgent ? "Set ✦" : "Not set βœ•"}`,
					`7. ${"Cookie Refresh Interval"} β€” ${fb.intervalGetNewCookie ?? "Not set"} min`
				], "Reply 1-7 · ⚠️ needs a bot restart to take effect");
				return await sendAndListen(menu, "fbAccount");
			}
			if (num === 2) {
				const menu = renderBox("πŸ€–", "Bot Behavior", [], "fields", [
					`1. ${"Prefix"} β€” ${config.prefix || "/"}`,
					`2. ${"Language"} β€” ${config.language || "en"}`,
					`3. ${"Time Zone"} β€” ${config.timeZone || "Not set"}`,
					`4. ${"Anti Inbox"} β€” ${status(config.antiInbox)}`,
					`5. ${"Admin Only"} β€” ${status(config.adminOnly?.enable)}`,
					`6. ${"Only Admin Box"} β€” ${status(config.onlyAdminBox)}`,
					`7. ${"Auto Refresh Fbstate"} β€” ${status(config.autoRefreshFbstate)}`,
					`8. ${"Auto Relogin On Account Change"} β€” ${status(config.autoReloginWhenChangeAccount)}`,
					`9. ${"Auto Restart On MQTT Error"} β€” ${status(config.autoRestartWhenListenMqttError)}`,
					`10. ${"Auto Restart Time (cron/ms)"} β€” ${config.autoRestart?.time ?? "Not set"}`
				], "Reply 1-10");
				return await sendAndListen(menu, "botBehavior");
			}
			if (num === 3) {
				const menu = renderBox("βš™οΈ", "Admin Manage", [], "actions", [
					`1. ${"Add Admin"}`,
					`2. ${"Remove Admin"}`,
					`3. ${"List Admins"}`
				], "Reply 1-3");
				return await sendAndListen(menu, "adminManage");
			}
			if (num === 4) {
				const menu = renderBox("βš™οΈ", "Whitelist Manage", [], "actions", [
					`1. ${"Thread Whitelist"} β€” ${status(config.whiteListModeThread?.enable)}`,
					`2. ${"Add Thread"}`,
					`3. ${"Remove Thread"}`,
					`4. ${"User Whitelist"} β€” ${status(config.whiteListMode?.enable)}`,
					`5. ${"Add User"}`,
					`6. ${"Remove User"}`
				], "Reply 1-6");
				return await sendAndListen(menu, "whitelist");
			}
			if (num === 5) {
				config.noPrefix = config.noPrefix || {};
				config.noPrefix.enable = !config.noPrefix.enable;
				saveConfig();
				return message.reply(`✦ No Prefix is now ${status(config.noPrefix.enable)}`);
			}
			if (num === 6) {
				const menu = renderBox("βš™οΈ", "React Unsend", [], "fields", [
					`1. ${"Toggle"} β€” ${status(config.reactUnsend?.enable)}`,
					`2. ${"Only Admin"} β€” ${status(config.reactUnsend?.onlyAdmin)}`,
					`3. ${"Add Emoji"}`,
					`4. ${"Remove Emoji"}`,
					`5. ${"List Emojis"}`
				], "Reply 1-5");
				return await sendAndListen(menu, "reactUnsend");
			}
			if (num === 7) {
				const current = config.nickNameBot || "Not set";
				const menu = renderBox("βš™οΈ", "Nickname", [`Current : ${current}`], "actions", [
					`1. ${"Set Nickname (this group)"}`,
					`2. ${"Set Nickname (all groups)"}`,
					`3. ${"Reset Nickname"}`
				], "Reply 1-3");
				return await sendAndListen(menu, "nickname");
			}
			if (num === 8) return await sendAndListen(renderBoolGroupMenu(config, "fcaOptions"), "fcaOptions");
			if (num === 9) {
				const menu = renderBox("βš™οΈ", "Typing Indicator", [], "fields", [
					`1. ${"Toggle"} β€” ${status(config.enableTypingIndicator)}`,
					`2. ${"Duration"} β€” ${config.typingDuration || 2000}ms`
				], "Reply 1-2");
				return await sendAndListen(menu, "typingIndicator");
			}
			if (num === 10) {
				const db = config.database || {};
				const menu = renderBox("πŸ—„οΈ", "Database", [], "fields", [
					`1. ${"Type"} β€” ${db.type || "Not set"} (needs restart to change)`,
					`2. ${"MongoDB URI"} β€” ${masked(db.uriMongodb)}`,
					`3. ${"Auto Sync When Start"} β€” ${status(db.autoSyncWhenStart)}`,
					`4. ${"Auto Refresh Thread Info (first time)"} β€” ${status(db.autoRefreshThreadInfoFirstTime)}`
				], "Reply 1-4");
				return await sendAndListen(menu, "database");
			}
			if (num === 11) {
				const dash = config.dashBoard || {};
				const up = config.serverUptime || {};
				const auto = config.autoUptime || {};
				const menu = renderBox("πŸ“Š", "Dashboard & Uptime", [], "fields", [
					`1. ${"Dashboard Enable"} β€” ${status(dash.enable)}`,
					`2. ${"Dashboard Port"} β€” ${dash.port ?? "Not set"}`,
					`3. ${"Dashboard Verify Code Expiry (ms)"} β€” ${dash.expireVerifyCode ?? "Not set"}`,
					`4. ${"Server Uptime Enable"} β€” ${status(up.enable)}`,
					`5. ${"Server Uptime Port"} β€” ${up.port ?? "Not set"}`,
					`6. ${"Server Uptime Socket Enable"} β€” ${status(up.socket?.enable)}`,
					`7. ${"Server Uptime Channel Name"} β€” ${up.socket?.channelName || "Not set"}`,
					`8. ${"Auto Uptime Enable"} β€” ${status(auto.enable)}`,
					`9. ${"Auto Uptime Interval (s)"} β€” ${auto.timeInterval ?? "Not set"}`,
					`10. ${"Auto Uptime URL"} β€” ${auto.url || "Not set"}`
				], "Reply 1-10");
				return await sendAndListen(menu, "dashboardUptime");
			}
			if (num === 12) {
				const a = config.autoLoadScripts || {};
				const menu = renderBox("πŸ“œ", "Auto Load Scripts", [], "fields", [
					`1. ${"Enable"} β€” ${status(a.enable)}`,
					`2. ${"Ignore Commands"} β€” ${a.ignoreCmds || "(none)"}`,
					`3. ${"Ignore Events"} β€” ${a.ignoreEvents || "(none)"}`
				], "Reply 1-3");
				return await sendAndListen(menu, "autoLoadScripts");
			}
			if (num === 13) {
				const r = config.restartListenMqtt || {};
				const menu = renderBox("πŸ”", "Restart Listen MQTT", [], "fields", [
					`1. ${"Enable"} β€” ${status(r.enable)}`,
					`2. ${"Restart Interval (ms)"} β€” ${r.timeRestart ?? "Not set"}`,
					`3. ${"Delay After Stop (ms)"} β€” ${r.delayAfterStopListening ?? "Not set"}`,
					`4. ${"Log Notification"} β€” ${status(r.logNoti)}`
				], "Reply 1-4");
				return await sendAndListen(menu, "restartListenMqtt");
			}
			if (num === 14) {
				const menu = renderBox("πŸ””", "MQTT Error Notify", [], "channels", [
					`1. ${"Gmail"}`,
					`2. ${"Telegram"}`,
					`3. ${"Discord"}`
				], "Reply 1-3");
				return await sendAndListen(menu, "notiMqttPick");
			}
			if (num === 15) return await sendAndListen(renderBoolGroupMenu(config, "hideNoti"), "hideNoti");
			if (num === 16) return await sendAndListen(renderBoolGroupMenu(config, "logEvents"), "logEvents");
			if (num === 17) {
				const g = config.credentials?.gmailAccount || {};
				const r = config.credentials?.gRecaptcha || {};
				const menu = renderBox("πŸ”‘", "Credentials", [], "fields", [
					`1. ${"Gmail Email"} β€” ${g.email || "Not set βœ•"}`,
					`2. ${"Gmail Client ID"} β€” ${masked(g.clientId)}`,
					`3. ${"Gmail Client Secret"} β€” ${masked(g.clientSecret)}`,
					`4. ${"Gmail Refresh Token"} β€” ${masked(g.refreshToken)}`,
					`5. ${"Gmail API Key"} β€” ${masked(g.apiKey)}`,
					`6. ${"reCAPTCHA Site Key"} β€” ${masked(r.siteKey)}`,
					`7. ${"reCAPTCHA Secret Key"} β€” ${masked(r.secretKey)}`
				], "Reply 1-7");
				return await sendAndListen(menu, "credentials");
			}
			if (num === 18) {
				config.e2ee = config.e2ee || {};
				const was = config.e2ee.enable;
				config.e2ee.enable = !was;
				saveConfig();
				const sent = await message.reply(`✦ E2EE is now ${status(config.e2ee.enable)}\n⚠️ Needs a bot restart to take effect.\nβ€Ί React to this message to restart now, or select E2EE again later to switch it back without restarting.`);
				global.GoatBot.onReaction.set(sent.messageID, {
					commandName: "setting",
					messageID: sent.messageID,
					type: "e2eeRestartConfirm",
					author
				});
				return;
			}
		}

		if (state === "fbAccount") {
			const fields = [
				["email", "Email", "text"],
				["password", "Password", "text"],
				["2FASecret", "2FA Secret", "text"],
				["i_user", "i_user", "text"],
				["proxy", "Proxy", "text"],
				["userAgent", "User Agent", "text"],
				["intervalGetNewCookie", "Cookie Refresh Interval (minutes)", "number"]
			];
			const field = fields[num - 1];
			if (!field) return message.reply("𝗫 Invalid selection.");
			const [key, label, type] = field;
			return await sendAndListen(`β€Ί Reply with new value for ${label}:`, "fbAccountSet", { fieldKey: key, fieldType: type });
		}
		if (state === "fbAccountSet") {
			const { fieldKey, fieldType } = Reply;
			let value = input;
			if (fieldType === "number") {
				if (isNaN(num)) return message.reply("𝗫 Invalid number.");
				value = num;
			}
			config.facebookAccount = config.facebookAccount || {};
			config.facebookAccount[fieldKey] = value;
			saveConfig();
			return message.reply(`✦ Updated. (Restart the bot for this to take effect.)`);
		}

		if (state === "botBehavior") {
			if (num === 1) return await sendAndListen("β€Ί Reply with new prefix:", "prefixSet");
			if (num === 2) return await sendAndListen("β€Ί Reply with new language code (e.g. en, bn):", "languageSet");
			if (num === 3) return await sendAndListen("β€Ί Reply with new time zone (e.g. Asia/Dhaka):", "timeZoneSet");
			if (num === 4) {
				config.antiInbox = !config.antiInbox;
				saveConfig();
				return message.reply(`✦ Anti Inbox β€” ${status(config.antiInbox)}`);
			}
			if (num === 5) {
				config.adminOnly = config.adminOnly || {};
				config.adminOnly.enable = !config.adminOnly.enable;
				saveConfig();
				return message.reply(`✦ Admin Only β€” ${status(config.adminOnly.enable)}`);
			}
			if (num === 6) {
				config.onlyAdminBox = !config.onlyAdminBox;
				saveConfig();
				return message.reply(`✦ Only Admin Box β€” ${status(config.onlyAdminBox)}`);
			}
			if (num === 7) {
				config.autoRefreshFbstate = !config.autoRefreshFbstate;
				saveConfig();
				return message.reply(`✦ Auto Refresh Fbstate β€” ${status(config.autoRefreshFbstate)}`);
			}
			if (num === 8) {
				config.autoReloginWhenChangeAccount = !config.autoReloginWhenChangeAccount;
				saveConfig();
				return message.reply(`✦ Auto Relogin On Account Change β€” ${status(config.autoReloginWhenChangeAccount)}`);
			}
			if (num === 9) {
				config.autoRestartWhenListenMqttError = !config.autoRestartWhenListenMqttError;
				saveConfig();
				return message.reply(`✦ Auto Restart On MQTT Error β€” ${status(config.autoRestartWhenListenMqttError)}`);
			}
			if (num === 10) return await sendAndListen("β€Ί Reply with new Auto Restart time (ms, or cron string, or 'null' to disable):", "autoRestartTimeSet");
		}
		if (state === "prefixSet") {
			if (!input) return message.reply("𝗫 Invalid prefix.");
			config.prefix = input;
			saveConfig();
			return message.reply(`✦ Prefix set to: ${input}`);
		}
		if (state === "languageSet") {
			if (!input) return message.reply("𝗫 Invalid language code.");
			config.language = input;
			saveConfig();
			return message.reply(`✦ Language set to: ${input}`);
		}
		if (state === "timeZoneSet") {
			if (!input) return message.reply("𝗫 Invalid time zone.");
			config.timeZone = input;
			saveConfig();
			return message.reply(`✦ Time Zone set to: ${input}`);
		}
		if (state === "autoRestartTimeSet") {
			config.autoRestart = config.autoRestart || {};
			config.autoRestart.time = input === "null" ? null : (isNaN(num) ? input : num);
			saveConfig();
			return message.reply(`✦ Auto Restart time set to: ${config.autoRestart.time}`);
		}

		if (state === "adminManage") {
			if (num === 1) return await sendAndListen("β€Ί Reply with UID or tag user to add as admin:", "adminAdd");
			if (num === 2) {
				const admins = config.adminBot || [];
				if (!admins.length) return message.reply("𝗫 No admins found.");
				const list = admins.map((id, i) => `${i + 1}. ${id}`);
				await message.reply(renderBox("βš™οΈ", "Remove Admin", [], "admins", list, "Reply number"));
				return await sendAndListen("β€Ί Waiting...", "adminRemoveSelect");
			}
			if (num === 3) {
				const admins = config.adminBot || [];
				if (!admins.length) return message.reply("𝗫 No admins found.");
				return message.reply(renderBox("βš™οΈ", "Admins", [], "list", admins.map((id, i) => `${i + 1}. ${id}`), "Reply 1 to go back to categories"));
			}
		}

		if (state === "adminAdd") {
			let uid = input;
			if (event.mentions && Object.keys(event.mentions).length > 0) uid = Object.keys(event.mentions)[0];
			if (!uid || isNaN(uid)) return message.reply("𝗫 Invalid UID.");
			config.adminBot = config.adminBot || [];
			if (config.adminBot.includes(uid)) return message.reply("𝗫 Already an admin.");
			config.adminBot.push(uid);
			saveConfig();
			return message.reply(`✦ Added ${uid} as admin.`);
		}

		if (state === "adminRemoveSelect") {
			const admins = config.adminBot || [];
			const idx = num - 1;
			if (isNaN(num) || !admins[idx]) return message.reply("𝗫 Invalid selection.");
			const removed = admins.splice(idx, 1)[0];
			config.adminBot = admins;
			saveConfig();
			return message.reply(`✦ Removed ${removed} from admins.`);
		}

		if (state === "whitelist") {
			if (num === 1) {
				config.whiteListModeThread = config.whiteListModeThread || {};
				config.whiteListModeThread.enable = !config.whiteListModeThread.enable;
				saveConfig();
				return message.reply(`✦ Thread Whitelist is now ${status(config.whiteListModeThread.enable)}`);
			}
			if (num === 2) return await sendAndListen("β€Ί Reply with Thread ID to add:", "threadAdd");
			if (num === 3) {
				const threads = config.whiteListModeThread?.whiteListThreadIds || [];
				if (!threads.length) return message.reply("𝗫 No threads found.");
				const list = threads.map((id, i) => `${i + 1}. ${id}`);
				await message.reply(renderBox("βš™οΈ", "Remove Thread", [], "threads", list, "Reply number"));
				return await sendAndListen("β€Ί Waiting...", "threadRemoveSelect");
			}
			if (num === 4) {
				config.whiteListMode = config.whiteListMode || {};
				config.whiteListMode.enable = !config.whiteListMode.enable;
				saveConfig();
				return message.reply(`✦ User Whitelist is now ${status(config.whiteListMode.enable)}`);
			}
			if (num === 5) return await sendAndListen("β€Ί Reply with UID or tag user to add:", "userAdd");
			if (num === 6) {
				const users = config.whiteListMode?.whiteListIds || [];
				if (!users.length) return message.reply("𝗫 No users found.");
				const list = users.map((id, i) => `${i + 1}. ${id}`);
				await message.reply(renderBox("βš™οΈ", "Remove User", [], "users", list, "Reply number"));
				return await sendAndListen("β€Ί Waiting...", "userRemoveSelect");
			}
		}

		if (state === "threadAdd") {
			const tid = input;
			if (!tid || isNaN(tid)) return message.reply("𝗫 Invalid Thread ID.");
			config.whiteListModeThread = config.whiteListModeThread || {};
			config.whiteListModeThread.whiteListThreadIds = config.whiteListModeThread.whiteListThreadIds || [];
			if (config.whiteListModeThread.whiteListThreadIds.includes(tid)) return message.reply("𝗫 Already in whitelist.");
			config.whiteListModeThread.whiteListThreadIds.push(tid);
			saveConfig();
			return message.reply(`✦ Thread ${tid} added.`);
		}

		if (state === "threadRemoveSelect") {
			const threads = config.whiteListModeThread?.whiteListThreadIds || [];
			const idx = num - 1;
			if (isNaN(num) || !threads[idx]) return message.reply("𝗫 Invalid selection.");
			const removed = threads.splice(idx, 1)[0];
			config.whiteListModeThread.whiteListThreadIds = threads;
			saveConfig();
			return message.reply(`✦ Thread ${removed} removed.`);
		}

		if (state === "userAdd") {
			let uid = input;
			if (event.mentions && Object.keys(event.mentions).length > 0) uid = Object.keys(event.mentions)[0];
			if (!uid || isNaN(uid)) return message.reply("𝗫 Invalid UID.");
			config.whiteListMode = config.whiteListMode || {};
			config.whiteListMode.whiteListIds = config.whiteListMode.whiteListIds || [];
			if (config.whiteListMode.whiteListIds.includes(uid)) return message.reply("𝗫 Already in whitelist.");
			config.whiteListMode.whiteListIds.push(uid);
			saveConfig();
			return message.reply(`✦ User ${uid} added.`);
		}

		if (state === "userRemoveSelect") {
			const users = config.whiteListMode?.whiteListIds || [];
			const idx = num - 1;
			if (isNaN(num) || !users[idx]) return message.reply("𝗫 Invalid selection.");
			const removed = users.splice(idx, 1)[0];
			config.whiteListMode.whiteListIds = users;
			saveConfig();
			return message.reply(`✦ User ${removed} removed.`);
		}

		if (state === "reactUnsend") {
			if (num === 1) {
				config.reactUnsend = config.reactUnsend || {};
				config.reactUnsend.enable = !config.reactUnsend.enable;
				saveConfig();
				return message.reply(`✦ React Unsend β€” ${status(config.reactUnsend.enable)}`);
			}
			if (num === 2) {
				config.reactUnsend = config.reactUnsend || {};
				config.reactUnsend.onlyAdmin = !config.reactUnsend.onlyAdmin;
				saveConfig();
				return message.reply(`✦ Only Admin β€” ${status(config.reactUnsend.onlyAdmin)}`);
			}
			if (num === 3) return await sendAndListen("β€Ί Reply with emoji to add:", "emojiAdd");
			if (num === 4) {
				const emojis = config.reactUnsend?.emojis || [];
				if (!emojis.length) return message.reply("𝗫 No emojis found.");
				const list = emojis.map((e, i) => `${i + 1}. ${e}`);
				await message.reply(renderBox("βš™οΈ", "Remove Emoji", [], "emojis", list, "Reply number"));
				return await sendAndListen("β€Ί Waiting...", "emojiRemoveSelect");
			}
			if (num === 5) {
				const emojis = config.reactUnsend?.emojis || [];
				if (!emojis.length) return message.reply("𝗫 No emojis.");
				return message.reply(renderBox("βš™οΈ", "Emojis", [], "list", [emojis.join("  ")], "Reply 1 to go back to categories"));
			}
		}

		if (state === "emojiAdd") {
			const emoji = input.trim();
			if (!emoji) return message.reply("𝗫 Invalid emoji.");
			config.reactUnsend = config.reactUnsend || {};
			config.reactUnsend.emojis = config.reactUnsend.emojis || [];
			if (config.reactUnsend.emojis.includes(emoji)) return message.reply("𝗫 Already added.");
			config.reactUnsend.emojis.push(emoji);
			saveConfig();
			return message.reply(`✦ Emoji ${emoji} added.`);
		}

		if (state === "emojiRemoveSelect") {
			const emojis = config.reactUnsend?.emojis || [];
			const idx = num - 1;
			if (isNaN(num) || !emojis[idx]) return message.reply("𝗫 Invalid selection.");
			const removed = emojis.splice(idx, 1)[0];
			config.reactUnsend.emojis = emojis;
			saveConfig();
			return message.reply(`✦ Emoji ${removed} removed.`);
		}

		if (state === "nickname") {
			if (num === 1) return await sendAndListen("β€Ί Reply with new nickname:", "nicknameSet");
			if (num === 2) return await sendAndListen("β€Ί Reply with new nickname (will set in all groups):", "nicknameSetAll");
			if (num === 3) {
				config.nickNameBot = "";
				saveConfig();
				try { await api.changeNickname("", event.threadID, api.getCurrentUserID()); } catch (e) { }
				return message.reply("✦ Nickname reset.");
			}
		}

		if (state === "nicknameSet") {
			const nickname = input;
			if (!nickname) return message.reply("𝗫 Invalid nickname.");
			config.nickNameBot = nickname;
			saveConfig();
			try { await api.changeNickname(nickname, event.threadID, api.getCurrentUserID()); } catch (e) { }
			return message.reply(`✦ Nickname set to: ${nickname}`);
		}

		if (state === "nicknameSetAll") {
			const nickname = input;
			if (!nickname) return message.reply("𝗫 Invalid nickname.");
			config.nickNameBot = nickname;
			saveConfig();
			const threads = await api.getThreadList(100, null, ["INBOX"]);
			let success = 0;
			for (const thread of threads) {
				if (!thread.isGroup) continue;
				try { await api.changeNickname(nickname, thread.threadID, api.getCurrentUserID()); success++; } catch (e) { }
			}
			return message.reply(`✦ Nickname set to: ${nickname}\nβ€Ί Updated in ${success} groups.`);
		}

		if (state === "typingIndicator") {
			if (num === 1) {
				config.enableTypingIndicator = !config.enableTypingIndicator;
				saveConfig();
				return message.reply(`✦ Typing Indicator β€” ${status(config.enableTypingIndicator)}`);
			}
			if (num === 2) return await sendAndListen("β€Ί Reply with duration in milliseconds (e.g. 2000 for 2s):", "typingDurationSet");
		}
		if (state === "typingDurationSet") {
			if (isNaN(num) || num < 500 || num > 15000) return message.reply("𝗫 Invalid duration. Use a value between 500 and 15000 ms.");
			config.typingDuration = num;
			saveConfig();
			return message.reply(`✦ Typing Duration set to: ${num}ms`);
		}

		if (state === "database") {
			if (num === 1) return message.reply("𝗫 Database type can't be changed live β€” edit config.json manually and restart the bot.");
			if (num === 2) return await sendAndListen("β€Ί Reply with new MongoDB URI:", "mongoUriSet");
			if (num === 3) {
				config.database = config.database || {};
				config.database.autoSyncWhenStart = !config.database.autoSyncWhenStart;
				saveConfig();
				return message.reply(`✦ Auto Sync When Start β€” ${status(config.database.autoSyncWhenStart)}`);
			}
			if (num === 4) {
				config.database = config.database || {};
				config.database.autoRefreshThreadInfoFirstTime = !config.database.autoRefreshThreadInfoFirstTime;
				saveConfig();
				return message.reply(`✦ Auto Refresh Thread Info β€” ${status(config.database.autoRefreshThreadInfoFirstTime)}`);
			}
		}
		if (state === "mongoUriSet") {
			if (!input) return message.reply("𝗫 Invalid URI.");
			config.database = config.database || {};
			config.database.uriMongodb = input;
			saveConfig();
			return message.reply("✦ MongoDB URI updated. (Restart the bot for this to take effect.)");
		}

		if (state === "dashboardUptime") {
			config.dashBoard = config.dashBoard || {};
			config.serverUptime = config.serverUptime || {};
			config.serverUptime.socket = config.serverUptime.socket || {};
			config.autoUptime = config.autoUptime || {};
			if (num === 1) {
				config.dashBoard.enable = !config.dashBoard.enable;
				saveConfig();
				return message.reply(`✦ Dashboard Enable β€” ${status(config.dashBoard.enable)}`);
			}
			if (num === 2) return await sendAndListen("β€Ί Reply with new Dashboard port:", "dashPortSet");
			if (num === 3) return await sendAndListen("β€Ί Reply with new Verify Code expiry (ms):", "dashExpirySet");
			if (num === 4) {
				config.serverUptime.enable = !config.serverUptime.enable;
				saveConfig();
				return message.reply(`✦ Server Uptime Enable β€” ${status(config.serverUptime.enable)}`);
			}
			if (num === 5) return await sendAndListen("β€Ί Reply with new Server Uptime port:", "uptimePortSet");
			if (num === 6) {
				config.serverUptime.socket.enable = !config.serverUptime.socket.enable;
				saveConfig();
				return message.reply(`✦ Server Uptime Socket Enable β€” ${status(config.serverUptime.socket.enable)}`);
			}
			if (num === 7) return await sendAndListen("β€Ί Reply with new channel name:", "uptimeChannelSet");
			if (num === 8) {
				config.autoUptime.enable = !config.autoUptime.enable;
				saveConfig();
				return message.reply(`✦ Auto Uptime Enable β€” ${status(config.autoUptime.enable)}`);
			}
			if (num === 9) return await sendAndListen("β€Ί Reply with new interval in seconds:", "autoUptimeIntervalSet");
			if (num === 10) return await sendAndListen("β€Ί Reply with new Auto Uptime URL:", "autoUptimeUrlSet");
		}
		if (state === "dashPortSet") {
			if (isNaN(num)) return message.reply("𝗫 Invalid port.");
			config.dashBoard.port = num;
			saveConfig();
			return message.reply(`✦ Dashboard port set to: ${num}`);
		}
		if (state === "dashExpirySet") {
			if (isNaN(num)) return message.reply("𝗫 Invalid value.");
			config.dashBoard.expireVerifyCode = num;
			saveConfig();
			return message.reply(`✦ Verify Code expiry set to: ${num}ms`);
		}
		if (state === "uptimePortSet") {
			if (isNaN(num)) return message.reply("𝗫 Invalid port.");
			config.serverUptime.port = num;
			saveConfig();
			return message.reply(`✦ Server Uptime port set to: ${num}`);
		}
		if (state === "uptimeChannelSet") {
			if (!input) return message.reply("𝗫 Invalid channel name.");
			config.serverUptime.socket.channelName = input;
			saveConfig();
			return message.reply(`✦ Channel name set to: ${input}`);
		}
		if (state === "autoUptimeIntervalSet") {
			if (isNaN(num)) return message.reply("𝗫 Invalid value.");
			config.autoUptime.timeInterval = num;
			saveConfig();
			return message.reply(`✦ Interval set to: ${num}s`);
		}
		if (state === "autoUptimeUrlSet") {
			if (!input) return message.reply("𝗫 Invalid URL.");
			config.autoUptime.url = input;
			saveConfig();
			return message.reply(`✦ URL set to: ${input}`);
		}

		if (state === "autoLoadScripts") {
			config.autoLoadScripts = config.autoLoadScripts || {};
			if (num === 1) {
				config.autoLoadScripts.enable = !config.autoLoadScripts.enable;
				saveConfig();
				return message.reply(`✦ Auto Load Scripts β€” ${status(config.autoLoadScripts.enable)}`);
			}
			if (num === 2) return await sendAndListen("β€Ί Reply with comma-separated command names to ignore:", "ignoreCmdsSet");
			if (num === 3) return await sendAndListen("β€Ί Reply with comma-separated event names to ignore:", "ignoreEventsSet");
		}
		if (state === "ignoreCmdsSet") {
			config.autoLoadScripts.ignoreCmds = input;
			saveConfig();
			return message.reply(`✦ Ignore Commands set to: ${input || "(none)"}`);
		}
		if (state === "ignoreEventsSet") {
			config.autoLoadScripts.ignoreEvents = input;
			saveConfig();
			return message.reply(`✦ Ignore Events set to: ${input || "(none)"}`);
		}

		if (state === "restartListenMqtt") {
			config.restartListenMqtt = config.restartListenMqtt || {};
			if (num === 1) {
				config.restartListenMqtt.enable = !config.restartListenMqtt.enable;
				saveConfig();
				return message.reply(`✦ Restart Listen MQTT Enable β€” ${status(config.restartListenMqtt.enable)}`);
			}
			if (num === 2) return await sendAndListen("β€Ί Reply with new restart interval (ms):", "mqttTimeRestartSet");
			if (num === 3) return await sendAndListen("β€Ί Reply with new delay after stop (ms):", "mqttDelaySet");
			if (num === 4) {
				config.restartListenMqtt.logNoti = !config.restartListenMqtt.logNoti;
				saveConfig();
				return message.reply(`✦ Log Notification β€” ${status(config.restartListenMqtt.logNoti)}`);
			}
		}
		if (state === "mqttTimeRestartSet") {
			if (isNaN(num)) return message.reply("𝗫 Invalid value.");
			config.restartListenMqtt.timeRestart = num;
			saveConfig();
			return message.reply(`✦ Restart interval set to: ${num}ms`);
		}
		if (state === "mqttDelaySet") {
			if (isNaN(num)) return message.reply("𝗫 Invalid value.");
			config.restartListenMqtt.delayAfterStopListening = num;
			saveConfig();
			return message.reply(`✦ Delay set to: ${num}ms`);
		}

		if (state === "notiMqttPick") {
			if (num === 1) return await sendAndListen(renderBoolGroupMenu(config, "notiMqttGmail") + "\n\n2. Set Notify Email\n0. Back", "notiMqttGmailMenu");
			if (num === 2) return await sendAndListen(renderBoolGroupMenu(config, "notiMqttTelegram") + "\n\n2. Set Bot Token\n3. Set Chat ID\n0. Back", "notiMqttTelegramMenu");
			if (num === 3) return await sendAndListen(renderBoolGroupMenu(config, "notiMqttDiscord") + "\n\n2. Set Webhook URL\n0. Back", "notiMqttDiscordMenu");
		}
		if (state === "notiMqttGmailMenu") {
			config.notiWhenListenMqttError = config.notiWhenListenMqttError || {};
			config.notiWhenListenMqttError.gmail = config.notiWhenListenMqttError.gmail || {};
			if (num === 0) {
				const menu = renderBox("πŸ””", "MQTT Error Notify", [], "channels", [
					`1. ${"Gmail"}`,
					`2. ${"Telegram"}`,
					`3. ${"Discord"}`
				], "Reply 1-3");
				return await sendAndListen(menu, "notiMqttPick");
			}
			if (num === 1) {
				config.notiWhenListenMqttError.gmail.enable = !config.notiWhenListenMqttError.gmail.enable;
				saveConfig();
				return message.reply(`✦ Gmail Notify β€” ${status(config.notiWhenListenMqttError.gmail.enable)}`);
			}
			if (num === 2) return await sendAndListen("β€Ί Reply with notify email:", "notiGmailEmailSet");
		}
		if (state === "notiGmailEmailSet") {
			if (!input) return message.reply("𝗫 Invalid email.");
			config.notiWhenListenMqttError.gmail.emailGetNoti = input;
			saveConfig();
			return message.reply(`✦ Notify email set to: ${input}`);
		}
		if (state === "notiMqttTelegramMenu") {
			config.notiWhenListenMqttError = config.notiWhenListenMqttError || {};
			config.notiWhenListenMqttError.telegram = config.notiWhenListenMqttError.telegram || {};
			if (num === 1) {
				config.notiWhenListenMqttError.telegram.enable = !config.notiWhenListenMqttError.telegram.enable;
				saveConfig();
				return message.reply(`✦ Telegram Notify β€” ${status(config.notiWhenListenMqttError.telegram.enable)}`);
			}
			if (num === 2) return await sendAndListen("β€Ί Reply with bot token:", "notiTgTokenSet");
			if (num === 3) return await sendAndListen("β€Ί Reply with chat ID:", "notiTgChatSet");
		}
		if (state === "notiTgTokenSet") {
			config.notiWhenListenMqttError.telegram.botToken = input;
			saveConfig();
			return message.reply("✦ Bot token updated.");
		}
		if (state === "notiTgChatSet") {
			config.notiWhenListenMqttError.telegram.chatId = input;
			saveConfig();
			return message.reply(`✦ Chat ID set to: ${input}`);
		}
		if (state === "notiMqttDiscordMenu") {
			config.notiWhenListenMqttError = config.notiWhenListenMqttError || {};
			config.notiWhenListenMqttError.discordHook = config.notiWhenListenMqttError.discordHook || {};
			if (num === 1) {
				config.notiWhenListenMqttError.discordHook.enable = !config.notiWhenListenMqttError.discordHook.enable;
				saveConfig();
				return message.reply(`✦ Discord Notify β€” ${status(config.notiWhenListenMqttError.discordHook.enable)}`);
			}
			if (num === 2) return await sendAndListen("β€Ί Reply with webhook URL:", "notiDiscordUrlSet");
		}
		if (state === "notiDiscordUrlSet") {
			config.notiWhenListenMqttError.discordHook.webhookUrl = input;
			saveConfig();
			return message.reply("✦ Webhook URL updated.");
		}

		if (state === "credentials") {
			config.credentials = config.credentials || {};
			config.credentials.gmailAccount = config.credentials.gmailAccount || {};
			config.credentials.gRecaptcha = config.credentials.gRecaptcha || {};
			const fields = [
				["gmailAccount", "email", "Gmail Email"],
				["gmailAccount", "clientId", "Gmail Client ID"],
				["gmailAccount", "clientSecret", "Gmail Client Secret"],
				["gmailAccount", "refreshToken", "Gmail Refresh Token"],
				["gmailAccount", "apiKey", "Gmail API Key"],
				["gRecaptcha", "siteKey", "reCAPTCHA Site Key"],
				["gRecaptcha", "secretKey", "reCAPTCHA Secret Key"]
			];
			const field = fields[num - 1];
			if (!field) return message.reply("𝗫 Invalid selection.");
			const [section, key, label] = field;
			return await sendAndListen(`β€Ί Reply with new value for ${label}:`, "credentialSet", { credSection: section, credKey: key });
		}
		if (state === "credentialSet") {
			const { credSection, credKey } = Reply;
			config.credentials[credSection][credKey] = input;
			saveConfig();
			return message.reply("✦ Updated.");
		}
	},

	onReaction: async function ({ api, event, Reaction, message }) {
		if (Reaction.type !== "e2eeRestartConfirm") return;
		if (event.userID !== Reaction.author) return;

		const fs = require("fs");
		const pathFile = `${__dirname}/tmp/restart.txt`;
		fs.writeFileSync(pathFile, `${event.threadID} ${Date.now()}`);
		await message.reply("πŸ”„ | Restarting bot to apply E2EE change...");
		process.exit(2);
	}
};

View raw file