Initial commit.
This commit is contained in:
commit
5054cac49b
6
.flake8
Normal file
6
.flake8
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[flake8]
|
||||||
|
exclude = .git,__pycache__,.venv
|
||||||
|
per-file-ignores = __init__.py:F401
|
||||||
|
max-line-length = 140
|
||||||
|
max-complexity = 10
|
||||||
|
select = B,C,E,F,W,T4,B9
|
||||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
11
.pre-commit-config.yaml
Normal file
11
.pre-commit-config.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
|
rev: 'v1.1.1'
|
||||||
|
hooks:
|
||||||
|
- id: mypy
|
||||||
|
args: [--config-file=mypy.ini]
|
||||||
|
|
||||||
|
- repo: https://github.com/pycqa/flake8
|
||||||
|
rev: 6.0.0
|
||||||
|
hooks:
|
||||||
|
- id: flake8
|
||||||
25
README.md
Normal file
25
README.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Fjerkroa bot
|
||||||
|
|
||||||
|
A simple Discord bot that uses OpenAI's GPT to chat with users.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Install the package using pip:
|
||||||
|
```
|
||||||
|
pip install fjerkroa-bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create a `bot.py` file with the following content, replacing the tokens with your own:
|
||||||
|
```python
|
||||||
|
from discord_gpt_bot import main
|
||||||
|
|
||||||
|
main.DISCORD_BOT_TOKEN = "your_discord_bot_token"
|
||||||
|
main.OPENAI_API_KEY = "your_openai_api_key"
|
||||||
|
|
||||||
|
main.run_bot()
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run the bot:
|
||||||
|
```
|
||||||
|
python bot.py
|
||||||
|
```
|
||||||
1
fjerkroa_bot/__init__.py
Normal file
1
fjerkroa_bot/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .discord_bot import FjerkroaBot, main
|
||||||
74
fjerkroa_bot/discord_bot.py
Normal file
74
fjerkroa_bot/discord_bot.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import sys
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import discord
|
||||||
|
from discord import Message
|
||||||
|
from discord.ext import commands
|
||||||
|
from watchdog.observers import Observer
|
||||||
|
from watchdog.events import FileSystemEventHandler
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFileHandler(FileSystemEventHandler):
|
||||||
|
def __init__(self, on_modified):
|
||||||
|
self._on_modified = on_modified
|
||||||
|
|
||||||
|
def on_modified(self, event):
|
||||||
|
self._on_modified(event)
|
||||||
|
|
||||||
|
|
||||||
|
class FjerkroaBot(commands.Bot):
|
||||||
|
def __init__(self, config_file: str):
|
||||||
|
assert not hasattr(self, 'config_file')
|
||||||
|
assert not hasattr(self, 'config')
|
||||||
|
assert not hasattr(self, 'observer')
|
||||||
|
assert not hasattr(self, 'file_handler')
|
||||||
|
|
||||||
|
self.config_file = config_file
|
||||||
|
self.config = self.load_config(self.config_file)
|
||||||
|
intents = discord.Intents.default()
|
||||||
|
intents.message_content = True
|
||||||
|
intents.members = True
|
||||||
|
|
||||||
|
self.observer = Observer()
|
||||||
|
self.file_handler = ConfigFileHandler(self.on_config_file_modified)
|
||||||
|
self.observer.schedule(self.file_handler, path=".", recursive=False)
|
||||||
|
self.observer.start()
|
||||||
|
|
||||||
|
super().__init__(command_prefix="!", case_insensitive=True, intents=intents)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_config(self, config_file: str = "config.json"):
|
||||||
|
with open(config_file, "r") as file:
|
||||||
|
return json.load(file)
|
||||||
|
|
||||||
|
def on_config_file_modified(self, event):
|
||||||
|
if event.src_path == self.config_file:
|
||||||
|
self.config = self.load_config(self.config_file)
|
||||||
|
|
||||||
|
async def on_ready(self):
|
||||||
|
print(f"We have logged in as {self.user}")
|
||||||
|
|
||||||
|
async def on_message(self, message: Message) -> None:
|
||||||
|
await message.channel.send("Hello!")
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
self.observer.stop()
|
||||||
|
await super().close()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
from .logging import setup_logging
|
||||||
|
setup_logging()
|
||||||
|
parser = argparse.ArgumentParser(description='Fjerkroa AI bot')
|
||||||
|
parser.add_argument('--config', type=str, default='config.json', help='Config file.')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
config = FjerkroaBot.load_config(args.config)
|
||||||
|
bot = FjerkroaBot(args.config)
|
||||||
|
bot.run(config["discord_token"])
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
11
fjerkroa_bot/logging.py
Normal file
11
fjerkroa_bot/logging.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging():
|
||||||
|
logging_handler = logging.StreamHandler(stream=sys.stdout)
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||||
|
handlers=[logging_handler],
|
||||||
|
)
|
||||||
7
mypy.ini
Normal file
7
mypy.ini
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[mypy]
|
||||||
|
files = fjerkroa_bot, tests
|
||||||
|
ignore_missing_imports = True
|
||||||
|
strict_optional = True
|
||||||
|
warn_unused_ignores = True
|
||||||
|
warn_redundant_casts = True
|
||||||
|
warn_unused_configs = True
|
||||||
24
pyproject.toml
Normal file
24
pyproject.toml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools", "wheel"]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
files = ["fjerkroa_bot", "tests"]
|
||||||
|
|
||||||
|
[tool.flake8]
|
||||||
|
max-line-length = 140
|
||||||
|
max-complexity = 10
|
||||||
|
ignore = [
|
||||||
|
"E203",
|
||||||
|
"E266",
|
||||||
|
"E501",
|
||||||
|
"W503",
|
||||||
|
]
|
||||||
|
exclude = [
|
||||||
|
".git",
|
||||||
|
".mypy_cache",
|
||||||
|
".pytest_cache",
|
||||||
|
"__pycache__",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
"venv",
|
||||||
|
]
|
||||||
10
requirements.txt
Normal file
10
requirements.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
discord.py
|
||||||
|
openai
|
||||||
|
aiohttp
|
||||||
|
mypy
|
||||||
|
flake8
|
||||||
|
pre-commit
|
||||||
|
pytest
|
||||||
|
setuptools
|
||||||
|
wheel
|
||||||
|
watchdog
|
||||||
15
setup.py
Normal file
15
setup.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
setup(name='fjerkroa-bot',
|
||||||
|
version='2.0',
|
||||||
|
packages=find_packages(),
|
||||||
|
entry_points={'console_scripts': ['fjerkroa_bot = fjerkroa_bot:main']},
|
||||||
|
test_suite="tests",
|
||||||
|
install_requires=["discord.py", "openai"],
|
||||||
|
author="Oleksandr Kozachuk",
|
||||||
|
author_email="ddeus.lp@mailnull.com",
|
||||||
|
description="A simple Discord bot that uses OpenAI's GPT to chat with users",
|
||||||
|
long_description=open("README.md").read(),
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
|
url="https://github.com/ok2/fjerkroa-bot",
|
||||||
|
classifiers=["Development Status :: 3 - Alpha", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3"])
|
||||||
6
setupenv.sh
Normal file
6
setupenv.sh
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export HOME=/home/pi
|
||||||
|
export PATH=$HOME/.pyenv/bin:$HOME/.pyenv/shims:$PATH
|
||||||
|
export PYENV_VIRTUALENV_DISABLE_PROMPT=1
|
||||||
|
eval "$(pyenv init -)"
|
||||||
|
eval "$(pyenv virtualenv-init -)"
|
||||||
|
pyenv activate py311
|
||||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
50
tests/test_main.py
Normal file
50
tests/test_main.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import unittest
|
||||||
|
import json
|
||||||
|
from unittest.mock import Mock, PropertyMock, MagicMock, AsyncMock, patch, mock_open
|
||||||
|
from fjerkroa_bot import FjerkroaBot
|
||||||
|
from discord import User, Message, TextChannel
|
||||||
|
|
||||||
|
|
||||||
|
class TestBotBase(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
|
async def asyncSetUp(self):
|
||||||
|
self.mock_response = Mock()
|
||||||
|
self.mock_response.choices = [
|
||||||
|
Mock(text="Nice day today!")
|
||||||
|
]
|
||||||
|
self.config_data = {
|
||||||
|
"openai_key": "OPENAIKEY",
|
||||||
|
"engine": "gpt-4",
|
||||||
|
"max_tokens": 1024,
|
||||||
|
"n": 1,
|
||||||
|
"temperature": 0.9,
|
||||||
|
}
|
||||||
|
self.history_data = []
|
||||||
|
|
||||||
|
|
||||||
|
class TestFunctionality(TestBotBase):
|
||||||
|
|
||||||
|
def test_load_config(self):
|
||||||
|
with patch('builtins.open', mock_open(read_data=json.dumps(self.config_data))):
|
||||||
|
result = FjerkroaBot.load_config('config.json')
|
||||||
|
self.assertEqual(result, self.config_data)
|
||||||
|
|
||||||
|
async def test_on_message_event(self):
|
||||||
|
with patch.object(FjerkroaBot, 'load_config', new=lambda s, c: self.config_data), \
|
||||||
|
patch.object(FjerkroaBot, 'user', new_callable=PropertyMock) as mock_user:
|
||||||
|
mock_user.return_value = MagicMock(spec=User)
|
||||||
|
mock_user.return_value.id = 12
|
||||||
|
bot = FjerkroaBot('config.json')
|
||||||
|
message = MagicMock(spec=Message)
|
||||||
|
message.content = "Hello, how are you?"
|
||||||
|
message.author = AsyncMock(spec=User)
|
||||||
|
message.author.id = 123
|
||||||
|
message.author.bot = False
|
||||||
|
message.channel = AsyncMock(spec=TextChannel)
|
||||||
|
message.channel.send = AsyncMock()
|
||||||
|
await bot.on_message(message)
|
||||||
|
message.channel.send.assert_called_once_with("Hello!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__mait__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Reference in New Issue
Block a user