Repository: SimuCorps/corvy-sdk
Pull request #24: SDK Revamp 1.3 :D
Author: login: zrodevkaan
Labels: name: enhancement
description: New feature or request
name: javascript
description: A pull request or issue for the JavaScript SDK
Created: 2025-05-16T05:57:57Z | Merged: 2025-05-16T06:49:44Z

2.0 gonna be wildin


Diff:
diff --git a/javascript/docs/command-handler-example/simple-bot.js b/javascript/docs/command-handler-example/simple-bot.js
index 243c860..d62e87b 100644
--- a/javascript/docs/command-handler-example/simple-bot.js
+++ b/javascript/docs/command-handler-example/simple-bot.js
@@ -23,6 +23,4 @@ for (let file of commandFiles) {
     const command = await import(`./commands/${file}`);
     
     client.registerCommand([command.name, ...(command.aliases ?? [])], command.execute);
-}
-
-client.login();
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/javascript/docs/simple-bot.js b/javascript/docs/simple-bot.js
index 1b65e43..cf4a3e1 100644
--- a/javascript/docs/simple-bot.js
+++ b/javascript/docs/simple-bot.js
@@ -17,6 +17,4 @@ client.on("ready", (client) => {
 
 client.registerCommand("ping", async (msg, client, args) => {
     return "Pong!";
-});
-
-client.login();
+});
\ No newline at end of file
diff --git a/javascript/sdk/corvy.d.ts b/javascript/sdk/corvy.d.ts
deleted file mode 100644
index 1fb3aa7..0000000
--- a/javascript/sdk/corvy.d.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import { EventEmitter } from "events";
-
-export interface ClientOptions {
-    token: string;
-    prefix?: string;
-    devMode?: boolean;
-}
-
-export interface Message {
-    content: string;
-    flock_id: string;
-    nest_id: string;
-    user: {
-        id: string;
-        username: string;
-        is_bot: boolean;
-        photo_url: string;
-        badge: string;
-        available_badges: string[];
-    };
-    flock_name?: string;
-    nest_name?: string;
-}
-
-export interface User {
-    user: {
-        id: string;
-        username: string;
-        is_bot: boolean;
-        photo_url: string;
-        badge: string;
-        available_badges: string[];
-    }
-}
-
-export interface ClientUser {
-    user: {
-        id: string;
-        name: string;
-        icon: string;
-        created_at: string;
-        updated_at: string;
-        user_id: string;
-    }
-}
-
-export interface Flock {
-    flock: {
-        id: string;
-        name: string;
-        icon: string;
-        members_count: number;
-        nests_count: number;
-        created_at: string;
-    };
-}
-
-export class Flocks {
-    constructor(client: Client);
-
-    /**
-     * Fetches and caches the flocks from the API
-     */
-    fetch(): Promise<void>;
-
-    /**
-     * List of all fetched flocks
-     */
-    get list(): Flock[];
-
-    /**
-     * Number of fetched flocks
-     */
-    get size(): number;
-}
-
-export class Client extends EventEmitter {
-    /**
-     * 
-     * @param {Object} options - Bot configuration
-     * @param {string} options.token - Authentication token
-     * @param {string} [options.prefix=";"] - Global command prefix (default is ";")
-     * @param {boolean} options.devMode - Enables more detailed logging
-     */
-    constructor(options: ClientOptions);
-
-    token: string;
-    prefix: string;
-    client: any;
-    devMode: boolean;
-    currentCursor: number;
-    commands: Map<string, Function>;
-    user: ClientUser | null;
-    flocks: Flocks;
-
-    /**
-     * Registers a command
-     * @param {string} name - Command name
-     * @param {Function} handler - Function to handle the command
-     */
-    registerCommand(name: string, handler: (msg: Message, client: Client, args: string) => void): void;
-
-    /**
-     * Begins login
-     */
-    login(): Promise<void>;
-
-    /**
-     * Fetches a user object by their ID
-     * @param {string} userId - The user ID to look up
-     */
-    getUserById(userId: string): Promise<User>;
-
-    /**
-     * Fetches a user object by their username
-     * @param {string} username - The username to look up
-     */
-    getUserByUsername(username: string): Promise<User>;
-
-    /**
-     * Fetches a flock object by its ID
-     * @param {string} flockId - The flock ID to look up
-     */
-    getFlockById(flockId: string): Promise<Flock>;
-
-    /**
-     * Milliseconds since the client logged in
-     */
-    get uptime(): number;
-
-    /**
-     * Human-readable uptime (e.g., "1d 3h 12m 5s")
-     */
-    get formattedUptime(): string;
-
-    on(event: "ready", listener: (client: Client) => void): this;
-    on(event: "messageRaw", listener: (msg: Message) => void): this;
-    on(event: "message", listener: (msg: Message) => void): this;
-    on(event: "error", listener: (err: Error) => void): this;
-    on(event: "commandError", listener: (name: string, msg: Message, err: Error) => void): this;
-}
\ No newline at end of file
diff --git a/javascript/sdk/corvy.js b/javascript/sdk/corvy.js
index 37ad5d2..1e85728 100644
--- a/javascript/sdk/corvy.js
+++ b/javascript/sdk/corvy.js
@@ -1,251 +1,351 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
+        if (!handler || typeof handler !== 'function') {
+            throw new Error('Command handler must be a function');
+        }
 
-  async _processMessages() {
-    this._log("Starting message loop...");
+        const nameList = Array.isArray(names) ? names : [names];
+
+        for (const name of nameList) {
+            if (typeof name !== 'string' || name.trim() === '') {
+                throw new Error('Command name must be a non-empty string');
+            }
+
+            const normalizedName = name.toLowerCase().trim();
+            this._log(`📝 Registered command: ${this.prefix}${normalizedName}`);
+
+            this.commands.set(normalizedName, {
+                staff: staffUserIds,
+                desc: desc,
+                hide: data.hide,
+                callback: async (msg, client, args) => {
+                    try {
+                        const allowedStaff = Array.isArray(staffUserIds) ? staffUserIds : [];
+
+                        const isStaff = allowedStaff.length === 0 || this.checkForStaff(msg.user.id, allowedStaff);
+                        if (!isStaff) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, `🚫 You are not permitted to use the **${this.prefix}${normalizedName}** command.`);
+                            return;
+                        }
+
+                        this._log(`📣 Executing command: ${this.prefix}${normalizedName}`);
+
+                        const response = await handler(msg, client, args);
+                        if (response) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, response);
+                        }
+                    } catch (error) {
+                        this._log(`❌ Error in command "${normalizedName}": ${error.message}`);
+                        this.emit('commandError', normalizedName, msg, error);
+                    }
+                }
+            });
 
-    while (true) {
-      try {
-        const res = await this.client.get("/messages", {
-          params: { cursor: this.currentCursor }
-        });
+        }
+    }
+
+    sendMessage(flockId, nestId, content) {
+        this.channel.push("send_message", {
+            flock_id: flockId.toString(),
+            nest_id: nestId.toString(),
+            content: content
+        })
+            .receive("ok", () => {
+                this._log(`📤 Sent message to nest ${nestId} in flock ${flockId}`);
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to send message: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to send message: ${JSON.stringify(response)}`));
+            });
+    }
+
+    async _handleNewMessage(message) {
+        const msgContent = message.content;
+        const nestId = message.nest_id;
+        const flockId = message.flock_id;
+        const sender = message.user?.username || 'unknown';
+
+        this.emit('messageRaw', message);
+
+        if (message.user?.is_bot) {
+            return;
+        }
+
+        if (!msgContent || !nestId) return;
 
-        const data = res.data;
-        if (data.cursor) {
-          this.currentCursor = data.cursor;
+        if (flockId && nestId) {
+            this.flockNestMap[nestId.toString()] = flockId.toString();
         }
 
-        for (const msg of data.messages || []) {
-          if (msg.user.is_bot) {
-            continue;
-          }
+        this._log(`📨 Message in nest ${nestId} from ${sender}: '${msgContent}'`);
 
-          this.emit("messageRaw", msg);
-          this._log(`Received message "${msg.content}" from "${msg.user.username}" in "${msg.flock_name}/${msg.nest_name}"`);
+        if (msgContent.startsWith(this.prefix)) {
+            const parts = msgContent.slice(this.prefix.length).trim().split(/\s+/);
+            const commandName = parts[0].toLowerCase();
+            const args = parts.slice(1);
+            const argsText = msgContent.slice(this.prefix.length + commandName.length).trim();
 
-          const handled = await this._handleCommand(msg);
-          if (!handled) {
-            this.emit("message", msg);
-          }
+            const handled = await this._executeCommand(message, commandName, args, argsText);
+
+            if (!handled) {
+                this.emit('message', message);
+            }
+        } else {
+            this.emit('message', message);
+        }
+    }
+
+    async _executeCommand(message, commandName, args, argsText) {
+        const commandHandler = this.commands.get(commandName);
+
+        if (commandHandler) {
+            let flockId = message.flock_id
+
+            if (!flockId) {
+                flockId = this.flockNestMap[message.nest_id.toString()];
+
+                /* Keeping incase dishy does some API stuff again. */
+                // if (!flockId) {
+                //     this._log(`⚠️ Could not find flock ID for nest ${message.nest_id}, can't respond`);
+                //     return false;
+                // }
+
+                message.flock_id = flockId;
+            }
+
+            await commandHandler.callback(message, this, args, argsText);
+            return true;
+        }
+
+        return false;
+    }
+
+    async _fetchFlockForNest(nestId) {
+        this._log(`🔍 Looking up flock for nest ${nestId}...`);
+
+        try {
+            const response = await this.axiosInstance.get(`/flocks`);
+
+            if (response.status === 200 && response.data.success) {
+                return await this._checkFlocksForNest([...response.data.flocks], nestId);
+            } else {
+                this._log(`⚠️ API response unsuccessful when fetching flocks`);
+                return null;
+            }
+        } catch (error) {
+            this._log(`⚠️ HTTP error while fetching flocks: ${error.message}`);
+            return null;
+        }
+    }
+
+    async _checkFlocksForNest(flocks, nestId) {
+        if (flocks.length === 0) return null;
+
+        const flock = flocks.shift();
+
+        try {
+            const response = await this.axiosInstance.get(`/flocks/${flock.id}/nests`);
+
+            if (response.status === 200 && response.data.success) {
+                for (const nest of response.data.nests) {
+                    if (nest.id.toString() === nestId.toString()) {
+                        const flockId = flock.id;
+                        this.flockNestMap[nestId.toString()] = flockId;
+                        this._log(`✅ Found flock ${flockId} for nest ${nestId}`);
+                        return flockId;
+                    }
+                }
+            }
+
+            if (flocks.length > 0) {
+                return await this._checkFlocksForNest(flocks, nestId);
+            } else {
+                this._log(`⚠️ Couldn't find flock ID for nest ${nestId} in any flock`);
+                return null;
+            }
+        } catch (error) {
+            this._log(`⚠️ HTTP error while fetching nests for flock ${flock.id}: ${error.message}`);
+            if (flocks.length > 0) {
+                return await this._checkFlocksForNest(flocks, nestId);
+            }
+            return null;
+        }
+    }
+
+    async getUserById(userId) {
+        try {
+            this._log(`Fetching user by ID: ${userId}`);
+            const response = await this.axiosInstance.get(`/users/${userId}`);
+            return response.data;
+        } catch (err) {
+            this.emit("error", new Error("Get user by id failed: " + err.message));
+            this._log(`Get user by id failed: ${err.message}`);
         }
+    }
+
+    async getUserByUsername(username) {
+        try {
+            this._log(`Fetching user by username: ${username}`);
+            const response = await this.axiosInstance.get(`/users/by-username/${username}`);
 
-        await new Promise(r => setTimeout(r, 1000));
-      } catch (err) {
-        this.emit("error", new Error("Error in message loop: " + err.message));
-        this._log(`Error in message loop: ${err.message}`);
-        await new Promise(r => setTimeout(r, 5000));
-      }
-    }
-  }
-
-  async _handleCommand(msg) {
-    const content = msg.content.toLowerCase();
-    for (const [prefix, handler] of this.commands.entries()) {
-      if (content.startsWith(prefix)) {
-        const argsText = msg.content.slice(prefix.length).trim();
-        await handler(msg, this, argsText);
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  // -------------------------------
-  // API Helpers
-  // -------------------------------
-
-  async _sendMsg(flockId, nestId, content) {
-    try {
-      this._log(`Sending message to flock:${flockId}, nest:${nestId} -> "${content}"`);
-      await this.client.post(`/flocks/${flockId}/nests/${nestId}/messages`, { content });
-    } catch (err) {
-      this.emit("error", new Error("Send message failed: " + err.message));
-      this._log(`Send message failed: ${err.message}`);
-    }
-  }
-
-  async getUserById(userId) {
-    try {
-      this._log(`Fetching user by ID: ${userId}`);
-      const response = await this.client.get(`/users/${userId}`);
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by id failed: " + err.message));
-      this._log(`Get user by id failed: ${err.message}`);
-    }
-  }
-
-  async getUserByUsername(username) {
-    try {
-      this._log(`Fetching user by username: ${username}`);
-      const response = await this.client.get(`/users/by-username/${username}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by username failed: " + err.message));
-      this._log(`Get user by username failed: ${err.message}`);
-    }
-  }
-
-  async getFlockById(flockId) {
-    try {
-      this._log(`Fetching flock by id: ${flockId}`);
-      const response = await this.client.get(`/flocks/${flockId}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get flock by id failed: " + err.message));
-      this._log(`Get flock by id failed: ${err.message}`);
-    }
-  }
-
-  // -------------------------------
-  // Stuff?
-  // -------------------------------
-
-  get uptime() {
-    return this.startTime ? Date.now() - this.startTime : 0;
-  }
-
-  get formattedUptime() {
-    const ms = this.uptime;
-    const sec = Math.floor((ms / 1000) % 60);
-    const min = Math.floor((ms / (1000 * 60)) % 60);
-    const hr = Math.floor((ms / (1000 * 60 * 60)) % 24);
-    const days = Math.floor(ms / (1000 * 60 * 60 * 24));
-    const parts = [];
-    
-    if (days) parts.push(`${days}d`);
-    if (hr) parts.push(`${hr}h`);
-    if (min) parts.push(`${min}m`);
-    if (sec || parts.length === 0) parts.push(`${sec}s`);
-
-    return parts.join(" ");
-  }
-};
\ No newline at end of file
+            return response.data;
+        } catch (err) {
+            this.emit("error", new Error("Get user by username failed: " + err.message));
+            this._log(`Get user by username failed: ${err.message}`);
+        }
+    }
+
+    get uptime() {
+        return Date.now() - this.startTime;
+    }
+
+    get formattedUptime() {
+        const ms = this.uptime;
+        const sec = Math.floor((ms / 1000) % 60);
+        const min = Math.floor((ms / (1000 * 60)) % 60);
+        const hr = Math.floor((ms / (1000 * 60 * 60)) % 24);
+        const days = Math.floor(ms / (1000 * 60 * 60 * 24));
+        const parts = [];
+
+        if (days) parts.push(`${days}d`);
+        if (hr) parts.push(`${hr}h`);
+        if (min) parts.push(`${min}m`);
+        if (sec || parts.length === 0) parts.push(`${sec}s`);
+
+        return parts.join(" ");
+    }
+}
\ No newline at end of file


Reviews:
user: login: Dishpit
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:02:26Z
user: login: zrodevkaan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:03:26Z
user: login: zrodevkaan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:03:40Z
user: login: infernostars
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T06:09:10Z
user: login: zrodevkaan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:10:48Z
user: login: zrodevkaan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:11:02Z
user: login: zrodevkaan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:11:11Z
user: login: infernostars
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T06:14:17Z
user: login: zrodevkaan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:14:49Z
user: login: infernostars
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T06:14:54Z
user: login: infernostars
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T06:15:23Z
user: login: infernostars
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T06:15:44Z
user: login: zrodevkaan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:18:49Z
user: login: zrodevkaan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:19:01Z
user: login: infernostars
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T06:19:05Z
user: login: zrodevkaan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:19:09Z
user: login: infernostars
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T06:21:00Z
user: login: infernostars
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T06:22:04Z
user: login: Dishpit
body: will approve once resolved
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:24:01Z
user: login: infernostars
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T06:24:29Z
user: login: infernostars
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-16T06:24:49Z
user: login: zrodevkaan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:24:55Z
user: login: infernostars
body: LGTM
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-16T06:46:24Z
user: login: Dishpit
body: <img width="290" alt="image" src="https://github.com/user-attachments/assets/ebe46f42-3bef-42da-961d-b19d457961e4" />

state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T06:48:48Z

Review comments:
path: javascript/sdk/corvy.d.ts
user: login: Dishpit
body: thank you for deleting the typeshit
created_at: 2025-05-16T06:01:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
path: javascript/sdk/corvy.js
user: login: Dishpit
body: we probably shouldn't use prefixes for an sdk tbh, let the users define their prefix (if any)
created_at: 2025-05-16T06:02:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
path: javascript/sdk/corvy.js
user: login: infernostars
body: iirc these are the defaults
created_at: 2025-05-16T06:03:00Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
path: javascript/sdk/corvy.js
user: login: zrodevkaan
body: ... ? 

I am confused. This is defaulted. not set?

