commit 5054cac49b98520f11fad8426e3271f09882fdc4 Author: Fjerkroa Auto Date: Tue Mar 21 18:25:14 2023 +0100 Initial commit. diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..f1d12eb --- /dev/null +++ b/.flake8 @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0163807 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__/ +*.pyc +*.pyo +*.egg-info/ +dist/ +build/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..55cc3e7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..bac3d7e --- /dev/null +++ b/README.md @@ -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 +``` diff --git a/fjerkroa_bot/__init__.py b/fjerkroa_bot/__init__.py new file mode 100644 index 0000000..fb6df71 --- /dev/null +++ b/fjerkroa_bot/__init__.py @@ -0,0 +1 @@ +from .discord_bot import FjerkroaBot, main diff --git a/fjerkroa_bot/discord_bot.py b/fjerkroa_bot/discord_bot.py new file mode 100644 index 0000000..2a33606 --- /dev/null +++ b/fjerkroa_bot/discord_bot.py @@ -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()) diff --git a/fjerkroa_bot/logging.py b/fjerkroa_bot/logging.py new file mode 100644 index 0000000..e3e5ccf --- /dev/null +++ b/fjerkroa_bot/logging.py @@ -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], + ) diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..0ccec44 --- /dev/null +++ b/mypy.ini @@ -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 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..004930f --- /dev/null +++ b/pyproject.toml @@ -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", +] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..75320a0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +discord.py +openai +aiohttp +mypy +flake8 +pre-commit +pytest +setuptools +wheel +watchdog diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a129634 --- /dev/null +++ b/setup.py @@ -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"]) diff --git a/setupenv.sh b/setupenv.sh new file mode 100644 index 0000000..93c84ca --- /dev/null +++ b/setupenv.sh @@ -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 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..859a401 --- /dev/null +++ b/tests/test_main.py @@ -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()