A lightweight Socket.IO gateway that streams AI replies for the Morphereum community chat.
Provides two real-time flows: general chat and raid message generator, with per-IP rate limits, friendly block UX, and OpenAI-backed responses.
- Chat replies: receives a user message and streams back a witty MatrixCHAT reply from OpenAI. The assistant persona is set with a system prompt (humorous, meme-y).
- Raid composer: given the daily raid context (platform + share message), returns a spicy, share-ready text for raiders.
- Fair-use limits: 5 chat messages / 12h and 1 raid message / 18h per IP; graceful “blocked” events include a human-readable unlock time.
- CORS-safe: allows frontends from
https://localhost:5173andhttps://morphereum.netlify.app.
- Runtime: Node.js + Express (for bootstrapping the HTTP server).
- Realtime: Socket.IO server, room-addressing by
socket.id. - AI: OpenAI Chat Completions API, configurable model via env, with
max_tokens: 200andtemperature: 0.1. - Rate limiting: rate-limiter-flexible (memory store) for chat and raid flows.
- Config & validation:
dotenv+ zod for strict environment parsing.
-
"message"
Payload: a string (the user’s chat text).
Server runs OpenAI with a humorous “MatrixCHAT” prompt and responds to that socket room. -
"raid-message"
Payload (JSON):{ platform: string; shareMessage: string; }
Server crafts a meme-y call-to-action text for the given platform and base share copy.
"bot-message"— AI reply to"message". Payload:string."bot-raid-message"— AI reply to"raid-message". Payload:string."blocked"— chat flow limit reached. Payload:The formatted time uses pt-BR and timezone America/Sao_Paulo.{ "message": "Você atingiu o limite de mensagens. Tente novamente em X...", "unblockDateFormatted": "dd/mm/aaaa hh:mm:ss", "unblockDate": "2025-10-20T12:34:56.000Z" }"raid-blocked"— raid flow limit reached. Payload:Same localization as above.{ "unblockDateFormatted": "dd/mm/aaaa hh:mm:ss", "unblockDate": "2025-10-20T12:34:56.000Z" }
- General chat (
"message"): 5 messages per 12 hours per IP. On exceed, server emits"blocked"with the remaining wait time (humanized). The humanization uses an internal helper that renders hours/minutes/seconds in Portuguese (e.g.,2 horas e 15 minutos). - Raid composer (
"raid-message"): 1 message per 18 hours per IP. On exceed, server emits"raid-blocked"with unlock timestamps.
- Model: read from
OPENAI_MODEL(e.g.,gpt-4o-mini,gpt-4.1-mini, etc.). - Completion:
max_tokens: 200,temperature: 0.1. - System persona: the assistant speaks with an irreverent, comedic tone tailored for the Morphereum community (prompt is in Portuguese).
Validated on boot via zod; process exits if missing/invalid.
PORT=8081
IS_DEV_MODE=true
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o-mini# 1) install deps
pnpm install # or npm i / yarn
# 2) create .env
cp .env.example .env # fill in PORT, OPENAI keys, etc.
# 3) run dev
pnpm dev
# 4) run prod
pnpm build && pnpm start- On boot, you’ll see
[server] --> Running at http://localhost:<PORT>. - CORS allows
https://localhost:5173andhttps://morphereum.netlify.app. Adjust inserver.tsif needed.
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
<script>
const socket = io("http://localhost:8081", {
transports: ["websocket"],
withCredentials: true,
});
// Send a chat message
function sendChat(text) {
socket.emit("message", text);
}
// Receive AI reply
socket.on("bot-message", (reply) => {
console.log("AI:", reply);
});
// Handle rate limit
socket.on("blocked", ({ message, unblockDateFormatted }) => {
alert(`${message}\nDesbloqueio: ${unblockDateFormatted}`);
});
// Raid composer
function composeRaid(platform, shareMessage) {
socket.emit("raid-message", { platform, shareMessage });
}
socket.on("bot-raid-message", (reply) => {
console.log("RAID:", reply);
});
socket.on("raid-blocked", ({ unblockDateFormatted }) => {
alert(`Raid composer locked. Try again at ${unblockDateFormatted}`);
});
</script>import { io } from "socket.io-client";
const socket = io("http://localhost:8081");
export function askMatrixChat(message: string) {
socket.emit("message", message);
}
socket.on("bot-message", (text: string) => {
// render in UI
});
export function askRaidCopy(platform: string, shareMessage: string) {
socket.emit("raid-message", { platform, shareMessage });
}
socket.on("bot-raid-message", (text: string) => {
// render in UI
});
socket.on("blocked", ({ message, unblockDateFormatted }) => {
// set UI state to blocked until unblockDateFormatted
});
socket.on("raid-blocked", ({ unblockDateFormatted }) => {
// show toast
});Client "message" --> rateLimiter (5/12h) --> OpenAI chat.completions --> emit "bot-message"
Client "raid-message" --> raidLimiter (1/18h) --> OpenAI chat.completions --> emit "bot-raid-message"
Exceed limits --> emit "blocked" | "raid-blocked" with unlock dates (pt-BR, America/Sao_Paulo)
- Socket rooms use
socket.id; replies target the originating client only.
- CORS: update
originallowlist inserver.tsfor new frontends. - Formatting helper:
formatMsToTime({ ms })converts wait time into “X horas e Y minutos…”. - HTTP layer: Express +
http.createServerbootstrap before attaching Socket.IO.
- Application logs a startup banner with host/port.
- When a limiter rejects, the server does not throw; it emits structured block events with both a raw
unblockDateand a localizedunblockDateFormatted.