Initial commit
This commit is contained in:
commit
c18b8d83ef
9 changed files with 2191 additions and 0 deletions
8
.eslintrc.json
Normal file
8
.eslintrc.json
Normal 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
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
src/config.json
|
3
README.md
Normal file
3
README.md
Normal 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
2025
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
22
package.json
Normal file
22
package.json
Normal 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
8
src/config_example.json
Normal 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
66
src/index.ts
Normal 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
43
src/toOpenAIMessages.ts
Normal 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
13
tsconfig.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"sourceMap": true,
|
||||||
|
"outDir": "./dist/",
|
||||||
|
"rootDir": "./src/",
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue