Initial upload of the project in its first usable version
This commit is contained in:
commit
46c766fd38
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
3
.eslintignore
Normal file
3
.eslintignore
Normal file
@ -0,0 +1,3 @@
|
||||
/dist
|
||||
/node_modules
|
||||
.eslintrc.js
|
72
.eslintrc.js
Normal file
72
.eslintrc.js
Normal file
@ -0,0 +1,72 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
// https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#usage
|
||||
// ESLint typescript rules
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'airbnb-base'
|
||||
],
|
||||
plugins: [
|
||||
// required to apply rules which need type information
|
||||
'@typescript-eslint'
|
||||
],
|
||||
parserOptions: {
|
||||
project: './tsconfig.eslint.json',
|
||||
},
|
||||
// add your custom rules here
|
||||
rules: {
|
||||
'no-param-reassign': 'off',
|
||||
'no-void': 'off',
|
||||
'no-nested-ternary': 'off',
|
||||
'max-classes-per-file': 'off',
|
||||
|
||||
'no-shadow': 'off',
|
||||
'@typescript-eslint/no-shadow': 'error',
|
||||
|
||||
'import/first': 'off',
|
||||
'import/named': 'error',
|
||||
'import/namespace': 'error',
|
||||
'import/default': 'error',
|
||||
'import/export': 'error',
|
||||
'import/extensions': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'import/prefer-default-export': 'off',
|
||||
|
||||
'prefer-promise-reject-errors': 'off',
|
||||
|
||||
quotes: ['warn', 'single', { avoidEscape: true }],
|
||||
|
||||
// this rule, if on, would require explicit return type on the `render` function
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
|
||||
// in plain CommonJS modules, you can't use `import foo = require('foo')` to pass this rule, so it has to be disabled
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
|
||||
// The core 'no-unused-vars' rules (in the eslint:recommended ruleset)
|
||||
// does not work with type definitions
|
||||
'no-unused-vars': 'off',
|
||||
|
||||
// allow console
|
||||
'no-console': 'off',
|
||||
|
||||
// allow underscore for privates
|
||||
'no-underscore-dangle': 'off',
|
||||
|
||||
// allow debugger during development only
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
|
||||
// allow plusplus in coding style
|
||||
'no-plusplus': 'off',
|
||||
|
||||
// max-len not affect coding style effectivity
|
||||
'max-len': ['error', {
|
||||
'code': 100,
|
||||
'ignoreComments': true,
|
||||
'ignoreUrls': true,
|
||||
'ignoreStrings': true,
|
||||
'ignoreTemplateLiterals': true,
|
||||
'ignoreRegExpLiterals': true,
|
||||
}]
|
||||
}
|
||||
};
|
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
.DS_Store
|
||||
.thumbs.db
|
||||
node_modules
|
||||
|
||||
# Environment
|
||||
.env
|
||||
|
||||
# Unrelated directories
|
||||
/dist
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Matías Espinoza
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
246
README.md
Normal file
246
README.md
Normal file
@ -0,0 +1,246 @@
|
||||
<!-- Improved compatibility of back to top link: See: https://github.com/othneildrew/Best-README-Template/pull/73 -->
|
||||
<a name="readme-top"></a>
|
||||
<!--
|
||||
*** Thanks for checking out the Best-README-Template. If you have a suggestion
|
||||
*** that would make this better, please fork the repo and create a pull request
|
||||
*** or simply open an issue with the tag "enhancement".
|
||||
*** Don't forget to give the project a star!
|
||||
*** Thanks again! Now go create something AMAZING! :D
|
||||
-->
|
||||
|
||||
|
||||
|
||||
<!-- PROJECT SHIELDS -->
|
||||
<!--
|
||||
*** I'm using markdown "reference style" links for readability.
|
||||
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
|
||||
*** See the bottom of this document for the declaration of the reference variables
|
||||
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
|
||||
*** https://www.markdownguide.org/basic-syntax/#reference-style-links
|
||||
-->
|
||||
<div align="center" markdown="1">
|
||||
|
||||
[![Contributors][contributors-shield]][contributors-url]
|
||||
[![Forks][forks-shield]][forks-url]
|
||||
[![Stargazers][stars-shield]][stars-url]
|
||||
[![Issues][issues-shield]][issues-url]
|
||||
[![MIT License][license-shield]][license-url]
|
||||
[![LinkedIn][linkedin-shield]][linkedin-url]
|
||||
|
||||
</div>
|
||||
|
||||
<!-- PROJECT LOGO -->
|
||||
<br />
|
||||
<div align="center">
|
||||
<a>
|
||||
<img src="https://i.imgur.com/2UDLbNX.png" alt="Logo" width="300" height="220">
|
||||
</a>
|
||||
|
||||
<h3 align="center">OpenAI Discord</h3>
|
||||
|
||||
<p align="center">
|
||||
A very simple Discord Bot that integrates the OpenAI library to make use of ChatGPT
|
||||
<br />
|
||||
<a href="https://github.com/KrozT/openai-discord"><strong>Explore the docs »</strong></a>
|
||||
<br />
|
||||
<br />
|
||||
<a href="https://discord.com/oauth2/authorize?client_id=1084340374010593311&permissions=534723950656&scope=bot">View Demo</a>
|
||||
·
|
||||
<a href="https://github.com/KrozT/openai-discord/issues">Report Bug</a>
|
||||
·
|
||||
<a href="https://github.com/KrozT/openai-discord/pulls">Request Feature</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- TABLE OF CONTENTS -->
|
||||
<details>
|
||||
<summary>Table of Contents</summary>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#about-the-project">About The Project</a>
|
||||
<ul>
|
||||
<li><a href="#built-with">Built With</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="#packages">Packages</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#getting-started">Getting Started</a>
|
||||
<ul>
|
||||
<li><a href="#installation">Installation</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#usage">Usage</a>
|
||||
<ul>
|
||||
<li><a href="#commands">Commands</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#roadmap">Roadmap</a></li>
|
||||
<li><a href="#contributing">Contributing</a></li>
|
||||
<li><a href="#license">License</a></li>
|
||||
<li><a href="#acknowledgments">Acknowledgments</a></li>
|
||||
</ol>
|
||||
</details>
|
||||
|
||||
<!-- ABOUT THE PROJECT -->
|
||||
## About The Project
|
||||
Formally called 'Aurora GPT', it is a very simple Discord chatbot that was built using discord.js and the GPT-3.5-Turbo of OpenAI.
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
### Built With
|
||||
|
||||
* [![TypeScript][TypeScript-shield]][TypeScript-url]
|
||||
|
||||
### Packages
|
||||
- [discord.js](https://github.com/discordjs/discord.js)
|
||||
- [winston](https://github.com/winstonjs/winston)
|
||||
- [openai-node](https://github.com/openai/openai-node)
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- GETTING STARTED -->
|
||||
## Getting Started
|
||||
|
||||
### Installation
|
||||
|
||||
1. Get a neccesary API Keys
|
||||
- [OpenAI](https://platform.openai.com/account/api-keys)
|
||||
- [Discord](https://platform.openai.com/account/api-keys)
|
||||
<br>
|
||||
|
||||
2. Clone the repo
|
||||
```sh
|
||||
git clone https://github.com/KrozT/openai-discord.git
|
||||
```
|
||||
3. Install packages
|
||||
```sh
|
||||
yarn install
|
||||
```
|
||||
4. Add the API Keys to your environment variables
|
||||
```sh
|
||||
DISCORD_API_KEY='YOUR DISCORD API KEY'
|
||||
OPENAI_API_KEY='YOUR OPENAI API KEY'
|
||||
```
|
||||
5. Build project
|
||||
```sh
|
||||
yarn run build
|
||||
```
|
||||
6. Start binaries
|
||||
```sh
|
||||
yarn run start
|
||||
```
|
||||
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- USAGE EXAMPLES -->
|
||||
## Usage
|
||||
|
||||
Once you have the project initialized
|
||||
just add the chat bot to your server and enjoy.
|
||||
|
||||
### Commands
|
||||
| Command | Description |
|
||||
| --- | --- |
|
||||
| `/ping` | A very simple ping command |
|
||||
| `/chat` | Say anything to the Chat Bot |
|
||||
| `/clear` | Delete your interactions with the Chat bot |
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- ROADMAP -->
|
||||
## Roadmap
|
||||
|
||||
- [x] Discord Integration
|
||||
- [x] OpenAI Integration
|
||||
- [x] Context-based single-user usability
|
||||
- [ ] Simultaneous context-based multi-user usability
|
||||
- [ ] Client-based multi-language UI support
|
||||
|
||||
See the [open issues](https://github.com/KrozT/openai-discord/issues) for a full list of proposed features (and known issues).
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- CONTRIBUTING -->
|
||||
## Contributing
|
||||
|
||||
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
|
||||
|
||||
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
|
||||
Don't forget to give the project a star! Thanks again!
|
||||
|
||||
1. Fork the Project
|
||||
2. Create your Feature Branch (`git checkout -b feature/YourAmazingFeature`)
|
||||
3. Commit your Changes (`git commit -m 'Add some YourAmazingFeature'`)
|
||||
4. Push to the Branch (`git push origin feature/YourAmazingFeature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- LICENSE -->
|
||||
## License
|
||||
|
||||
Distributed under the MIT License. See `LICENSE` for more information.
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
<!-- ACKNOWLEDGMENTS -->
|
||||
## Acknowledgments
|
||||
|
||||
* [Othneil Drew](https://github.com/othneildrew/)
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
|
||||
|
||||
<!-- MARKDOWN LINKS & IMAGES -->
|
||||
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
|
||||
[contributors-shield]: https://img.shields.io/github/contributors/KrozT/openai-discord.svg?style=for-the-badge
|
||||
[contributors-url]: https://github.com/KrozT/openai-discord/graphs/contributors
|
||||
[forks-shield]: https://img.shields.io/github/forks/KrozT/openai-discord.svg?style=for-the-badge
|
||||
[forks-url]: https://github.com/KrozT/openai-discord/network/members
|
||||
[stars-shield]: https://img.shields.io/github/stars/KrozT/openai-discord.svg?style=for-the-badge
|
||||
[stars-url]: https://github.com/KrozT/openai-discord/stargazers
|
||||
[issues-shield]: https://img.shields.io/github/issues/KrozT/openai-discord.svg?style=for-the-badge
|
||||
[issues-url]: https://github.com/KrozT/openai-discord/issues
|
||||
[license-shield]: https://img.shields.io/github/license/KrozT/openai-discord.svg?style=for-the-badge
|
||||
[license-url]: https://github.com/KrozT/openai-discord/blob/master/LICENSE
|
||||
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
|
||||
[linkedin-url]: https://www.linkedin.com/in/matias-espinoza-bustos/
|
||||
[product-screenshot]: images/screenshot.png
|
||||
[Next.js]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white
|
||||
[Next-url]: https://nextjs.org/
|
||||
[React.js]: https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB
|
||||
[React-url]: https://reactjs.org/
|
||||
[Vue.js]: https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs&logoColor=4FC08D
|
||||
[Vue-url]: https://vuejs.org/
|
||||
[Angular.io]: https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white
|
||||
[Angular-url]: https://angular.io/
|
||||
[Svelte.dev]: https://img.shields.io/badge/Svelte-4A4A55?style=for-the-badge&logo=svelte&logoColor=FF3E00
|
||||
[Svelte-url]: https://svelte.dev/
|
||||
[Laravel.com]: https://img.shields.io/badge/Laravel-FF2D20?style=for-the-badge&logo=laravel&logoColor=white
|
||||
[Laravel-url]: https://laravel.com
|
||||
[Bootstrap.com]: https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white
|
||||
[Bootstrap-url]: https://getbootstrap.com
|
||||
[JQuery.com]: https://img.shields.io/badge/jQuery-0769AD?style=for-the-badge&logo=jquery&logoColor=white
|
||||
[JQuery-url]: https://jquery.com
|
||||
|
||||
[TypeScript-url]: https://www.typescriptlang.org
|
||||
[TypeScript-shield]: https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white
|
37
package.json
Normal file
37
package.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "openai-discord",
|
||||
"version": "1.0.0",
|
||||
"description": "A very simple Discord Bot that integrates the OpenAI library to make use of ChatGPT",
|
||||
"author": "Matias Espinoza <contact@krozt.dev>",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "yarn tsc && tsc-alias",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "ts-node-dev -r tsconfig-paths/register src/index.ts"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/KrozT/openai-discord",
|
||||
"type": "git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.15.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.51.0",
|
||||
"@typescript-eslint/parser": "^5.51.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"eslint": "^8.33.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"tsc-alias": "^1.8.3",
|
||||
"tsconfig-paths": "^4.1.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"discord.js": "^14.7.1",
|
||||
"openai": "^3.2.1",
|
||||
"winston": "^3.8.2"
|
||||
}
|
||||
}
|
38
src/api/index.ts
Normal file
38
src/api/index.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import {
|
||||
ChatCompletionRequestMessage, ChatCompletionResponseMessage, Configuration, OpenAIApi,
|
||||
} from 'openai';
|
||||
import { AI } from '@/models/ai';
|
||||
import { Runnable } from '@/models/runnable';
|
||||
import { Logger } from '@/logger';
|
||||
|
||||
export class Api implements AI, Runnable {
|
||||
private _logger: Logger;
|
||||
|
||||
private _api: OpenAIApi;
|
||||
|
||||
private readonly _configuration: Configuration;
|
||||
|
||||
constructor() {
|
||||
this._logger = new Logger(Api.name);
|
||||
|
||||
this._configuration = new Configuration({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
});
|
||||
|
||||
this._api = new OpenAIApi(this._configuration);
|
||||
}
|
||||
|
||||
run(): void {
|
||||
this._logger.service.info('OpenAI Service has been initialized successfully.');
|
||||
}
|
||||
|
||||
async chatCompletion(chatHistory: ChatCompletionRequestMessage[])
|
||||
: Promise<ChatCompletionResponseMessage> {
|
||||
const request = await this._api.createChatCompletion({
|
||||
model: 'gpt-3.5-turbo',
|
||||
messages: chatHistory,
|
||||
});
|
||||
|
||||
return (request.data.choices[0].message as ChatCompletionResponseMessage);
|
||||
}
|
||||
}
|
87
src/bot/commands/chatCommand.ts
Normal file
87
src/bot/commands/chatCommand.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import {
|
||||
CommandInteraction,
|
||||
Client,
|
||||
CommandInteractionOptionResolver,
|
||||
TextChannel, ApplicationCommandType, ApplicationCommandOptionType,
|
||||
} from 'discord.js';
|
||||
import { ChatCompletionRequestMessage } from 'openai';
|
||||
import { Command } from '@/bot/models/command';
|
||||
|
||||
export const ChatCommand: Command = {
|
||||
name: 'chat',
|
||||
description: 'Say anything to the Chat bot',
|
||||
type: ApplicationCommandType.ChatInput,
|
||||
options: [
|
||||
{
|
||||
name: 'question',
|
||||
description: 'Question for the Chat bot',
|
||||
required: true,
|
||||
type: ApplicationCommandOptionType.String,
|
||||
},
|
||||
{
|
||||
name: 'ephemeral',
|
||||
description: 'If you set \'false\' the message will be persisted over time',
|
||||
required: false,
|
||||
type: ApplicationCommandOptionType.Boolean,
|
||||
},
|
||||
],
|
||||
execute: async (client: Client, interaction: CommandInteraction, ai) => {
|
||||
const channel = client.channels.cache.get(interaction.channelId) as TextChannel;
|
||||
const messages = await channel.messages.fetch({ limit: 100 });
|
||||
|
||||
const chatHistory: ChatCompletionRequestMessage[] = [];
|
||||
const consistentMessages = messages
|
||||
.filter((x) => x.interaction?.user.id === interaction.user.id);
|
||||
|
||||
const embed = consistentMessages.map((message) => message.embeds)
|
||||
.flatMap((item) => item)
|
||||
.flatMap((item) => item.data);
|
||||
|
||||
embed.forEach((item) => {
|
||||
const message: ChatCompletionRequestMessage = {
|
||||
role: item.footer?.text === 'embed-question' ? 'user' : 'assistant',
|
||||
content: item.description || 'Please notify an error in process',
|
||||
};
|
||||
|
||||
chatHistory.push(message);
|
||||
});
|
||||
|
||||
const interactionResolver = (interaction.options as CommandInteractionOptionResolver);
|
||||
const question = interactionResolver.getString('question') || undefined;
|
||||
const ephemeral = interactionResolver.getBoolean('ephemeral') || true;
|
||||
|
||||
const currentQuestion: ChatCompletionRequestMessage = {
|
||||
role: 'user',
|
||||
content: question || 'Please notify an error in process',
|
||||
};
|
||||
|
||||
chatHistory.push(currentQuestion);
|
||||
|
||||
const answer = await ai?.chatCompletion(chatHistory)
|
||||
.then((response) => response.content)
|
||||
.catch((error) => error);
|
||||
|
||||
await interaction.followUp({
|
||||
ephemeral,
|
||||
fetchReply: true,
|
||||
embeds: [
|
||||
{
|
||||
color: 15844367,
|
||||
title: '✥ Question',
|
||||
description: question,
|
||||
footer: {
|
||||
text: 'embed-question',
|
||||
},
|
||||
},
|
||||
{
|
||||
color: 5763719,
|
||||
title: '✥ Answer',
|
||||
description: answer,
|
||||
footer: {
|
||||
text: 'embed-answer',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
};
|
25
src/bot/commands/clearCommand.ts
Normal file
25
src/bot/commands/clearCommand.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {
|
||||
CommandInteraction, Client, TextChannel, ChannelType, ApplicationCommandType,
|
||||
} from 'discord.js';
|
||||
import { Command } from '@/bot/models/command';
|
||||
|
||||
export const ClearCommand: Command = {
|
||||
name: 'clear',
|
||||
description: 'Delete your interactions with the Chat bot',
|
||||
type: ApplicationCommandType.ChatInput,
|
||||
execute: async (client: Client, interaction: CommandInteraction) => {
|
||||
const channel = client.channels.cache.get(interaction.channelId) as TextChannel;
|
||||
const messages = await channel.messages.fetch({ limit: 100 });
|
||||
const consistentMessages = messages
|
||||
.filter((x) => x.interaction?.user.id === interaction.user.id);
|
||||
|
||||
if (channel.type === ChannelType.GuildText) {
|
||||
await channel.bulkDelete(consistentMessages);
|
||||
} else {
|
||||
await messages.forEach((message) => {
|
||||
if (message.author.id !== client.user?.id) return;
|
||||
message.delete();
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
10
src/bot/commands/index.ts
Normal file
10
src/bot/commands/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Command } from '@/bot/models/command';
|
||||
import { PingCommand } from '@/bot/commands/pingCommand';
|
||||
import { ChatCommand } from '@/bot/commands/chatCommand';
|
||||
import { ClearCommand } from '@/bot/commands/clearCommand';
|
||||
|
||||
export const commands: Command[] = [
|
||||
PingCommand,
|
||||
ChatCommand,
|
||||
ClearCommand,
|
||||
];
|
15
src/bot/commands/pingCommand.ts
Normal file
15
src/bot/commands/pingCommand.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { CommandInteraction, Client, ApplicationCommandType } from 'discord.js';
|
||||
import { Command } from '@/bot/models/command';
|
||||
|
||||
export const PingCommand: Command = {
|
||||
name: 'ping',
|
||||
description: 'A very simple ping command',
|
||||
type: ApplicationCommandType.ChatInput,
|
||||
execute: async (client: Client, interaction: CommandInteraction) => {
|
||||
const content = 'Pong';
|
||||
await interaction.followUp({
|
||||
ephemeral: true,
|
||||
content,
|
||||
});
|
||||
},
|
||||
};
|
73
src/bot/index.ts
Normal file
73
src/bot/index.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import {
|
||||
ActivityType, Client, CommandInteraction, IntentsBitField, Interaction, Partials,
|
||||
} from 'discord.js';
|
||||
import { Logger } from '@/logger';
|
||||
import { Runnable } from '@/models/runnable';
|
||||
import { AI } from '@/models/ai';
|
||||
import { commands } from '@/bot/commands';
|
||||
|
||||
export class Bot implements Runnable {
|
||||
private _logger: Logger;
|
||||
|
||||
private readonly _ai: AI;
|
||||
|
||||
private readonly _client: Client;
|
||||
|
||||
constructor(ai: AI) {
|
||||
this._logger = new Logger(Bot.name);
|
||||
this._ai = ai;
|
||||
|
||||
this._client = new Client({
|
||||
intents: [
|
||||
IntentsBitField.Flags.Guilds,
|
||||
IntentsBitField.Flags.GuildMessages,
|
||||
IntentsBitField.Flags.MessageContent,
|
||||
IntentsBitField.Flags.DirectMessages,
|
||||
],
|
||||
partials: [
|
||||
Partials.Channel,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
private async handleSlashCommand(interaction: CommandInteraction): Promise<void> {
|
||||
const slashCommand = commands.find((command) => command.name === interaction.commandName);
|
||||
if (!slashCommand) {
|
||||
this._logger.service.warning(`SlashCommand [${interaction.commandName}] not found.`);
|
||||
await interaction.followUp({ content: 'An error has occurred' });
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply();
|
||||
this._logger.service.debug(`SlashCommand [${interaction.commandName}] executed properly.`);
|
||||
await slashCommand.execute(this._client, interaction, this._ai);
|
||||
}
|
||||
|
||||
run(): void {
|
||||
this._client.login(process.env.DISCORD_API_KEY).then(() => {
|
||||
this._logger.service.info('Discord Service has been initialized successfully.');
|
||||
}).catch((reason) => {
|
||||
this._logger.service.error(`Failed to start Discord Service: ${reason}`);
|
||||
});
|
||||
|
||||
this._client.on('ready', async () => {
|
||||
// Set status to listening command
|
||||
this._client.user?.setActivity({
|
||||
name: '/chat',
|
||||
type: ActivityType.Listening,
|
||||
});
|
||||
|
||||
if (!this._client.user || !this._client.application) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this._client.application.commands.set(commands);
|
||||
});
|
||||
|
||||
this._client.on('interactionCreate', async (interaction: Interaction) => {
|
||||
if (interaction.isCommand() || interaction.isChatInputCommand()) {
|
||||
await this.handleSlashCommand(interaction);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
6
src/bot/models/command.ts
Normal file
6
src/bot/models/command.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { CommandInteraction, ChatInputApplicationCommandData, Client } from 'discord.js';
|
||||
import { AI } from '@/models/ai';
|
||||
|
||||
export interface Command extends ChatInputApplicationCommandData {
|
||||
execute: (client: Client, interaction: CommandInteraction, ai?: AI) => Promise<void>;
|
||||
}
|
20
src/index.ts
Normal file
20
src/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import dotenv from 'dotenv';
|
||||
import { Bot } from '@/bot';
|
||||
import { Api } from '@/api';
|
||||
|
||||
/**
|
||||
* Configure dotenv.
|
||||
*/
|
||||
dotenv.config();
|
||||
|
||||
/**
|
||||
* OpenAI contained in API Module.
|
||||
*/
|
||||
const api = new Api();
|
||||
api.run();
|
||||
|
||||
/**
|
||||
* Discord contained in Bot Module.
|
||||
*/
|
||||
const bot = new Bot(api);
|
||||
bot.run();
|
29
src/logger/index.ts
Normal file
29
src/logger/index.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import {
|
||||
createLogger, format, Logger as WinstonLogger, transports,
|
||||
} from 'winston';
|
||||
import process from 'process';
|
||||
|
||||
export class Logger {
|
||||
protected _logger: WinstonLogger;
|
||||
|
||||
constructor(serviceName: string) {
|
||||
this._logger = createLogger({
|
||||
level: process.env.NODE_ENV === 'dev' ? 'debug' : 'info',
|
||||
transports: [new transports.Console()],
|
||||
format: format.combine(
|
||||
format.colorize(),
|
||||
format.timestamp(),
|
||||
format.printf(({
|
||||
timestamp, level, message, service,
|
||||
}) => `[${timestamp}] [${service}] ${level}: ${message}`),
|
||||
),
|
||||
defaultMeta: {
|
||||
service: serviceName,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
get service() {
|
||||
return this._logger;
|
||||
}
|
||||
}
|
6
src/models/ai.ts
Normal file
6
src/models/ai.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { ChatCompletionRequestMessage, ChatCompletionResponseMessage } from 'openai';
|
||||
|
||||
export interface AI {
|
||||
chatCompletion(chatHistory: ChatCompletionRequestMessage[]):
|
||||
Promise<ChatCompletionResponseMessage>;
|
||||
}
|
3
src/models/runnable.ts
Normal file
3
src/models/runnable.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface Runnable {
|
||||
run(): void;
|
||||
}
|
4
tsconfig.eslint.json
Normal file
4
tsconfig.eslint.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": ["./**/*.ts", "./**/*.js", "./.*.js"]
|
||||
}
|
107
tsconfig.json
Normal file
107
tsconfig.json
Normal file
@ -0,0 +1,107 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
"baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user