created_at: 2025-05-16T06:03:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
path: javascript/sdk/corvy.js
user: login: zrodevkaan
body: The developer still can set the prefix
created_at: 2025-05-16T06:03:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
path: javascript/sdk/corvy.js
user: login: infernostars
body: maybe could be named something better than _log? maybe _debugLog?
created_at: 2025-05-16T06:03:45Z
author_association: COLLABORATOR
diff_hunk: @@ -23,6 +23,4 @@ for (let file of commandFiles) {
     const command = await import(`./commands/${file}`);
     
     client.registerCommand([command.name, ...(command.aliases ?? [])], command.execute);
-}
-
-client.login();
path: javascript/docs/command-handler-example/simple-bot.js
user: login: Dishpit
body: why the deletion of this?
created_at: 2025-05-16T06:03:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,6 +17,4 @@ client.on("ready", (client) => {
 
 client.registerCommand("ping", async (msg, client, args) => {
     return "Pong!";
-});
-
-client.login();
path: javascript/docs/simple-bot.js
user: login: Dishpit
body: also why?
created_at: 2025-05-16T06:03:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
path: javascript/sdk/corvy.js
user: login: infernostars
body: technically you should be getting this [the WEBSOCKET_URL] from the auth response since it may change
created_at: 2025-05-16T06:04:25Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
path: javascript/sdk/corvy.js
user: login: infernostars
body: why the FUCK is this named getPrefix
created_at: 2025-05-16T06:04:50Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
path: javascript/sdk/corvy.js
user: login: infernostars
body: why is this the type of thing thats built into the sdk? maybe more generic check wrapping but this seems a bit wack
created_at: 2025-05-16T06:05:41Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
path: javascript/sdk/corvy.js
user: login: infernostars
body: why are staffs per-function
created_at: 2025-05-16T06:05:55Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
+        if (!handler || typeof handler !== 'function') {
+            throw new Error('Command handler must be a function');
+        }
 
-  async _processMessages() {
-    this._log("Starting message loop...");
+        const nameList = Array.isArray(names) ? names : [names];
+
+        for (const name of nameList) {
+            if (typeof name !== 'string' || name.trim() === '') {
+                throw new Error('Command name must be a non-empty string');
path: javascript/sdk/corvy.js
user: login: infernostars
body: not sure if you can do this in js, but if you could it might be a good idea to fall back to the function name
created_at: 2025-05-16T06:06:33Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
+        if (!handler || typeof handler !== 'function') {
+            throw new Error('Command handler must be a function');
+        }
 
-  async _processMessages() {
-    this._log("Starting message loop...");
+        const nameList = Array.isArray(names) ? names : [names];
+
+        for (const name of nameList) {
+            if (typeof name !== 'string' || name.trim() === '') {
+                throw new Error('Command name must be a non-empty string');
+            }
+
+            const normalizedName = name.toLowerCase().trim();
+            this._log(`📝 Registered command: ${this.prefix}${normalizedName}`);
+
+            this.commands.set(normalizedName, {
+                staff: staffUserIds,
+                desc: desc,
+                hide: data.hide,
+                callback: async (msg, client, args) => {
+                    try {
+                        const allowedStaff = Array.isArray(staffUserIds) ? staffUserIds : [];
+
+                        const isStaff = allowedStaff.length === 0 || this.checkForStaff(msg.user.id, allowedStaff);
+                        if (!isStaff) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, `🚫 You are not permitted to use the **${this.prefix}${normalizedName}** command.`);
+                            return;
+                        }
+
+                        this._log(`📣 Executing command: ${this.prefix}${normalizedName}`);
+
+                        const response = await handler(msg, client, args);
+                        if (response) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, response);
+                        }
+                    } catch (error) {
+                        this._log(`❌ Error in command "${normalizedName}": ${error.message}`);
+                        this.emit('commandError', normalizedName, msg, error);
+                    }
+                }
+            });
 
-    while (true) {
-      try {
-        const res = await this.client.get("/messages", {
-          params: { cursor: this.currentCursor }
-        });
+        }
+    }
+
+    sendMessage(flockId, nestId, content) {
+        this.channel.push("send_message", {
+            flock_id: flockId.toString(),
+            nest_id: nestId.toString(),
+            content: content
+        })
+            .receive("ok", () => {
+                this._log(`📤 Sent message to nest ${nestId} in flock ${flockId}`);
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to send message: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to send message: ${JSON.stringify(response)}`));
+            });
+    }
 
-        const data = res.data;
-        if (data.cursor) {
-          this.currentCursor = data.cursor;
+    async _handleNewMessage(message) {
+        if (message.user?.is_bot) {
+            return;
         }
 
-        for (const msg of data.messages || []) {
-          if (msg.user.is_bot) {
-            continue;
-          }
+        const msgContent = message.content;
+        const nestId = message.nest_id;
+        const flockId = message.flock_id;
+        const sender = message.user?.username || 'unknown';
 
-          this.emit("messageRaw", msg);
-          this._log(`Received message "${msg.content}" from "${msg.user.username}" in "${msg.flock_name}/${msg.nest_name}"`);
+        if (!msgContent || !nestId) return;
 
-          const handled = await this._handleCommand(msg);
-          if (!handled) {
-            this.emit("message", msg);
-          }
+        if (flockId && nestId) {
+            this.flockNestMap[nestId.toString()] = flockId.toString();
         }
 
-        await new Promise(r => setTimeout(r, 1000));
-      } catch (err) {
-        this.emit("error", new Error("Error in message loop: " + err.message));
-        this._log(`Error in message loop: ${err.message}`);
-        await new Promise(r => setTimeout(r, 5000));
-      }
-    }
-  }
-
-  async _handleCommand(msg) {
-    const content = msg.content.toLowerCase();
-    for (const [prefix, handler] of this.commands.entries()) {
-      if (content.startsWith(prefix)) {
-        const argsText = msg.content.slice(prefix.length).trim();
-        await handler(msg, this, argsText);
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  // -------------------------------
-  // API Helpers
-  // -------------------------------
-
-  async _sendMsg(flockId, nestId, content) {
-    try {
-      this._log(`Sending message to flock:${flockId}, nest:${nestId} -> "${content}"`);
-      await this.client.post(`/flocks/${flockId}/nests/${nestId}/messages`, { content });
-    } catch (err) {
-      this.emit("error", new Error("Send message failed: " + err.message));
-      this._log(`Send message failed: ${err.message}`);
-    }
-  }
-
-  async getUserById(userId) {
-    try {
-      this._log(`Fetching user by ID: ${userId}`);
-      const response = await this.client.get(`/users/${userId}`);
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by id failed: " + err.message));
-      this._log(`Get user by id failed: ${err.message}`);
-    }
-  }
-
-  async getUserByUsername(username) {
-    try {
-      this._log(`Fetching user by username: ${username}`);
-      const response = await this.client.get(`/users/by-username/${username}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by username failed: " + err.message));
-      this._log(`Get user by username failed: ${err.message}`);
-    }
-  }
-
-  async getFlockById(flockId) {
-    try {
-      this._log(`Fetching flock by id: ${flockId}`);
-      const response = await this.client.get(`/flocks/${flockId}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get flock by id failed: " + err.message));
-      this._log(`Get flock by id failed: ${err.message}`);
-    }
-  }
-
-  // -------------------------------
-  // Stuff?
-  // -------------------------------
-
-  get uptime() {
-    return this.startTime ? Date.now() - this.startTime : 0;
-  }
-
-  get formattedUptime() {
-    const ms = this.uptime;
-    const sec = Math.floor((ms / 1000) % 60);
-    const min = Math.floor((ms / (1000 * 60)) % 60);
-    const hr = Math.floor((ms / (1000 * 60 * 60)) % 24);
-    const days = Math.floor(ms / (1000 * 60 * 60 * 24));
-    const parts = [];
-    
-    if (days) parts.push(`${days}d`);
-    if (hr) parts.push(`${hr}h`);
-    if (min) parts.push(`${min}m`);
-    if (sec || parts.length === 0) parts.push(`${sec}s`);
-
-    return parts.join(" ");
-  }
-};
\ No newline at end of file
+        this._log(`📨 Message in nest ${nestId} from ${sender}: '${msgContent}'`);
+        this.emit('messageRaw', message);
path: javascript/sdk/corvy.js
user: login: infernostars
body: messageRaw should include bot messages if you're striving for parity with the Python SDK [they are filtered earlier in the function]
created_at: 2025-05-16T06:07:30Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
path: javascript/sdk/corvy.js
user: login: Dishpit
body: ah you're right, it's late and it's been a minute since i've used this syntax 💀
created_at: 2025-05-16T06:07:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
+        if (!handler || typeof handler !== 'function') {
+            throw new Error('Command handler must be a function');
+        }
 
-  async _processMessages() {
-    this._log("Starting message loop...");
+        const nameList = Array.isArray(names) ? names : [names];
+
+        for (const name of nameList) {
+            if (typeof name !== 'string' || name.trim() === '') {
+                throw new Error('Command name must be a non-empty string');
+            }
+
+            const normalizedName = name.toLowerCase().trim();
+            this._log(`📝 Registered command: ${this.prefix}${normalizedName}`);
+
+            this.commands.set(normalizedName, {
+                staff: staffUserIds,
+                desc: desc,
+                hide: data.hide,
+                callback: async (msg, client, args) => {
+                    try {
+                        const allowedStaff = Array.isArray(staffUserIds) ? staffUserIds : [];
+
+                        const isStaff = allowedStaff.length === 0 || this.checkForStaff(msg.user.id, allowedStaff);
+                        if (!isStaff) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, `🚫 You are not permitted to use the **${this.prefix}${normalizedName}** command.`);
+                            return;
+                        }
+
+                        this._log(`📣 Executing command: ${this.prefix}${normalizedName}`);
+
+                        const response = await handler(msg, client, args);
+                        if (response) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, response);
+                        }
+                    } catch (error) {
+                        this._log(`❌ Error in command "${normalizedName}": ${error.message}`);
+                        this.emit('commandError', normalizedName, msg, error);
+                    }
+                }
+            });
 
-    while (true) {
-      try {
-        const res = await this.client.get("/messages", {
-          params: { cursor: this.currentCursor }
-        });
+        }
+    }
+
+    sendMessage(flockId, nestId, content) {
+        this.channel.push("send_message", {
+            flock_id: flockId.toString(),
+            nest_id: nestId.toString(),
+            content: content
+        })
+            .receive("ok", () => {
+                this._log(`📤 Sent message to nest ${nestId} in flock ${flockId}`);
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to send message: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to send message: ${JSON.stringify(response)}`));
+            });
+    }
 
-        const data = res.data;
-        if (data.cursor) {
-          this.currentCursor = data.cursor;
+    async _handleNewMessage(message) {
+        if (message.user?.is_bot) {
+            return;
         }
 
-        for (const msg of data.messages || []) {
-          if (msg.user.is_bot) {
-            continue;
-          }
+        const msgContent = message.content;
+        const nestId = message.nest_id;
+        const flockId = message.flock_id;
+        const sender = message.user?.username || 'unknown';
 
-          this.emit("messageRaw", msg);
-          this._log(`Received message "${msg.content}" from "${msg.user.username}" in "${msg.flock_name}/${msg.nest_name}"`);
+        if (!msgContent || !nestId) return;
 
-          const handled = await this._handleCommand(msg);
-          if (!handled) {
-            this.emit("message", msg);
-          }
+        if (flockId && nestId) {
+            this.flockNestMap[nestId.toString()] = flockId.toString();
         }
 
-        await new Promise(r => setTimeout(r, 1000));
-      } catch (err) {
-        this.emit("error", new Error("Error in message loop: " + err.message));
-        this._log(`Error in message loop: ${err.message}`);
-        await new Promise(r => setTimeout(r, 5000));
-      }
-    }
-  }
-
-  async _handleCommand(msg) {
-    const content = msg.content.toLowerCase();
-    for (const [prefix, handler] of this.commands.entries()) {
-      if (content.startsWith(prefix)) {
-        const argsText = msg.content.slice(prefix.length).trim();
-        await handler(msg, this, argsText);
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  // -------------------------------
-  // API Helpers
-  // -------------------------------
-
-  async _sendMsg(flockId, nestId, content) {
-    try {
-      this._log(`Sending message to flock:${flockId}, nest:${nestId} -> "${content}"`);
-      await this.client.post(`/flocks/${flockId}/nests/${nestId}/messages`, { content });
-    } catch (err) {
-      this.emit("error", new Error("Send message failed: " + err.message));
-      this._log(`Send message failed: ${err.message}`);
-    }
-  }
-
-  async getUserById(userId) {
-    try {
-      this._log(`Fetching user by ID: ${userId}`);
-      const response = await this.client.get(`/users/${userId}`);
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by id failed: " + err.message));
-      this._log(`Get user by id failed: ${err.message}`);
-    }
-  }
-
-  async getUserByUsername(username) {
-    try {
-      this._log(`Fetching user by username: ${username}`);
-      const response = await this.client.get(`/users/by-username/${username}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by username failed: " + err.message));
-      this._log(`Get user by username failed: ${err.message}`);
-    }
-  }
-
-  async getFlockById(flockId) {
-    try {
-      this._log(`Fetching flock by id: ${flockId}`);
-      const response = await this.client.get(`/flocks/${flockId}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get flock by id failed: " + err.message));
-      this._log(`Get flock by id failed: ${err.message}`);
-    }
-  }
-
-  // -------------------------------
-  // Stuff?
-  // -------------------------------
-
-  get uptime() {
-    return this.startTime ? Date.now() - this.startTime : 0;
-  }
-
-  get formattedUptime() {
-    const ms = this.uptime;
-    const sec = Math.floor((ms / 1000) % 60);
-    const min = Math.floor((ms / (1000 * 60)) % 60);
-    const hr = Math.floor((ms / (1000 * 60 * 60)) % 24);
-    const days = Math.floor(ms / (1000 * 60 * 60 * 24));
-    const parts = [];
-    
-    if (days) parts.push(`${days}d`);
-    if (hr) parts.push(`${hr}h`);
-    if (min) parts.push(`${min}m`);
-    if (sec || parts.length === 0) parts.push(`${sec}s`);
-
-    return parts.join(" ");
-  }
-};
\ No newline at end of file
+        this._log(`📨 Message in nest ${nestId} from ${sender}: '${msgContent}'`);
+        this.emit('messageRaw', message);
+
+        if (msgContent.startsWith(this.prefix)) {
+            const parts = msgContent.slice(this.prefix.length).trim().split(/\s+/);
+            const commandName = parts[0].toLowerCase();
+            const args = parts.slice(1);
+            const argsText = msgContent.slice(this.prefix.length + commandName.length).trim();
+
+            const handled = await this._executeCommand(message, commandName, args, argsText);
+
+            if (!handled) {
+                this.emit('message', message);
+            }
+        } else {
+            this.emit('message', message);
+        }
+    }
+
+    async _executeCommand(message, commandName, args, argsText) {
+        const commandHandler = this.commands.get(commandName);
+
+        if (commandHandler) {
+            let flockId = message.flock_id || this.flockNestMap[message.nest_id.toString()];
+
+            if (!flockId) {
+                await this._fetchFlockForNest(message.nest_id.toString());
+                flockId = this.flockNestMap[message.nest_id.toString()];
+
+                if (!flockId) {
+                    this._log(`⚠️ Could not find flock ID for nest ${message.nest_id}, can't respond`);
+                    return false;
+                }
+
+                message.flock_id = flockId;
+            }
path: javascript/sdk/corvy.js
user: login: infernostars
body: i think the api should always provide a flock id so you shouldnt need this fallback
created_at: 2025-05-16T06:08:54Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
path: javascript/sdk/corvy.js
user: login: zrodevkaan
body: Being genuine. Is this an actual question?

You are telling me each function shouldnt have permissions? Whitelist?
created_at: 2025-05-16T06:10:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
path: javascript/sdk/corvy.js
user: login: zrodevkaan
body: For permission checking.. ?
created_at: 2025-05-16T06:11:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
path: javascript/sdk/corvy.js
user: login: zrodevkaan
body: I forgot to remove that.
created_at: 2025-05-16T06:11:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
path: javascript/sdk/corvy.js
user: login: Dishpit
body: honestly we can make it even more generic? why stop at debugging logs? just call it _debugThing
created_at: 2025-05-16T06:11:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
path: javascript/sdk/corvy.js
user: login: Dishpit
body: i mean technically the api_base_url could be yoinked from somewhere too since that'll change, BUT we need to give the sdk devs AND the end user the opportunity to migrate properly instead of forcing a breaking change
created_at: 2025-05-16T06:12:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
path: javascript/sdk/corvy.js
user: login: Dishpit
body: true, this makes more sense as getToken
created_at: 2025-05-16T06:13:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
path: javascript/sdk/corvy.js
user: login: infernostars
body: yes but why is that built into the sdk directly and not something you can build around the sdk?
created_at: 2025-05-16T06:14:17Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
path: javascript/sdk/corvy.js
user: login: Dishpit
body: agreed; this seems kinda niche. but maybe it could be used?
created_at: 2025-05-16T06:14:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
path: javascript/sdk/corvy.js
user: login: zrodevkaan
body: for simplicity's sake
created_at: 2025-05-16T06:14:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
path: javascript/sdk/corvy.js
user: login: infernostars
body: ok fair i thought this one through a bit less than i should have, since ig youd want some functions to be unblocked obv

created_at: 2025-05-16T06:14:54Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
path: javascript/sdk/corvy.js
user: login: infernostars
body: i mean, if that's what you want, sure
created_at: 2025-05-16T06:15:23Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
path: javascript/sdk/corvy.js
user: login: infernostars
body: will resolve when removed 👍🏼 
created_at: 2025-05-16T06:15:44Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
+        if (!handler || typeof handler !== 'function') {
+            throw new Error('Command handler must be a function');
+        }
 
-  async _processMessages() {
-    this._log("Starting message loop...");
+        const nameList = Array.isArray(names) ? names : [names];
+
+        for (const name of nameList) {
+            if (typeof name !== 'string' || name.trim() === '') {
+                throw new Error('Command name must be a non-empty string');
+            }
+
+            const normalizedName = name.toLowerCase().trim();
+            this._log(`📝 Registered command: ${this.prefix}${normalizedName}`);
+
+            this.commands.set(normalizedName, {
+                staff: staffUserIds,
+                desc: desc,
+                hide: data.hide,
+                callback: async (msg, client, args) => {
+                    try {
+                        const allowedStaff = Array.isArray(staffUserIds) ? staffUserIds : [];
+
+                        const isStaff = allowedStaff.length === 0 || this.checkForStaff(msg.user.id, allowedStaff);
+                        if (!isStaff) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, `🚫 You are not permitted to use the **${this.prefix}${normalizedName}** command.`);
+                            return;
+                        }
+
+                        this._log(`📣 Executing command: ${this.prefix}${normalizedName}`);
+
+                        const response = await handler(msg, client, args);
+                        if (response) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, response);
+                        }
+                    } catch (error) {
+                        this._log(`❌ Error in command "${normalizedName}": ${error.message}`);
+                        this.emit('commandError', normalizedName, msg, error);
+                    }
+                }
+            });
 
-    while (true) {
-      try {
-        const res = await this.client.get("/messages", {
-          params: { cursor: this.currentCursor }
-        });
+        }
+    }
+
+    sendMessage(flockId, nestId, content) {
+        this.channel.push("send_message", {
+            flock_id: flockId.toString(),
+            nest_id: nestId.toString(),
+            content: content
+        })
+            .receive("ok", () => {
+                this._log(`📤 Sent message to nest ${nestId} in flock ${flockId}`);
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to send message: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to send message: ${JSON.stringify(response)}`));
+            });
+    }
 
-        const data = res.data;
-        if (data.cursor) {
-          this.currentCursor = data.cursor;
+    async _handleNewMessage(message) {
+        if (message.user?.is_bot) {
+            return;
         }
 
-        for (const msg of data.messages || []) {
-          if (msg.user.is_bot) {
-            continue;
-          }
+        const msgContent = message.content;
+        const nestId = message.nest_id;
+        const flockId = message.flock_id;
+        const sender = message.user?.username || 'unknown';
 
-          this.emit("messageRaw", msg);
-          this._log(`Received message "${msg.content}" from "${msg.user.username}" in "${msg.flock_name}/${msg.nest_name}"`);
+        if (!msgContent || !nestId) return;
 
-          const handled = await this._handleCommand(msg);
-          if (!handled) {
-            this.emit("message", msg);
-          }
+        if (flockId && nestId) {
+            this.flockNestMap[nestId.toString()] = flockId.toString();
         }
 
-        await new Promise(r => setTimeout(r, 1000));
-      } catch (err) {
-        this.emit("error", new Error("Error in message loop: " + err.message));
-        this._log(`Error in message loop: ${err.message}`);
-        await new Promise(r => setTimeout(r, 5000));
-      }
-    }
-  }
-
-  async _handleCommand(msg) {
-    const content = msg.content.toLowerCase();
-    for (const [prefix, handler] of this.commands.entries()) {
-      if (content.startsWith(prefix)) {
-        const argsText = msg.content.slice(prefix.length).trim();
-        await handler(msg, this, argsText);
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  // -------------------------------
-  // API Helpers
-  // -------------------------------
-
-  async _sendMsg(flockId, nestId, content) {
-    try {
-      this._log(`Sending message to flock:${flockId}, nest:${nestId} -> "${content}"`);
-      await this.client.post(`/flocks/${flockId}/nests/${nestId}/messages`, { content });
-    } catch (err) {
-      this.emit("error", new Error("Send message failed: " + err.message));
-      this._log(`Send message failed: ${err.message}`);
-    }
-  }
-
-  async getUserById(userId) {
-    try {
-      this._log(`Fetching user by ID: ${userId}`);
-      const response = await this.client.get(`/users/${userId}`);
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by id failed: " + err.message));
-      this._log(`Get user by id failed: ${err.message}`);
-    }
-  }
-
-  async getUserByUsername(username) {
-    try {
-      this._log(`Fetching user by username: ${username}`);
-      const response = await this.client.get(`/users/by-username/${username}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by username failed: " + err.message));
-      this._log(`Get user by username failed: ${err.message}`);
-    }
-  }
-
-  async getFlockById(flockId) {
-    try {
-      this._log(`Fetching flock by id: ${flockId}`);
-      const response = await this.client.get(`/flocks/${flockId}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get flock by id failed: " + err.message));
-      this._log(`Get flock by id failed: ${err.message}`);
-    }
-  }
-
-  // -------------------------------
-  // Stuff?
-  // -------------------------------
-
-  get uptime() {
-    return this.startTime ? Date.now() - this.startTime : 0;
-  }
-
-  get formattedUptime() {
-    const ms = this.uptime;
-    const sec = Math.floor((ms / 1000) % 60);
-    const min = Math.floor((ms / (1000 * 60)) % 60);
-    const hr = Math.floor((ms / (1000 * 60 * 60)) % 24);
-    const days = Math.floor(ms / (1000 * 60 * 60 * 24));
-    const parts = [];
-    
-    if (days) parts.push(`${days}d`);
-    if (hr) parts.push(`${hr}h`);
-    if (min) parts.push(`${min}m`);
-    if (sec || parts.length === 0) parts.push(`${sec}s`);
-
-    return parts.join(" ");
-  }
-};
\ No newline at end of file
+        this._log(`📨 Message in nest ${nestId} from ${sender}: '${msgContent}'`);
+        this.emit('messageRaw', message);
+
+        if (msgContent.startsWith(this.prefix)) {
+            const parts = msgContent.slice(this.prefix.length).trim().split(/\s+/);
+            const commandName = parts[0].toLowerCase();
+            const args = parts.slice(1);
+            const argsText = msgContent.slice(this.prefix.length + commandName.length).trim();
+
+            const handled = await this._executeCommand(message, commandName, args, argsText);
+
+            if (!handled) {
+                this.emit('message', message);
+            }
+        } else {
+            this.emit('message', message);
+        }
+    }
+
+    async _executeCommand(message, commandName, args, argsText) {
+        const commandHandler = this.commands.get(commandName);
+
+        if (commandHandler) {
+            let flockId = message.flock_id || this.flockNestMap[message.nest_id.toString()];
+
+            if (!flockId) {
+                await this._fetchFlockForNest(message.nest_id.toString());
+                flockId = this.flockNestMap[message.nest_id.toString()];
+
+                if (!flockId) {
+                    this._log(`⚠️ Could not find flock ID for nest ${message.nest_id}, can't respond`);
+                    return false;
+                }
+
+                message.flock_id = flockId;
+            }
path: javascript/sdk/corvy.js
user: login: Dishpit
body: yeah this shouldn't be here, flock id is transmitted with the messages
created_at: 2025-05-16T06:16:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
+        if (!handler || typeof handler !== 'function') {
+            throw new Error('Command handler must be a function');
+        }
 
-  async _processMessages() {
-    this._log("Starting message loop...");
+        const nameList = Array.isArray(names) ? names : [names];
+
+        for (const name of nameList) {
+            if (typeof name !== 'string' || name.trim() === '') {
+                throw new Error('Command name must be a non-empty string');
path: javascript/sdk/corvy.js
user: login: zrodevkaan
body: Not possible. `handler` will most likely be an anonymous function anyway which means no function name. Even if so I dont think adding safeguards for that would be worth it at all.
created_at: 2025-05-16T06:18:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
+        if (!handler || typeof handler !== 'function') {
+            throw new Error('Command handler must be a function');
+        }
 
-  async _processMessages() {
-    this._log("Starting message loop...");
+        const nameList = Array.isArray(names) ? names : [names];
+
+        for (const name of nameList) {
+            if (typeof name !== 'string' || name.trim() === '') {
+                throw new Error('Command name must be a non-empty string');
+            }
+
+            const normalizedName = name.toLowerCase().trim();
+            this._log(`📝 Registered command: ${this.prefix}${normalizedName}`);
+
+            this.commands.set(normalizedName, {
+                staff: staffUserIds,
+                desc: desc,
+                hide: data.hide,
+                callback: async (msg, client, args) => {
+                    try {
+                        const allowedStaff = Array.isArray(staffUserIds) ? staffUserIds : [];
+
+                        const isStaff = allowedStaff.length === 0 || this.checkForStaff(msg.user.id, allowedStaff);
+                        if (!isStaff) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, `🚫 You are not permitted to use the **${this.prefix}${normalizedName}** command.`);
+                            return;
+                        }
+
+                        this._log(`📣 Executing command: ${this.prefix}${normalizedName}`);
+
+                        const response = await handler(msg, client, args);
+                        if (response) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, response);
+                        }
+                    } catch (error) {
+                        this._log(`❌ Error in command "${normalizedName}": ${error.message}`);
+                        this.emit('commandError', normalizedName, msg, error);
+                    }
+                }
+            });
 
-    while (true) {
-      try {
-        const res = await this.client.get("/messages", {
-          params: { cursor: this.currentCursor }
-        });
+        }
+    }
+
+    sendMessage(flockId, nestId, content) {
+        this.channel.push("send_message", {
+            flock_id: flockId.toString(),
+            nest_id: nestId.toString(),
+            content: content
+        })
+            .receive("ok", () => {
+                this._log(`📤 Sent message to nest ${nestId} in flock ${flockId}`);
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to send message: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to send message: ${JSON.stringify(response)}`));
+            });
+    }
 
-        const data = res.data;
-        if (data.cursor) {
-          this.currentCursor = data.cursor;
+    async _handleNewMessage(message) {
+        if (message.user?.is_bot) {
+            return;
         }
 
-        for (const msg of data.messages || []) {
-          if (msg.user.is_bot) {
-            continue;
-          }
+        const msgContent = message.content;
+        const nestId = message.nest_id;
+        const flockId = message.flock_id;
+        const sender = message.user?.username || 'unknown';
 
-          this.emit("messageRaw", msg);
-          this._log(`Received message "${msg.content}" from "${msg.user.username}" in "${msg.flock_name}/${msg.nest_name}"`);
+        if (!msgContent || !nestId) return;
 
-          const handled = await this._handleCommand(msg);
-          if (!handled) {
-            this.emit("message", msg);
-          }
+        if (flockId && nestId) {
+            this.flockNestMap[nestId.toString()] = flockId.toString();
         }
 
-        await new Promise(r => setTimeout(r, 1000));
-      } catch (err) {
-        this.emit("error", new Error("Error in message loop: " + err.message));
-        this._log(`Error in message loop: ${err.message}`);
-        await new Promise(r => setTimeout(r, 5000));
-      }
-    }
-  }
-
-  async _handleCommand(msg) {
-    const content = msg.content.toLowerCase();
-    for (const [prefix, handler] of this.commands.entries()) {
-      if (content.startsWith(prefix)) {
-        const argsText = msg.content.slice(prefix.length).trim();
-        await handler(msg, this, argsText);
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  // -------------------------------
-  // API Helpers
-  // -------------------------------
-
-  async _sendMsg(flockId, nestId, content) {
-    try {
-      this._log(`Sending message to flock:${flockId}, nest:${nestId} -> "${content}"`);
-      await this.client.post(`/flocks/${flockId}/nests/${nestId}/messages`, { content });
-    } catch (err) {
-      this.emit("error", new Error("Send message failed: " + err.message));
-      this._log(`Send message failed: ${err.message}`);
-    }
-  }
-
-  async getUserById(userId) {
-    try {
-      this._log(`Fetching user by ID: ${userId}`);
-      const response = await this.client.get(`/users/${userId}`);
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by id failed: " + err.message));
-      this._log(`Get user by id failed: ${err.message}`);
-    }
-  }
-
-  async getUserByUsername(username) {
-    try {
-      this._log(`Fetching user by username: ${username}`);
-      const response = await this.client.get(`/users/by-username/${username}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by username failed: " + err.message));
-      this._log(`Get user by username failed: ${err.message}`);
-    }
-  }
-
-  async getFlockById(flockId) {
-    try {
-      this._log(`Fetching flock by id: ${flockId}`);
-      const response = await this.client.get(`/flocks/${flockId}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get flock by id failed: " + err.message));
-      this._log(`Get flock by id failed: ${err.message}`);
-    }
-  }
-
-  // -------------------------------
-  // Stuff?
-  // -------------------------------
-
-  get uptime() {
-    return this.startTime ? Date.now() - this.startTime : 0;
-  }
-
-  get formattedUptime() {
-    const ms = this.uptime;
-    const sec = Math.floor((ms / 1000) % 60);
-    const min = Math.floor((ms / (1000 * 60)) % 60);
-    const hr = Math.floor((ms / (1000 * 60 * 60)) % 24);
-    const days = Math.floor(ms / (1000 * 60 * 60 * 24));
-    const parts = [];
-    
-    if (days) parts.push(`${days}d`);
-    if (hr) parts.push(`${hr}h`);
-    if (min) parts.push(`${min}m`);
-    if (sec || parts.length === 0) parts.push(`${sec}s`);
-
-    return parts.join(" ");
-  }
-};
\ No newline at end of file
+        this._log(`📨 Message in nest ${nestId} from ${sender}: '${msgContent}'`);
+        this.emit('messageRaw', message);
path: javascript/sdk/corvy.js
user: login: zrodevkaan
body: No clue what you mean by this.

created_at: 2025-05-16T06:19:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
+        if (!handler || typeof handler !== 'function') {
+            throw new Error('Command handler must be a function');
+        }
 
-  async _processMessages() {
-    this._log("Starting message loop...");
+        const nameList = Array.isArray(names) ? names : [names];
+
+        for (const name of nameList) {
+            if (typeof name !== 'string' || name.trim() === '') {
+                throw new Error('Command name must be a non-empty string');
path: javascript/sdk/corvy.js
user: login: infernostars
body: fair enough
created_at: 2025-05-16T06:19:05Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
+        if (!handler || typeof handler !== 'function') {
+            throw new Error('Command handler must be a function');
+        }
 
-  async _processMessages() {
-    this._log("Starting message loop...");
+        const nameList = Array.isArray(names) ? names : [names];
+
+        for (const name of nameList) {
+            if (typeof name !== 'string' || name.trim() === '') {
+                throw new Error('Command name must be a non-empty string');
+            }
+
+            const normalizedName = name.toLowerCase().trim();
+            this._log(`📝 Registered command: ${this.prefix}${normalizedName}`);
+
+            this.commands.set(normalizedName, {
+                staff: staffUserIds,
+                desc: desc,
+                hide: data.hide,
+                callback: async (msg, client, args) => {
+                    try {
+                        const allowedStaff = Array.isArray(staffUserIds) ? staffUserIds : [];
+
+                        const isStaff = allowedStaff.length === 0 || this.checkForStaff(msg.user.id, allowedStaff);
+                        if (!isStaff) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, `🚫 You are not permitted to use the **${this.prefix}${normalizedName}** command.`);
+                            return;
+                        }
+
+                        this._log(`📣 Executing command: ${this.prefix}${normalizedName}`);
+
+                        const response = await handler(msg, client, args);
+                        if (response) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, response);
+                        }
+                    } catch (error) {
+                        this._log(`❌ Error in command "${normalizedName}": ${error.message}`);
+                        this.emit('commandError', normalizedName, msg, error);
+                    }
+                }
+            });
 
-    while (true) {
-      try {
-        const res = await this.client.get("/messages", {
-          params: { cursor: this.currentCursor }
-        });
+        }
+    }
+
+    sendMessage(flockId, nestId, content) {
+        this.channel.push("send_message", {
+            flock_id: flockId.toString(),
+            nest_id: nestId.toString(),
+            content: content
+        })
+            .receive("ok", () => {
+                this._log(`📤 Sent message to nest ${nestId} in flock ${flockId}`);
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to send message: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to send message: ${JSON.stringify(response)}`));
+            });
+    }
 
-        const data = res.data;
-        if (data.cursor) {
-          this.currentCursor = data.cursor;
+    async _handleNewMessage(message) {
+        if (message.user?.is_bot) {
+            return;
         }
 
-        for (const msg of data.messages || []) {
-          if (msg.user.is_bot) {
-            continue;
-          }
+        const msgContent = message.content;
+        const nestId = message.nest_id;
+        const flockId = message.flock_id;
+        const sender = message.user?.username || 'unknown';
 
-          this.emit("messageRaw", msg);
-          this._log(`Received message "${msg.content}" from "${msg.user.username}" in "${msg.flock_name}/${msg.nest_name}"`);
+        if (!msgContent || !nestId) return;
 
-          const handled = await this._handleCommand(msg);
-          if (!handled) {
-            this.emit("message", msg);
-          }
+        if (flockId && nestId) {
+            this.flockNestMap[nestId.toString()] = flockId.toString();
         }
 
-        await new Promise(r => setTimeout(r, 1000));
-      } catch (err) {
-        this.emit("error", new Error("Error in message loop: " + err.message));
-        this._log(`Error in message loop: ${err.message}`);
-        await new Promise(r => setTimeout(r, 5000));
-      }
-    }
-  }
-
-  async _handleCommand(msg) {
-    const content = msg.content.toLowerCase();
-    for (const [prefix, handler] of this.commands.entries()) {
-      if (content.startsWith(prefix)) {
-        const argsText = msg.content.slice(prefix.length).trim();
-        await handler(msg, this, argsText);
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  // -------------------------------
-  // API Helpers
-  // -------------------------------
-
-  async _sendMsg(flockId, nestId, content) {
-    try {
-      this._log(`Sending message to flock:${flockId}, nest:${nestId} -> "${content}"`);
-      await this.client.post(`/flocks/${flockId}/nests/${nestId}/messages`, { content });
-    } catch (err) {
-      this.emit("error", new Error("Send message failed: " + err.message));
-      this._log(`Send message failed: ${err.message}`);
-    }
-  }
-
-  async getUserById(userId) {
-    try {
-      this._log(`Fetching user by ID: ${userId}`);
-      const response = await this.client.get(`/users/${userId}`);
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by id failed: " + err.message));
-      this._log(`Get user by id failed: ${err.message}`);
-    }
-  }
-
-  async getUserByUsername(username) {
-    try {
-      this._log(`Fetching user by username: ${username}`);
-      const response = await this.client.get(`/users/by-username/${username}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by username failed: " + err.message));
-      this._log(`Get user by username failed: ${err.message}`);
-    }
-  }
-
-  async getFlockById(flockId) {
-    try {
-      this._log(`Fetching flock by id: ${flockId}`);
-      const response = await this.client.get(`/flocks/${flockId}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get flock by id failed: " + err.message));
-      this._log(`Get flock by id failed: ${err.message}`);
-    }
-  }
-
-  // -------------------------------
-  // Stuff?
-  // -------------------------------
-
-  get uptime() {
-    return this.startTime ? Date.now() - this.startTime : 0;
-  }
-
-  get formattedUptime() {
-    const ms = this.uptime;
-    const sec = Math.floor((ms / 1000) % 60);
-    const min = Math.floor((ms / (1000 * 60)) % 60);
-    const hr = Math.floor((ms / (1000 * 60 * 60)) % 24);
-    const days = Math.floor(ms / (1000 * 60 * 60 * 24));
-    const parts = [];
-    
-    if (days) parts.push(`${days}d`);
-    if (hr) parts.push(`${hr}h`);
-    if (min) parts.push(`${min}m`);
-    if (sec || parts.length === 0) parts.push(`${sec}s`);
-
-    return parts.join(" ");
-  }
-};
\ No newline at end of file
+        this._log(`📨 Message in nest ${nestId} from ${sender}: '${msgContent}'`);
+        this.emit('messageRaw', message);
+
+        if (msgContent.startsWith(this.prefix)) {
+            const parts = msgContent.slice(this.prefix.length).trim().split(/\s+/);
+            const commandName = parts[0].toLowerCase();
+            const args = parts.slice(1);
+            const argsText = msgContent.slice(this.prefix.length + commandName.length).trim();
+
+            const handled = await this._executeCommand(message, commandName, args, argsText);
+
+            if (!handled) {
+                this.emit('message', message);
+            }
+        } else {
+            this.emit('message', message);
+        }
+    }
+
+    async _executeCommand(message, commandName, args, argsText) {
+        const commandHandler = this.commands.get(commandName);
+
+        if (commandHandler) {
+            let flockId = message.flock_id || this.flockNestMap[message.nest_id.toString()];
+
+            if (!flockId) {
+                await this._fetchFlockForNest(message.nest_id.toString());
+                flockId = this.flockNestMap[message.nest_id.toString()];
+
+                if (!flockId) {
+                    this._log(`⚠️ Could not find flock ID for nest ${message.nest_id}, can't respond`);
+                    return false;
+                }
+
+                message.flock_id = flockId;
+            }
path: javascript/sdk/corvy.js
user: login: zrodevkaan
body: Commented it out.
created_at: 2025-05-16T06:19:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
path: javascript/sdk/corvy.js
user: login: Dishpit
body: pls fix
created_at: 2025-05-16T06:19:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
+        if (!handler || typeof handler !== 'function') {
+            throw new Error('Command handler must be a function');
+        }
 
-  async _processMessages() {
-    this._log("Starting message loop...");
+        const nameList = Array.isArray(names) ? names : [names];
+
+        for (const name of nameList) {
+            if (typeof name !== 'string' || name.trim() === '') {
+                throw new Error('Command name must be a non-empty string');
+            }
+
+            const normalizedName = name.toLowerCase().trim();
+            this._log(`📝 Registered command: ${this.prefix}${normalizedName}`);
+
+            this.commands.set(normalizedName, {
+                staff: staffUserIds,
+                desc: desc,
+                hide: data.hide,
+                callback: async (msg, client, args) => {
+                    try {
+                        const allowedStaff = Array.isArray(staffUserIds) ? staffUserIds : [];
+
+                        const isStaff = allowedStaff.length === 0 || this.checkForStaff(msg.user.id, allowedStaff);
+                        if (!isStaff) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, `🚫 You are not permitted to use the **${this.prefix}${normalizedName}** command.`);
+                            return;
+                        }
+
+                        this._log(`📣 Executing command: ${this.prefix}${normalizedName}`);
+
+                        const response = await handler(msg, client, args);
+                        if (response) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, response);
+                        }
+                    } catch (error) {
+                        this._log(`❌ Error in command "${normalizedName}": ${error.message}`);
+                        this.emit('commandError', normalizedName, msg, error);
+                    }
+                }
+            });
 
-    while (true) {
-      try {
-        const res = await this.client.get("/messages", {
-          params: { cursor: this.currentCursor }
-        });
+        }
+    }
+
+    sendMessage(flockId, nestId, content) {
+        this.channel.push("send_message", {
+            flock_id: flockId.toString(),
+            nest_id: nestId.toString(),
+            content: content
+        })
+            .receive("ok", () => {
+                this._log(`📤 Sent message to nest ${nestId} in flock ${flockId}`);
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to send message: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to send message: ${JSON.stringify(response)}`));
+            });
+    }
 
-        const data = res.data;
-        if (data.cursor) {
-          this.currentCursor = data.cursor;
+    async _handleNewMessage(message) {
+        if (message.user?.is_bot) {
+            return;
         }
 
-        for (const msg of data.messages || []) {
-          if (msg.user.is_bot) {
-            continue;
-          }
+        const msgContent = message.content;
+        const nestId = message.nest_id;
+        const flockId = message.flock_id;
+        const sender = message.user?.username || 'unknown';
 
-          this.emit("messageRaw", msg);
-          this._log(`Received message "${msg.content}" from "${msg.user.username}" in "${msg.flock_name}/${msg.nest_name}"`);
+        if (!msgContent || !nestId) return;
 
-          const handled = await this._handleCommand(msg);
-          if (!handled) {
-            this.emit("message", msg);
-          }
+        if (flockId && nestId) {
+            this.flockNestMap[nestId.toString()] = flockId.toString();
         }
 
-        await new Promise(r => setTimeout(r, 1000));
-      } catch (err) {
-        this.emit("error", new Error("Error in message loop: " + err.message));
-        this._log(`Error in message loop: ${err.message}`);
-        await new Promise(r => setTimeout(r, 5000));
-      }
-    }
-  }
-
-  async _handleCommand(msg) {
-    const content = msg.content.toLowerCase();
-    for (const [prefix, handler] of this.commands.entries()) {
-      if (content.startsWith(prefix)) {
-        const argsText = msg.content.slice(prefix.length).trim();
-        await handler(msg, this, argsText);
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  // -------------------------------
-  // API Helpers
-  // -------------------------------
-
-  async _sendMsg(flockId, nestId, content) {
-    try {
-      this._log(`Sending message to flock:${flockId}, nest:${nestId} -> "${content}"`);
-      await this.client.post(`/flocks/${flockId}/nests/${nestId}/messages`, { content });
-    } catch (err) {
-      this.emit("error", new Error("Send message failed: " + err.message));
-      this._log(`Send message failed: ${err.message}`);
-    }
-  }
-
-  async getUserById(userId) {
-    try {
-      this._log(`Fetching user by ID: ${userId}`);
-      const response = await this.client.get(`/users/${userId}`);
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by id failed: " + err.message));
-      this._log(`Get user by id failed: ${err.message}`);
-    }
-  }
-
-  async getUserByUsername(username) {
-    try {
-      this._log(`Fetching user by username: ${username}`);
-      const response = await this.client.get(`/users/by-username/${username}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by username failed: " + err.message));
-      this._log(`Get user by username failed: ${err.message}`);
-    }
-  }
-
-  async getFlockById(flockId) {
-    try {
-      this._log(`Fetching flock by id: ${flockId}`);
-      const response = await this.client.get(`/flocks/${flockId}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get flock by id failed: " + err.message));
-      this._log(`Get flock by id failed: ${err.message}`);
-    }
-  }
-
-  // -------------------------------
-  // Stuff?
-  // -------------------------------
-
-  get uptime() {
-    return this.startTime ? Date.now() - this.startTime : 0;
-  }
-
-  get formattedUptime() {
-    const ms = this.uptime;
-    const sec = Math.floor((ms / 1000) % 60);
-    const min = Math.floor((ms / (1000 * 60)) % 60);
-    const hr = Math.floor((ms / (1000 * 60 * 60)) % 24);
-    const days = Math.floor(ms / (1000 * 60 * 60 * 24));
-    const parts = [];
-    
-    if (days) parts.push(`${days}d`);
-    if (hr) parts.push(`${hr}h`);
-    if (min) parts.push(`${min}m`);
-    if (sec || parts.length === 0) parts.push(`${sec}s`);
-
-    return parts.join(" ");
-  }
-};
\ No newline at end of file
+        this._log(`📨 Message in nest ${nestId} from ${sender}: '${msgContent}'`);
+        this.emit('messageRaw', message);
+
+        if (msgContent.startsWith(this.prefix)) {
+            const parts = msgContent.slice(this.prefix.length).trim().split(/\s+/);
+            const commandName = parts[0].toLowerCase();
+            const args = parts.slice(1);
+            const argsText = msgContent.slice(this.prefix.length + commandName.length).trim();
+
+            const handled = await this._executeCommand(message, commandName, args, argsText);
+
+            if (!handled) {
+                this.emit('message', message);
+            }
+        } else {
+            this.emit('message', message);
+        }
+    }
+
+    async _executeCommand(message, commandName, args, argsText) {
+        const commandHandler = this.commands.get(commandName);
+
+        if (commandHandler) {
+            let flockId = message.flock_id || this.flockNestMap[message.nest_id.toString()];
+
+            if (!flockId) {
+                await this._fetchFlockForNest(message.nest_id.toString());
+                flockId = this.flockNestMap[message.nest_id.toString()];
+
+                if (!flockId) {
+                    this._log(`⚠️ Could not find flock ID for nest ${message.nest_id}, can't respond`);
+                    return false;
+                }
+
+                message.flock_id = flockId;
+            }
path: javascript/sdk/corvy.js
user: login: infernostars
body: you only commented out the inner loop when you shouldn't need to use the nestmap at all [blame github for not showing the first line]
created_at: 2025-05-16T06:21:00Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,351 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
+        if (!handler || typeof handler !== 'function') {
+            throw new Error('Command handler must be a function');
+        }
 
-  async _processMessages() {
-    this._log("Starting message loop...");
+        const nameList = Array.isArray(names) ? names : [names];
+
+        for (const name of nameList) {
+            if (typeof name !== 'string' || name.trim() === '') {
+                throw new Error('Command name must be a non-empty string');
+            }
+
+            const normalizedName = name.toLowerCase().trim();
+            this._log(`📝 Registered command: ${this.prefix}${normalizedName}`);
+
+            this.commands.set(normalizedName, {
+                staff: staffUserIds,
+                desc: desc,
+                hide: data.hide,
+                callback: async (msg, client, args) => {
+                    try {
+                        const allowedStaff = Array.isArray(staffUserIds) ? staffUserIds : [];
+
+                        const isStaff = allowedStaff.length === 0 || this.checkForStaff(msg.user.id, allowedStaff);
+                        if (!isStaff) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, `🚫 You are not permitted to use the **${this.prefix}${normalizedName}** command.`);
+                            return;
+                        }
+
+                        this._log(`📣 Executing command: ${this.prefix}${normalizedName}`);
+
+                        const response = await handler(msg, client, args);
+                        if (response) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, response);
+                        }
+                    } catch (error) {
+                        this._log(`❌ Error in command "${normalizedName}": ${error.message}`);
+                        this.emit('commandError', normalizedName, msg, error);
+                    }
+                }
+            });
 
-    while (true) {
-      try {
-        const res = await this.client.get("/messages", {
-          params: { cursor: this.currentCursor }
-        });
+        }
+    }
 
-        const data = res.data;
-        if (data.cursor) {
-          this.currentCursor = data.cursor;
+    sendMessage(flockId, nestId, content) {
+        this.channel.push("send_message", {
+            flock_id: flockId.toString(),
+            nest_id: nestId.toString(),
+            content: content
+        })
+            .receive("ok", () => {
+                this._log(`📤 Sent message to nest ${nestId} in flock ${flockId}`);
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to send message: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to send message: ${JSON.stringify(response)}`));
+            });
+    }
+
+    async _handleNewMessage(message) {
+        if (message.user?.is_bot) {
+            return;
         }
 
-        for (const msg of data.messages || []) {
-          if (msg.user.is_bot) {
-            continue;
-          }
+        const msgContent = message.content;
+        const nestId = message.nest_id;
+        const flockId = message.flock_id;
+        const sender = message.user?.username || 'unknown';
 
-          this.emit("messageRaw", msg);
-          this._log(`Received message "${msg.content}" from "${msg.user.username}" in "${msg.flock_name}/${msg.nest_name}"`);
+        if (!msgContent || !nestId) return;
 
-          const handled = await this._handleCommand(msg);
-          if (!handled) {
-            this.emit("message", msg);
-          }
+        if (flockId && nestId) {
+            this.flockNestMap[nestId.toString()] = flockId.toString();
         }
 
-        await new Promise(r => setTimeout(r, 1000));
-      } catch (err) {
-        this.emit("error", new Error("Error in message loop: " + err.message));
-        this._log(`Error in message loop: ${err.message}`);
-        await new Promise(r => setTimeout(r, 5000));
-      }
-    }
-  }
-
-  async _handleCommand(msg) {
-    const content = msg.content.toLowerCase();
-    for (const [prefix, handler] of this.commands.entries()) {
-      if (content.startsWith(prefix)) {
-        const argsText = msg.content.slice(prefix.length).trim();
-        await handler(msg, this, argsText);
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  // -------------------------------
-  // API Helpers
-  // -------------------------------
-
-  async _sendMsg(flockId, nestId, content) {
-    try {
-      this._log(`Sending message to flock:${flockId}, nest:${nestId} -> "${content}"`);
-      await this.client.post(`/flocks/${flockId}/nests/${nestId}/messages`, { content });
-    } catch (err) {
-      this.emit("error", new Error("Send message failed: " + err.message));
-      this._log(`Send message failed: ${err.message}`);
-    }
-  }
-
-  async getUserById(userId) {
-    try {
-      this._log(`Fetching user by ID: ${userId}`);
-      const response = await this.client.get(`/users/${userId}`);
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by id failed: " + err.message));
-      this._log(`Get user by id failed: ${err.message}`);
-    }
-  }
-
-  async getUserByUsername(username) {
-    try {
-      this._log(`Fetching user by username: ${username}`);
-      const response = await this.client.get(`/users/by-username/${username}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by username failed: " + err.message));
-      this._log(`Get user by username failed: ${err.message}`);
-    }
-  }
-
-  async getFlockById(flockId) {
-    try {
-      this._log(`Fetching flock by id: ${flockId}`);
-      const response = await this.client.get(`/flocks/${flockId}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get flock by id failed: " + err.message));
-      this._log(`Get flock by id failed: ${err.message}`);
-    }
-  }
-
-  // -------------------------------
-  // Stuff?
-  // -------------------------------
-
-  get uptime() {
-    return this.startTime ? Date.now() - this.startTime : 0;
-  }
-
-  get formattedUptime() {
-    const ms = this.uptime;
-    const sec = Math.floor((ms / 1000) % 60);
-    const min = Math.floor((ms / (1000 * 60)) % 60);
-    const hr = Math.floor((ms / (1000 * 60 * 60)) % 24);
-    const days = Math.floor(ms / (1000 * 60 * 60 * 24));
-    const parts = [];
-    
-    if (days) parts.push(`${days}d`);
-    if (hr) parts.push(`${hr}h`);
-    if (min) parts.push(`${min}m`);
-    if (sec || parts.length === 0) parts.push(`${sec}s`);
-
-    return parts.join(" ");
-  }
-};
\ No newline at end of file
+        this._log(`📨 Message in nest ${nestId} from ${sender}: '${msgContent}'`);
+        this.emit('messageRaw', message);
+
+        if (msgContent.startsWith(this.prefix)) {
+            const parts = msgContent.slice(this.prefix.length).trim().split(/\s+/);
+            const commandName = parts[0].toLowerCase();
+            const args = parts.slice(1);
+            const argsText = msgContent.slice(this.prefix.length + commandName.length).trim();
+
+            const handled = await this._executeCommand(message, commandName, args, argsText);
+
+            if (!handled) {
+                this.emit('message', message);
+            }
+        } else {
+            this.emit('message', message);
+        }
+    }
+
+    async _executeCommand(message, commandName, args, argsText) {
+        const commandHandler = this.commands.get(commandName);
+
+        if (commandHandler) {
+            let flockId = message.flock_id || this.flockNestMap[message.nest_id.toString()];
+
+            if (!flockId) {
+                await this._fetchFlockForNest(message.nest_id.toString());
+                flockId = this.flockNestMap[message.nest_id.toString()];
+
+                /* Keeping incase dishy does some API stuff again. */
path: javascript/sdk/corvy.js
user: login: Dishpit
body: \>:(

ligma fuckin nuhtz
created_at: 2025-05-16T06:21:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
+
+    async _initialize() {
         try {
-          this._log(`Executing command: ${fullPrefix}`);
-          const data = await handler(msg, client, argsText);
-          if (data) {
-            this._sendMsg(msg.flock_id, msg.nest_id, data);
-          }
-        } catch (err) {
-          this.emit("commandError", fullPrefix, msg, err);
-          this._log(`Error in command "${fullPrefix}": ${err.message}`);
+            const response = await this.axiosInstance.post(`${this.API_BASE_URL}/auth`);
+
+            this.bot = response.data.bot;
+        }
+        catch (ex) {
+            console.log(ex)
         }
-      });
 
-      this._log(`Registered command: ${fullPrefix}`);
+        await this._connectSocket();
+        this._setupProcessHandlers();
     }
-  }
 
-  // -------------------------------
-  // Auth
-  // -------------------------------
+    async _connectSocket() {
+        this.socket = new Socket(this.WEBSOCKET_URL, {
+            params: { token: this.token },
+            heartbeatIntervalMs: 25000,
+            transport: WebSocket
+        });
 
-  async login() {
-    try {
-      const res = await this.client.post("/auth");
-      this.user = res.data.bot;
+        this.socket.connect();
 
-      await this.flocks.fetch();
+        this.socket.onOpen(() => {
+            this._log('✅ Socket connection established');
+            this.emit('ready', this.bot);
+        });
+        this.socket.onError((error) => {
+            this._log(`❌ Socket error: ${error.message}`);
+            this.emit('error', new Error(`Socket error: ${error.message}`));
+        });
+        this.socket.onClose((event) => this._log(`Socket connection closed: ${event.code} - ${event.reason}`));
 
-      this.emit("ready", this);
+        this._joinBotChannel();
+    }
 
-      this.startTime = Date.now();
+    _joinBotChannel() {
+        this.channel = this.socket.channel("bot:any", { token: this.token });
 
-      const baseline = await this.client.get("/messages", {
-        params: { cursor: 0 }
-      });
+        this.channel.on("message", (payload) => {
+            if (payload.event === 'new_message') {
+                this._handleNewMessage(payload.message);
+            }
+        });
 
-      this.currentCursor = baseline.data.cursor || 0;
+        this.channel.join()
+            .receive("ok", (response) => {
+                this._log('✅ Successfully joined bot channel');
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to join bot channel: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to join bot channel: ${JSON.stringify(response)}`));
+            });
+    }
 
-      this._processMessages();
+    _setupProcessHandlers() {
+        process.on('SIGINT', () => {
+            this._log('\nShutting down...');
+            if (this.socket) {
+                this.socket.disconnect();
+            }
+            process.exit(0);
+        });
+    }
 
-      process.on("SIGINT", () => {
-        console.log("Client shutting down...");
-        process.exit(0);
-      });
-    } catch (err) {
-      this.emit("error", new Error("Error logging in: " + err.message));
-      this._log(`Error logging in: ${err.message}`);
+    checkForStaff(userid, staffs) {
+        return staffs?.includes(userid)
     }
-  }
 
-  // -------------------------------
-  // Message Loop
-  // -------------------------------
+    registerCommand(data, handler) {
+        const names = data.names ?? data
+        const desc = data.desc
+        const staffUserIds = data.staff
+        if (!handler || typeof handler !== 'function') {
+            throw new Error('Command handler must be a function');
+        }
 
-  async _processMessages() {
-    this._log("Starting message loop...");
+        const nameList = Array.isArray(names) ? names : [names];
+
+        for (const name of nameList) {
+            if (typeof name !== 'string' || name.trim() === '') {
+                throw new Error('Command name must be a non-empty string');
+            }
+
+            const normalizedName = name.toLowerCase().trim();
+            this._log(`📝 Registered command: ${this.prefix}${normalizedName}`);
+
+            this.commands.set(normalizedName, {
+                staff: staffUserIds,
+                desc: desc,
+                hide: data.hide,
+                callback: async (msg, client, args) => {
+                    try {
+                        const allowedStaff = Array.isArray(staffUserIds) ? staffUserIds : [];
+
+                        const isStaff = allowedStaff.length === 0 || this.checkForStaff(msg.user.id, allowedStaff);
+                        if (!isStaff) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, `🚫 You are not permitted to use the **${this.prefix}${normalizedName}** command.`);
+                            return;
+                        }
+
+                        this._log(`📣 Executing command: ${this.prefix}${normalizedName}`);
+
+                        const response = await handler(msg, client, args);
+                        if (response) {
+                            await this.sendMessage(msg.flock_id, msg.nest_id, response);
+                        }
+                    } catch (error) {
+                        this._log(`❌ Error in command "${normalizedName}": ${error.message}`);
+                        this.emit('commandError', normalizedName, msg, error);
+                    }
+                }
+            });
 
-    while (true) {
-      try {
-        const res = await this.client.get("/messages", {
-          params: { cursor: this.currentCursor }
-        });
+        }
+    }
+
+    sendMessage(flockId, nestId, content) {
+        this.channel.push("send_message", {
+            flock_id: flockId.toString(),
+            nest_id: nestId.toString(),
+            content: content
+        })
+            .receive("ok", () => {
+                this._log(`📤 Sent message to nest ${nestId} in flock ${flockId}`);
+            })
+            .receive("error", (response) => {
+                this._log(`❌ Failed to send message: ${JSON.stringify(response)}`);
+                this.emit('error', new Error(`Failed to send message: ${JSON.stringify(response)}`));
+            });
+    }
 
-        const data = res.data;
-        if (data.cursor) {
-          this.currentCursor = data.cursor;
+    async _handleNewMessage(message) {
+        if (message.user?.is_bot) {
+            return;
         }
 
-        for (const msg of data.messages || []) {
-          if (msg.user.is_bot) {
-            continue;
-          }
+        const msgContent = message.content;
+        const nestId = message.nest_id;
+        const flockId = message.flock_id;
+        const sender = message.user?.username || 'unknown';
 
-          this.emit("messageRaw", msg);
-          this._log(`Received message "${msg.content}" from "${msg.user.username}" in "${msg.flock_name}/${msg.nest_name}"`);
+        if (!msgContent || !nestId) return;
 
-          const handled = await this._handleCommand(msg);
-          if (!handled) {
-            this.emit("message", msg);
-          }
+        if (flockId && nestId) {
+            this.flockNestMap[nestId.toString()] = flockId.toString();
         }
 
-        await new Promise(r => setTimeout(r, 1000));
-      } catch (err) {
-        this.emit("error", new Error("Error in message loop: " + err.message));
-        this._log(`Error in message loop: ${err.message}`);
-        await new Promise(r => setTimeout(r, 5000));
-      }
-    }
-  }
-
-  async _handleCommand(msg) {
-    const content = msg.content.toLowerCase();
-    for (const [prefix, handler] of this.commands.entries()) {
-      if (content.startsWith(prefix)) {
-        const argsText = msg.content.slice(prefix.length).trim();
-        await handler(msg, this, argsText);
-
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  // -------------------------------
-  // API Helpers
-  // -------------------------------
-
-  async _sendMsg(flockId, nestId, content) {
-    try {
-      this._log(`Sending message to flock:${flockId}, nest:${nestId} -> "${content}"`);
-      await this.client.post(`/flocks/${flockId}/nests/${nestId}/messages`, { content });
-    } catch (err) {
-      this.emit("error", new Error("Send message failed: " + err.message));
-      this._log(`Send message failed: ${err.message}`);
-    }
-  }
-
-  async getUserById(userId) {
-    try {
-      this._log(`Fetching user by ID: ${userId}`);
-      const response = await this.client.get(`/users/${userId}`);
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by id failed: " + err.message));
-      this._log(`Get user by id failed: ${err.message}`);
-    }
-  }
-
-  async getUserByUsername(username) {
-    try {
-      this._log(`Fetching user by username: ${username}`);
-      const response = await this.client.get(`/users/by-username/${username}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get user by username failed: " + err.message));
-      this._log(`Get user by username failed: ${err.message}`);
-    }
-  }
-
-  async getFlockById(flockId) {
-    try {
-      this._log(`Fetching flock by id: ${flockId}`);
-      const response = await this.client.get(`/flocks/${flockId}`);
-
-      return response.data;
-    } catch (err) {
-      this.emit("error", new Error("Get flock by id failed: " + err.message));
-      this._log(`Get flock by id failed: ${err.message}`);
-    }
-  }
-
-  // -------------------------------
-  // Stuff?
-  // -------------------------------
-
-  get uptime() {
-    return this.startTime ? Date.now() - this.startTime : 0;
-  }
-
-  get formattedUptime() {
-    const ms = this.uptime;
-    const sec = Math.floor((ms / 1000) % 60);
-    const min = Math.floor((ms / (1000 * 60)) % 60);
-    const hr = Math.floor((ms / (1000 * 60 * 60)) % 24);
-    const days = Math.floor(ms / (1000 * 60 * 60 * 24));
-    const parts = [];
-    
-    if (days) parts.push(`${days}d`);
-    if (hr) parts.push(`${hr}h`);
-    if (min) parts.push(`${min}m`);
-    if (sec || parts.length === 0) parts.push(`${sec}s`);
-
-    return parts.join(" ");
-  }
-};
\ No newline at end of file
+        this._log(`📨 Message in nest ${nestId} from ${sender}: '${msgContent}'`);
+        this.emit('messageRaw', message);
path: javascript/sdk/corvy.js
user: login: infernostars
body: line 201 removes all bot messages, before messageRaw is emitted:
```javascript
if (message.user?.is_bot) {
    return;
}
```
created_at: 2025-05-16T06:22:03Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
+
+    _generateHelpMessage() {
+        const commands = Array.from(this.commands);
+        const commandSet = commands.map(([name, data]) => ({
+            name,
+            desc: data.desc || "No description provided."
+        })).filter(cmd => !cmd.hide);
+
+        const maxNameLength = Math.max(...commandSet.map(cmd => (this.prefix + cmd.name).length));
+
+        const commandsMessage = commandSet.map(cmd => {
+            const paddedName = (this.prefix + cmd.name).padEnd(maxNameLength, ' ');
+            return `• ${paddedName} → ${cmd.desc}`;
+        });
+
+        return [`📜 **Command List**`, ...commandsMessage]
+    }
+
+
+    _log(message) {
+        if (this.devMode) {
+            console.log(`[DEBUG] ${message}`);
+        }
+    }
path: javascript/sdk/corvy.js
user: login: infernostars
body: _debugSomeObjectPlease()
created_at: 2025-05-16T06:24:29Z
author_association: COLLABORATOR
diff_hunk: @@ -1,251 +1,354 @@
-import axios from "axios";
-import EventEmitter from "events";
-
-class ClientFlocks {
-  constructor(client) {
-    this.client = client;
-    this._list = [];
-  }
-
-  async fetch() {
-    try {
-      this.client._log(`Fetching ${this.client.user.name}'s flocks`);
-      const response = await this.client.client.get("/flocks");
-
-      this._list = Array.isArray(response.data) ? response.data : response.data.flocks;
-    } catch (err) {
-      this.client._log(`Failed to fetch flocks: ${err.message}`);
-      this._list = [];
-    }
-  }
-
-  get list() {
-    return this._list;
-  }
-
-  get size() {
-    return this._list.length;
-  }
-}
-
-export class Client extends EventEmitter {
-  constructor({ token, prefix = ";", devMode = false }) {
-    super();
-    this.token = token;
-    this.prefix = prefix;
-    this.devMode = devMode;
-    this.currentCursor = 0;
-    this.commands = new Map();
-    this.user = null;
-    this.flocks = new ClientFlocks(this);
-    this.startTime = null;
-    this.client = axios.create({
-      baseURL: "https://corvy.chat/api/v1",
-      headers: {
-        Authorization: `Bearer ${token}`,
-        "Content-Type": "application/json"
-      }
-    });
-  }
-
-  // -------------------------------
-  // Utils
-  // -------------------------------
-
-  _log(...args) {
-    if (this.devMode) {
-      console.log("[DEV MODE]", ...args);
-    }
-  }
-  
-  // -------------------------------
-  // Command Registration
-  // -------------------------------
-
-  registerCommand(names, handler) {
-    const nameList = Array.isArray(names) ? names : [names];
-
-    for (let name of nameList) {
-      if (typeof name !== "string" || typeof handler !== "function") {
-        throw new Error("Command must be registered with a name and a function handler.");
-      }
-
-      const fullPrefix = `${this.prefix}${name}`;
-      this.commands.set(fullPrefix.toLowerCase(), async (msg, client, argsText) => {
+import WebSocket from 'ws';
+import axios from 'axios';
+import { EventEmitter } from 'events';
+import { Socket } from 'phoenix';
+
+export default class Client extends EventEmitter {
+    constructor({ prefix = '!', token, devMode = false, help = false }) {
+        super();
+        this.prefix = prefix;
+        this.token = token;
+        this.devMode = devMode;
+        this.help = help;
+        this.flockNestMap = {};
+        this.socket = null;
+        this.channel = null;
+        this.commands = new Map();
+        this.startTime = Date.now();
+
+        this.API_BASE_URL = 'https://corvy.chat/api/v2';
+        this.WEBSOCKET_URL = 'wss://corvy.chat/api/v2/socket';
+
+        this.axiosInstance = axios.create({
+            baseURL: this.API_BASE_URL,
+            headers: {
+                Authorization: `Bearer ${token}`,
+                "Content-Type": "application/json"
+            }
+        });
+
+        process.nextTick(async () => {
+            await this._initialize();
+
+            if (this.help) {
+                const helpMessage = this._generateHelpMessage().join('<br />');
+                this.registerCommand('help', async () => helpMessage);
+            }
+        });
+    }
+
+    getPrefix() {
+        return this.token;
+    }
path: javascript/sdk/corvy.js
user: login: infernostars
body: > pls fix

dishy it got removed
created_at: 2025-05-16T06:24:49Z
author_association: COLLABORATOR
diff_hunk: @@ -23,6 +23,4 @@ for (let file of commandFiles) {
     const command = await import(`./commands/${file}`);
     
     client.registerCommand([command.name, ...(command.aliases ?? [])], command.execute);
-}
-
-client.login();
path: javascript/docs/command-handler-example/simple-bot.js
user: login: zrodevkaan
body: Not needed. The client handles it automatically.
created_at: 2025-05-16T06:24:55Z
author_association: CONTRIBUTOR

PR comments:
user: login: Dishpit
created_at: 2025-05-16T06:00:44Z
body: why is this one single commit
author_association: CONTRIBUTOR
user: login: zrodevkaan
created_at: 2025-05-16T06:01:03Z
body: console logs

author_association: CONTRIBUTOR