Initial commit

This commit is contained in:
Wroclaw 2023-03-14 21:16:54 +01:00
commit c18b8d83ef
9 changed files with 2191 additions and 0 deletions

8
.eslintrc.json Normal file
View file

@ -0,0 +1,8 @@
{
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"rules": {
"semi": ["error", "always"],
"eol-last": ["error", "always"]
}
}

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
dist
node_modules
src/config.json

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# GPTcord
Connect ChatGPT to your Discord chat! This is a direct bridge to bring ChatGPT to discord.

2025
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

22
package.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "gptcord",
"version": "0.0.1",
"description": "",
"main": "./dist/index.js",
"scripts": {
"start": "tsc && node dist/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Wroclaw",
"license": "MIT",
"dependencies": {
"discord.js": "^14.8.0",
"openai": "^3.2.1"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"eslint": "^8.36.0",
"typescript": "^4.9.5"
}
}

8
src/config_example.json Normal file
View file

@ -0,0 +1,8 @@
{
"__comment": "Don't forget to rename the file to config.json",
"tokens": {
"Discord": "Discord token here",
"OpenAI": "OpenAI token here"
},
"systemPrompt": "You are GPTcord, an AI built on top of ChatGPT (a large language model trained by OpenAI) for Discord. Answer as concisely as possible."
}

66
src/index.ts Normal file
View file

@ -0,0 +1,66 @@
import DiscordApi from "discord.js";
import { Configuration as OpenAIApiConfiguration, OpenAIApi } from "openai";
import config from "./config.json";
import toOpenAIMessages from "./toOpenAIMessages";
const discord = new DiscordApi.Client({
intents: [
DiscordApi.GatewayIntentBits.Guilds,
DiscordApi.GatewayIntentBits.GuildMessages,
DiscordApi.GatewayIntentBits.MessageContent,
]
});
const openai = new OpenAIApi(new OpenAIApiConfiguration({
apiKey: config.tokens.OpenAI
}));
discord.on("ready", async event => {
console.log(`Connected to Discord as ${event.user.tag} (${event.user.id})`);
});
discord.on("messageCreate", async message => {
if (message.author.bot) return;
if (!message.mentions.has(message.client.user)) return;
try {
let messages: DiscordApi.Collection<string, DiscordApi.Message> = await message.channel.messages.fetch({ limit: 50, cache: false });
messages = messages.filter(m => message.createdTimestamp - m.createdTimestamp < 1000*60*60 );
message.channel.sendTyping();
const answer = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: toOpenAIMessages(messages),
max_tokens: 168
});
message.reply({
content: answer.data.choices[0].message?.content,
allowedMentions: {
repliedUser: false,
}
});
const usage = answer.data.usage;
if (usage != undefined) {
const channelName: string = message.inGuild() ? `${message.channel.name} (${message.guild.name})` : `@${message.author.tag}`;
console.log(`Used ${usage.total_tokens} (${usage.prompt_tokens} + ${usage.completion_tokens}) tokens for ${message.author.tag} (${message.author.id}) in #${channelName}`);
}
} catch (e) {
console.error(e);
message.reply({
embeds: [{
color: 0xff0000,
description: "Something bad happened! :frown:"
}],
allowedMentions: {
repliedUser: false,
}
});
}
});
discord.login(config.tokens.Discord);

43
src/toOpenAIMessages.ts Normal file
View file

@ -0,0 +1,43 @@
import { ChatCompletionRequestMessage as OpenAIMessage } from "openai";
import { Collection, Message as DiscordMessage } from "discord.js";
import config from "./config.json";
function formatMessage(message: DiscordMessage): string {
let rvalue: string = message.cleanContent;
for (const attachment of message.attachments) {
rvalue += ` [Attachment: ${attachment[1].name}`;
rvalue += attachment[1].description ? ` - ${attachment[1].description}` : "";
rvalue += "]";
}
for (const embed of message.embeds) {
rvalue += ` [Embed:`;
rvalue += embed.title ? ` ${embed.title}` : "";
rvalue += embed.author ? ` by ${embed.author}` : "";
rvalue += embed.title || embed.author ? " -": "";
rvalue += embed.description ? ` ${embed.description}` : "";
rvalue += "]";
}
return rvalue;
}
export default function toOpenAIMessages(messages: Collection<string, DiscordMessage>): OpenAIMessage[] {
const rvalue: OpenAIMessage[] = [];
rvalue.push({ role: "system", content: config.systemPrompt});
messages
.sort((a, b) => a.createdTimestamp - b.createdTimestamp)
.each(message => {
rvalue.push({
role: message.author.id == message.client.user.id ? "assistant" : "user",
content: formatMessage(message),
name: message.member?.displayName,
});
});
return rvalue;
}

13
tsconfig.json Normal file
View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"sourceMap": true,
"outDir": "./dist/",
"rootDir": "./src/",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"resolveJsonModule": true,
}
}