Skip to content

Commit

Permalink
Merge pull request #320 from CommandDash/discord-bot
Browse files Browse the repository at this point in the history
Discord bot
  • Loading branch information
samyakkkk authored Jul 23, 2024
2 parents 214dd71 + 100a14f commit ae55b33
Show file tree
Hide file tree
Showing 3,445 changed files with 369,458 additions and 0 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
74 changes: 74 additions & 0 deletions .github/workflows/discord-bot_discord-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions

name: Build and deploy Node.js app to Azure Web App - discord-app

on:
push:
branches:
- discord-bot
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Node.js version
uses: actions/setup-node@v3
with:
node-version: '18.x'

- name: npm install, build, and test
run: |
cd discord
npm install
npm run build --if-present
npm run test --if-present
- name: Zip artifact for deployment
run: |
cd discord
zip -r ../release.zip ./*
- name: Upload artifact for deployment job
uses: actions/upload-artifact@v4
with:
name: node-app
path: release.zip

deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}
permissions:
id-token: write #This is required for requesting the JWT

steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: node-app

- name: Unzip artifact for deployment
run: unzip release.zip

- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_F79B5525A24E4DD1B0474AE8E88AC734 }}
tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_82993AB6E0F44B589C524B05273985DB }}
subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_BD59F4177F7646289B41D15D6119C77B }}

- name: 'Deploy to Azure Web App'
id: deploy-to-webapp
uses: azure/webapps-deploy@v3
with:
app-name: 'discord-app'
slot-name: 'Production'
package: .

196 changes: 196 additions & 0 deletions discord/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
require('dotenv').config();
const { Client, GatewayIntentBits, ChannelType } = require('discord.js');
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.GuildMembers
]
});

// Parse the GUILD_AGENT_MAP from the environment variable
const guildAgentMap = JSON.parse(process.env.GUILD_AGENT_MAP);

client.once('ready', () => {
console.log(`Logged in as ${client.user.tag}!`);
});

client.on('messageCreate', async message => {
console.log('message received');

// Ignore messages from the bot itself
if (message.author.bot) return;

// Check if the bot is tagged in the message
if (message.mentions.has(client.user)) {
const fetch = (await import('node-fetch')).default;

// Function to split message into chunks of 2000 characters or less
function splitMessage(message, maxLength = 2000) {
if (message.length <= maxLength) return [message];
const parts = [];
let currentPart = '';
let inCodeBlock = false;

const lines = message.split('\n');
for (const line of lines) {
if (line.startsWith('```')) {
inCodeBlock = !inCodeBlock;
}

if (currentPart.length + line.length + 1 > maxLength) {
if (inCodeBlock) {
currentPart += '\n```';
parts.push(currentPart);
currentPart = '```';
} else {
parts.push(currentPart);
currentPart = '';
}
}

currentPart += (currentPart.length > 0 ? '\n' : '') + line;

if (!inCodeBlock && currentPart.length >= maxLength) {
parts.push(currentPart);
currentPart = '';
}
}

if (currentPart.length > 0) {
parts.push(currentPart);
}

return parts;
}

// Function to keep typing indicator active
function keepTyping(channel) {
const interval = setInterval(() => {
channel.sendTyping();
}, 5000); // Typing indicator lasts for 10 seconds, so we refresh it every 5 seconds
return interval;
}

// Function to stop typing indicator
function stopTyping(interval) {
clearInterval(interval);
}

// Determine the channel (thread or main channel)
let channel;
if (message.channel.type === ChannelType.PublicThread || message.channel.type === ChannelType.PrivateThread) {
channel = message.channel
} else {
channel = await message.startThread({
name: `Thread for ${message.author.username}`,
autoArchiveDuration: 60,
});

// await channel.send("Hold tight, I'm preparing your answer!\n\nQuick tip ⚡️, I can help you better from your IDE. Install the [VSCode extension](https://marketplace.visualstudio.com/items?itemName=WelltestedAI.fluttergpt)");
}

// Start typing indicator
const typingInterval = keepTyping(channel);

// Fetch agent details
const guildId = message.guild.id;
const agentName = guildAgentMap[guildId];
if (!agentName) {
channel.send('Sorry, I could not find the agent for this guild.');
stopTyping(typingInterval); // Stop typing indicator
return;
}

let agentDetails;
try {
const response = await fetch("https://api.commanddash.dev/agent/get-latest-agent", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name: agentName }),
});

if (!response.ok) {
throw new Error(`Failed to load the agent: ${await response.json()}`);
}

agentDetails = await response.json();
console.log(agentDetails)
} catch (error) {
console.error('Error fetching agent details:', error);
channel.send('Sorry, I could not fetch the agent details.');
stopTyping(typingInterval); // Stop typing indicator
return;
}

try {
// Fetch all messages in the thread if it's a thread
let history = [];
history.push({ "role": "user", "text": "This conversation is happening on Discord, so please keep response concise and quote snippets only when necessary (unless of course explicity requested) " });
if (channel.type === ChannelType.PublicThread || channel.type === ChannelType.PrivateThread) {
const messages = await channel.messages.fetch({ limit: 100 });
history = messages.map(msg => ({
"role": msg.author.id === client.user.id ? "model" : "user",
"text": msg.content
}));
}

history.push({ "role": "user", "text": message.content });

// Prepare data for agent answer API
const agentData = {
agent_name: agentDetails.name,
agent_version: agentDetails.version,
chat_history: history,
included_references: [],
private: agentDetails.testing,
};

// Get answer from agent
const response = await fetch("https://api.commanddash.dev/v2/ai/agent/answer", {
method: "POST",
body: JSON.stringify(agentData),
headers: {
"Content-Type": "application/json",
},
});
const modelResponse = await response.json();
console.log(modelResponse);

// Split the response into chunks and send each chunk
const responseChunks = splitMessage(modelResponse.response);
for (const chunk of responseChunks) {
await channel.send(chunk);
}
} catch (error) {
console.error('Error getting answer from agent:', error);
channel.send('Sorry, I could not get an answer from the agent.');
} finally {
stopTyping(typingInterval); // Stop typing indicator
}
}
});

// Handle errors to prevent the bot from crashing
client.on('error', error => {
console.error('An error occurred:', error);
});

client.login(process.env.DISCORD_TOKEN);

// API endpoint to return the Discord token
app.get('/api/token', (req, res) => {
res.json({ token: process.env.DISCORD_TOKEN });
});

app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
1 change: 1 addition & 0 deletions discord/node_modules/.bin/mime

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit ae55b33

Please sign in to comment.