Building Your First NDC Bot
In this guide, you will build a simple “Ping-Pong” bot for NDC. When a user sends !ping in any channel your bot can see, the bot will immediately reply with Pong! 🏓.
How NDC Bots Work
Section titled “How NDC Bots Work”NDC uses a modern, two-part architecture for bots:
- The Gateway (WebSocket): A persistent connection where NDC pushes real-time events to your bot (e.g.
MESSAGE_CREATE,READY,GUILD_CREATE). - The REST API (HTTP): Standard HTTP endpoints for actions (e.g. sending a message, creating a channel, adding reactions).
Prerequisites
Section titled “Prerequisites”Before writing code, make sure you have:
- Created an Application in the Developer Portal and copied your Bot Token.
- Invited your bot to an NDC server with the Send Messages permission.
- Node.js (v18+) or Python (v3.10+) installed on your machine.
Choose Your Language
Section titled “Choose Your Language”1. Initialize Your Project
Section titled “1. Initialize Your Project”Open a terminal, create a new folder, and initialize a Node.js project:
mkdir ndc-ping-botcd ndc-ping-botnpm init -ynpm install ws dotenv2. Configure Your Environment
Section titled “2. Configure Your Environment”Create a .env file to securely store your token:
NDC_BOT_TOKEN=your_bot_token_hereNDC_API_URL=https://chat.nexoriadevelopment.com/api/v1NDC_GATEWAY_URL=wss://chat.nexoriadevelopment.com/gateway?v=1&encoding=json3. Create bot.js
Section titled “3. Create bot.js”Create a file named bot.js and paste the following code:
require('dotenv').config();const WebSocket = require('ws');
const TOKEN = process.env.NDC_BOT_TOKEN;const API_URL = process.env.NDC_API_URL;const GATEWAY_URL = process.env.NDC_GATEWAY_URL;
if (!TOKEN) { console.error("Error: NDC_BOT_TOKEN is missing in your .env file!"); process.exit(1);}
// 1. Helper function to send messages via NDC REST APIasync function sendMessage(channelId, content) { try { const res = await fetch(`${API_URL}/channels/${channelId}/messages`, { method: 'POST', headers: { 'Authorization': `Bot ${TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ content }), });
if (!res.ok) { const err = await res.text(); console.error(`Failed to send message (${res.status}):`, err); } } catch (error) { console.error('Error sending message:', error); }}
// 2. Connect to the NDC Gateway WebSocketconsole.log('Connecting to NDC Gateway...');const ws = new WebSocket(GATEWAY_URL);let heartbeatTimer = null;let lastSequence = null;
ws.on('open', () => { console.log('Connected to Gateway socket!');});
ws.on('message', async (data) => { const payload = JSON.parse(data.toString()); const { op, d, s, t } = payload;
if (s !== null) { lastSequence = s; }
// Opcode 10: Hello -> Start Heartbeating & Send Identify if (op === 10) { const { heartbeat_interval } = d; console.log(`Received Hello! Heartbeat interval: ${heartbeat_interval}ms`);
// Start sending periodic heartbeats (Opcode 1) heartbeatTimer = setInterval(() => { ws.send(JSON.stringify({ op: 1, d: lastSequence })); }, heartbeat_interval);
// Send Identify (Opcode 2) to log in with your Bot Token ws.send(JSON.stringify({ op: 2, d: { token: TOKEN, properties: { os: process.platform, browser: 'ndc-simple-bot', device: 'ndc-simple-bot', }, }, })); }
// Opcode 0: Dispatch Events if (op === 0) { // When the bot successfully logs in if (t === 'READY') { console.log(`Logged in as ${d.user.username}#${d.user.discriminator || '0'}!`); console.log('Bot is online and ready to listen for messages.'); }
// When a message is sent in any channel if (t === 'MESSAGE_CREATE') { // Ignore messages sent by other bots or ourself if (d.author.bot) return;
const content = d.content ? d.content.trim() : '';
// Check for !ping command if (content.toLowerCase() === '!ping') { console.log(`Received !ping in channel ${d.channel_id} from ${d.author.username}`); await sendMessage(d.channel_id, 'Pong! 🏓 Greetings from my first NDC Bot!'); } } }});
ws.on('close', (code, reason) => { clearInterval(heartbeatTimer); console.log(`Gateway connection closed (${code}): ${reason.toString()}`);});
ws.on('error', (err) => { console.error('Gateway WebSocket error:', err);});4. Run Your Bot!
Section titled “4. Run Your Bot!”node bot.jsYou will see:
Connecting to NDC Gateway...Connected to Gateway socket!Received Hello! Heartbeat interval: 41250msLogged in as My Helper Bot!Bot is online and ready to listen for messages.Go to your NDC server, type !ping in any channel the bot has access to, and watch it reply with Pong! 🏓!
1. Initialize Your Project
Section titled “1. Initialize Your Project”mkdir ndc-ping-botcd ndc-ping-botpython -m venv .venvsource .venv/bin/activate # On Windows: .venv\Scripts\activatepip install websockets requests python-dotenv2. Configure Your Environment
Section titled “2. Configure Your Environment”Create a .env file:
NDC_BOT_TOKEN=your_bot_token_hereNDC_API_URL=https://chat.nexoriadevelopment.com/api/v1NDC_GATEWAY_URL=wss://chat.nexoriadevelopment.com/gateway?v=1&encoding=json3. Create bot.py
Section titled “3. Create bot.py”import osimport jsonimport asyncioimport requestsimport websocketsfrom dotenv import load_dotenv
load_dotenv()
TOKEN = os.getenv("NDC_BOT_TOKEN")API_URL = os.getenv("NDC_API_URL", "https://chat.nexoriadevelopment.com/api/v1")GATEWAY_URL = os.getenv("NDC_GATEWAY_URL", "wss://chat.nexoriadevelopment.com/gateway?v=1&encoding=json")
def send_message(channel_id, content): """Sends a message to an NDC channel using the REST API.""" url = f"{API_URL}/channels/{channel_id}/messages" headers = { "Authorization": f"Bot {TOKEN}", "Content-Type": "application/json" } payload = {"content": content} resp = requests.post(url, headers=headers, json=payload) if not resp.ok: print(f"Failed to send message: {resp.status_code} {resp.text}")
async def heartbeat(ws, interval_ms): """Sends periodic heartbeats to maintain the Gateway session.""" while True: await asyncio.sleep(interval_ms / 1000) await ws.send(json.dumps({"op": 1, "d": None}))
async def run_bot(): print("Connecting to NDC Gateway...") async with websockets.connect(GATEWAY_URL) as ws: async for message in ws: payload = json.loads(message) op = payload.get("op") d = payload.get("d") t = payload.get("t")
# Opcode 10: Hello -> Start heartbeat & identify if op == 10: interval = d["heartbeat_interval"] asyncio.create_task(heartbeat(ws, interval))
# Identify with your Bot Token identify = { "op": 2, "d": { "token": TOKEN, "properties": { "os": "linux", "browser": "ndc-python-bot", "device": "ndc-python-bot" } } } await ws.send(json.dumps(identify))
# Opcode 0: Dispatch events elif op == 0: if t == "READY": bot_name = d["user"]["username"] print(f"Logged in as {bot_name}! Bot is ready.")
elif t == "MESSAGE_CREATE": # Ignore bot messages if d.get("author", {}).get("bot"): continue
content = d.get("content", "").strip() channel_id = d.get("channel_id")
if content.lower() == "!ping": print(f"Received !ping from {d['author']['username']}") send_message(channel_id, "Pong! 🏓 Greetings from Python!")
if __name__ == "__main__": asyncio.run(run_bot())4. Run Your Bot!
Section titled “4. Run Your Bot!”python bot.pyExpanding Your Bot
Section titled “Expanding Your Bot”Now that your bot can listen for messages and respond, check out the API guides to add more features:
- Sending Embeds & Attachments: Learn how to format rich embeds and upload attachments.
- Channel & Role Management: Create channels, modify topic descriptions, or assign roles.
- Gateway Event Reference: Explore other events like
GUILD_MEMBER_ADD,MESSAGE_REACTION_ADD, and more!
