"use strict"; const CONDITION_PATTERN = /^(<=|>=|<|>|==|=)\s*(-?\d+)$/; function normalizeRandomReplies(value) { let rows = value; if (typeof rows === "string") { try { rows = JSON.parse(rows || "[]"); } catch { rows = []; } } if (!Array.isArray(rows)) return []; return rows.map((row) => ({ text: String(row?.text || "").trim(), weight: integerValue(row?.weight, 1), condition: String(row?.condition || "").trim().replace(/\s+/g, "") })).filter((row) => row.text); } function parseRandomRepliesFromBody(body = {}) { const texts = arrayValue(body.random_text); const weights = arrayValue(body.random_weight); const conditions = arrayValue(body.random_condition); return normalizeRandomReplies(texts.map((text, index) => ({ text, weight: weights[index] ?? 1, condition: conditions[index] ?? "" }))); } function normalizeRandomConfig({ replies, rngEnabled, rngMin, rngMax } = {}) { return { replies: normalizeRandomReplies(replies), rngEnabled: rngEnabled === true || rngEnabled === 1 || rngEnabled === "on" || rngEnabled === "true", rngMin: integerValue(rngMin, 1), rngMax: integerValue(rngMax, 100) }; } function validateRandomConfig(input = {}) { const config = normalizeRandomConfig(input); const errors = []; if (!config.replies.length) errors.push("Add at least one random reply."); if (config.replies.length > 100) errors.push("Random Reply supports at most 100 messages."); if (!Number.isInteger(config.rngMin) || !Number.isInteger(config.rngMax) || config.rngMin < -1000000 || config.rngMin > 1000000 || config.rngMax < -1000000 || config.rngMax > 1000000) { errors.push("RNG limits must be whole numbers between -1,000,000 and 1,000,000."); } if (config.rngMin > config.rngMax) errors.push("RNG minimum must be less than or equal to the maximum."); config.replies.forEach((reply, index) => { if (reply.text.length > 2000) errors.push(`Reply ${index + 1} is longer than 2,000 characters.`); if (!Number.isInteger(reply.weight) || reply.weight < 1 || reply.weight > 999) { errors.push(`Reply ${index + 1} weight must be a whole number from 1 to 999.`); } if (reply.condition && !CONDITION_PATTERN.test(reply.condition)) { errors.push(`Reply ${index + 1} condition must look like <20, >=10, or =5.`); } if (reply.condition && !config.rngEnabled) { errors.push(`Turn on RNG before adding a condition to reply ${index + 1}.`); } }); return { ok: errors.length === 0, errors, config }; } function selectRandomReply(input = {}, random = Math.random) { const validation = validateRandomConfig(input); if (!validation.ok) throw new Error(validation.errors[0]); const config = validation.config; const rng = config.rngEnabled ? config.rngMin + Math.floor(safeRandom(random) * (config.rngMax - config.rngMin + 1)) : null; const eligible = config.replies.filter((reply) => !reply.condition || conditionMatches(reply.condition, rng)); if (!eligible.length) throw new Error(`No random reply matches RNG value ${rng}.`); const totalWeight = eligible.reduce((total, reply) => total + reply.weight, 0); let target = safeRandom(random) * totalWeight; for (const reply of eligible) { target -= reply.weight; if (target < 0) return { reply, rng, eligibleCount: eligible.length }; } return { reply: eligible[eligible.length - 1], rng, eligibleCount: eligible.length }; } function conditionMatches(condition, value) { const match = String(condition || "").match(CONDITION_PATTERN); if (!match || !Number.isFinite(value)) return false; const expected = Number(match[2]); if (match[1] === "<") return value < expected; if (match[1] === "<=") return value <= expected; if (match[1] === ">") return value > expected; if (match[1] === ">=") return value >= expected; return value === expected; } function safeRandom(random) { const value = Number(typeof random === "function" ? random() : Math.random()); if (!Number.isFinite(value)) return 0; return Math.max(0, Math.min(value, 0.999999999999)); } function arrayValue(value) { if (Array.isArray(value)) return value; if (value === undefined || value === null) return []; return [value]; } function integerValue(value, fallback) { if (value === undefined || value === null || value === "") return fallback; const number = Number(value); return Number.isInteger(number) ? number : Number.NaN; } module.exports = { conditionMatches, normalizeRandomConfig, normalizeRandomReplies, parseRandomRepliesFromBody, selectRandomReply, validateRandomConfig };