comprobot 2.1.0__tar.gz → 2.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {comprobot-2.1.0/comprobot.egg-info → comprobot-2.1.1}/PKG-INFO +31 -3
- comprobot-2.1.1/README.md +43 -0
- {comprobot-2.1.0 → comprobot-2.1.1/comprobot.egg-info}/PKG-INFO +31 -3
- comprobot-2.1.1/comprobot.egg-info/SOURCES.txt +22 -0
- comprobot-2.1.1/comprobot.egg-info/entry_points.txt +2 -0
- {comprobot-2.1.0 → comprobot-2.1.1}/comprobot.egg-info/requires.txt +1 -1
- comprobot-2.1.1/comprobot.egg-info/top_level.txt +1 -0
- {comprobot-2.1.0 → comprobot-2.1.1}/pyproject.toml +4 -4
- {comprobot-2.1.0/Comprobot → comprobot-2.1.1/src}/__main__.py +19 -12
- {comprobot-2.1.0/Comprobot → comprobot-2.1.1/src}/api.py +11 -5
- {comprobot-2.1.0/Comprobot → comprobot-2.1.1/src}/bot.py +9 -1
- {comprobot-2.1.0/Comprobot → comprobot-2.1.1/src}/data.py +8 -8
- {comprobot-2.1.0/Comprobot → comprobot-2.1.1/src}/functions.py +2 -2
- {comprobot-2.1.0/Comprobot → comprobot-2.1.1/src}/main.py +8 -7
- {comprobot-2.1.0/Comprobot → comprobot-2.1.1/src}/moderation.py +1 -0
- comprobot-2.1.1/src/process.py +411 -0
- {comprobot-2.1.0/Comprobot → comprobot-2.1.1/src}/templates.py +1 -3
- comprobot-2.1.1/src/testing.py +123 -0
- comprobot-2.1.0/Comprobot/process.py +0 -256
- comprobot-2.1.0/README.md +0 -15
- comprobot-2.1.0/comprobot.egg-info/SOURCES.txt +0 -21
- comprobot-2.1.0/comprobot.egg-info/entry_points.txt +0 -2
- comprobot-2.1.0/comprobot.egg-info/top_level.txt +0 -1
- {comprobot-2.1.0 → comprobot-2.1.1}/comprobot.egg-info/dependency_links.txt +0 -0
- {comprobot-2.1.0 → comprobot-2.1.1}/setup.cfg +0 -0
- {comprobot-2.1.0/Comprobot → comprobot-2.1.1/src}/__init__.py +0 -0
- {comprobot-2.1.0/Comprobot → comprobot-2.1.1/src}/commands.py +0 -0
- {comprobot-2.1.0/Comprobot → comprobot-2.1.1/src}/money_system.py +0 -0
- {comprobot-2.1.0/Comprobot → comprobot-2.1.1/src}/onboarding.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comprobot
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: A self-hostable Discord bot built for maximum customization.
|
|
5
5
|
Author: badluma
|
|
6
6
|
License: MIT
|
|
@@ -19,7 +19,7 @@ Requires-Dist: appdirs
|
|
|
19
19
|
Requires-Dist: ollama
|
|
20
20
|
Requires-Dist: google-genai
|
|
21
21
|
Requires-Dist: groq
|
|
22
|
-
Requires-Dist:
|
|
22
|
+
Requires-Dist: InquirerPy
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: ruff; extra == "dev"
|
|
25
25
|
|
|
@@ -27,10 +27,38 @@ Requires-Dist: ruff; extra == "dev"
|
|
|
27
27
|
|
|
28
28
|
Comprobot is a highly-customizable, open-source Discord bot that you can run on your own server.
|
|
29
29
|
|
|
30
|
-
It’s built with Python, has a wide range of fun and useful commands, and is designed to be easy to extend. You can add new commands, customize outputs
|
|
30
|
+
It’s built with Python, has a wide range of fun and useful commands, and is designed to be easy to extend. You can add new commands, customize outputs, or change the behaviour of existing ones. You can also easily edit the keywords of existing commands and customize their outputs.
|
|
31
31
|
|
|
32
32
|
The bot also comes with built-in AI capabilities when pinging the bot, with Ollama, Groq and Gemini as available providers.
|
|
33
33
|
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
**pipx** (All platforms)
|
|
37
|
+
```sh
|
|
38
|
+
pipx install comprobot
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**APT** (Ubuntu/Debian)
|
|
42
|
+
```sh
|
|
43
|
+
sudo add-apt-repository ppa:badluma/ppa
|
|
44
|
+
sudo apt update && sudo apt upgrade
|
|
45
|
+
sudo apt install comprobot
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Homebrew** (macOS)
|
|
49
|
+
```sh
|
|
50
|
+
brew tap badluma/homebrew
|
|
51
|
+
brew install comprobot
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Scoop** (Windows)
|
|
55
|
+
```sh
|
|
56
|
+
scoop bucket add badluma https://github.com/badluma/scoop-bucket
|
|
57
|
+
scoop install comprobot
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Support for Docker Hub and AUR (Arch Linux) is planned.
|
|
61
|
+
|
|
34
62
|
## Documentation
|
|
35
63
|
|
|
36
64
|
You can find the whole documentation [here](https://badluma.github.io/Comprobot-Docs/).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Comprobot
|
|
2
|
+
|
|
3
|
+
Comprobot is a highly-customizable, open-source Discord bot that you can run on your own server.
|
|
4
|
+
|
|
5
|
+
It’s built with Python, has a wide range of fun and useful commands, and is designed to be easy to extend. You can add new commands, customize outputs, or change the behaviour of existing ones. You can also easily edit the keywords of existing commands and customize their outputs.
|
|
6
|
+
|
|
7
|
+
The bot also comes with built-in AI capabilities when pinging the bot, with Ollama, Groq and Gemini as available providers.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
**pipx** (All platforms)
|
|
12
|
+
```sh
|
|
13
|
+
pipx install comprobot
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**APT** (Ubuntu/Debian)
|
|
17
|
+
```sh
|
|
18
|
+
sudo add-apt-repository ppa:badluma/ppa
|
|
19
|
+
sudo apt update && sudo apt upgrade
|
|
20
|
+
sudo apt install comprobot
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Homebrew** (macOS)
|
|
24
|
+
```sh
|
|
25
|
+
brew tap badluma/homebrew
|
|
26
|
+
brew install comprobot
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Scoop** (Windows)
|
|
30
|
+
```sh
|
|
31
|
+
scoop bucket add badluma https://github.com/badluma/scoop-bucket
|
|
32
|
+
scoop install comprobot
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Support for Docker Hub and AUR (Arch Linux) is planned.
|
|
36
|
+
|
|
37
|
+
## Documentation
|
|
38
|
+
|
|
39
|
+
You can find the whole documentation [here](https://badluma.github.io/Comprobot-Docs/).
|
|
40
|
+
|
|
41
|
+
## License
|
|
42
|
+
|
|
43
|
+
MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: comprobot
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: A self-hostable Discord bot built for maximum customization.
|
|
5
5
|
Author: badluma
|
|
6
6
|
License: MIT
|
|
@@ -19,7 +19,7 @@ Requires-Dist: appdirs
|
|
|
19
19
|
Requires-Dist: ollama
|
|
20
20
|
Requires-Dist: google-genai
|
|
21
21
|
Requires-Dist: groq
|
|
22
|
-
Requires-Dist:
|
|
22
|
+
Requires-Dist: InquirerPy
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: ruff; extra == "dev"
|
|
25
25
|
|
|
@@ -27,10 +27,38 @@ Requires-Dist: ruff; extra == "dev"
|
|
|
27
27
|
|
|
28
28
|
Comprobot is a highly-customizable, open-source Discord bot that you can run on your own server.
|
|
29
29
|
|
|
30
|
-
It’s built with Python, has a wide range of fun and useful commands, and is designed to be easy to extend. You can add new commands, customize outputs
|
|
30
|
+
It’s built with Python, has a wide range of fun and useful commands, and is designed to be easy to extend. You can add new commands, customize outputs, or change the behaviour of existing ones. You can also easily edit the keywords of existing commands and customize their outputs.
|
|
31
31
|
|
|
32
32
|
The bot also comes with built-in AI capabilities when pinging the bot, with Ollama, Groq and Gemini as available providers.
|
|
33
33
|
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
**pipx** (All platforms)
|
|
37
|
+
```sh
|
|
38
|
+
pipx install comprobot
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**APT** (Ubuntu/Debian)
|
|
42
|
+
```sh
|
|
43
|
+
sudo add-apt-repository ppa:badluma/ppa
|
|
44
|
+
sudo apt update && sudo apt upgrade
|
|
45
|
+
sudo apt install comprobot
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Homebrew** (macOS)
|
|
49
|
+
```sh
|
|
50
|
+
brew tap badluma/homebrew
|
|
51
|
+
brew install comprobot
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Scoop** (Windows)
|
|
55
|
+
```sh
|
|
56
|
+
scoop bucket add badluma https://github.com/badluma/scoop-bucket
|
|
57
|
+
scoop install comprobot
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Support for Docker Hub and AUR (Arch Linux) is planned.
|
|
61
|
+
|
|
34
62
|
## Documentation
|
|
35
63
|
|
|
36
64
|
You can find the whole documentation [here](https://badluma.github.io/Comprobot-Docs/).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
comprobot.egg-info/PKG-INFO
|
|
4
|
+
comprobot.egg-info/SOURCES.txt
|
|
5
|
+
comprobot.egg-info/dependency_links.txt
|
|
6
|
+
comprobot.egg-info/entry_points.txt
|
|
7
|
+
comprobot.egg-info/requires.txt
|
|
8
|
+
comprobot.egg-info/top_level.txt
|
|
9
|
+
src/__init__.py
|
|
10
|
+
src/__main__.py
|
|
11
|
+
src/api.py
|
|
12
|
+
src/bot.py
|
|
13
|
+
src/commands.py
|
|
14
|
+
src/data.py
|
|
15
|
+
src/functions.py
|
|
16
|
+
src/main.py
|
|
17
|
+
src/moderation.py
|
|
18
|
+
src/money_system.py
|
|
19
|
+
src/onboarding.py
|
|
20
|
+
src/process.py
|
|
21
|
+
src/templates.py
|
|
22
|
+
src/testing.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
src
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "comprobot"
|
|
7
|
-
version = "2.1.
|
|
7
|
+
version = "2.1.1"
|
|
8
8
|
authors = [{name = "badluma"}]
|
|
9
9
|
description = "A self-hostable Discord bot built for maximum customization."
|
|
10
10
|
readme = "README.md"
|
|
@@ -24,7 +24,7 @@ dependencies = [
|
|
|
24
24
|
"ollama",
|
|
25
25
|
"google-genai",
|
|
26
26
|
"groq",
|
|
27
|
-
"
|
|
27
|
+
"InquirerPy",
|
|
28
28
|
]
|
|
29
29
|
|
|
30
30
|
[project.optional-dependencies]
|
|
@@ -35,8 +35,8 @@ Homepage = "https://badluma.github.io/Comprobot-Docs"
|
|
|
35
35
|
Repository = "https://github.com/badluma/comprobot"
|
|
36
36
|
|
|
37
37
|
[project.scripts]
|
|
38
|
-
comprobot = "
|
|
38
|
+
comprobot = "src.__main__:cli_main"
|
|
39
39
|
|
|
40
40
|
[tool.setuptools.packages.find]
|
|
41
41
|
where = ["."]
|
|
42
|
-
include = ["
|
|
42
|
+
include = ["src*"]
|
|
@@ -6,21 +6,20 @@ from .data import active, ai, get_data_path, save_toml
|
|
|
6
6
|
from .main import main
|
|
7
7
|
from .onboarding import onboarding
|
|
8
8
|
|
|
9
|
-
parser = argparse.ArgumentParser(
|
|
10
|
-
prog="comprobot",
|
|
11
|
-
description="A self-hostable open-source Discord bot built for maximum customization.",
|
|
12
|
-
)
|
|
13
|
-
subparsers = parser.add_subparsers(dest="command", metavar="")
|
|
14
|
-
subparsers.add_parser("onboard", help="Set up Comprobot for the first time.")
|
|
15
|
-
subparsers.add_parser("start", help="Start the bot.")
|
|
16
|
-
# subparsers.add_parser("config", help="View or edit the bot's configuration.") TODO: implement config command
|
|
17
|
-
# subparsers.add_parser("reset", help="Reset the bot's data and configuration.") TODO: implement reset command
|
|
18
9
|
|
|
19
|
-
|
|
10
|
+
def cli_main():
|
|
11
|
+
parser = argparse.ArgumentParser(
|
|
12
|
+
prog="comprobot",
|
|
13
|
+
description="A self-hostable open-source Discord bot built for maximum customization.",
|
|
14
|
+
)
|
|
15
|
+
subparsers = parser.add_subparsers(dest="command", metavar="")
|
|
16
|
+
subparsers.add_parser("onboard", help="Set up Comprobot for the first time.")
|
|
17
|
+
subparsers.add_parser("start", help="Start the bot.")
|
|
18
|
+
test_parser = subparsers.add_parser("test", help="Process a message through the bot's command processor.")
|
|
19
|
+
test_parser.add_argument("message", help="The message to process (e.g. '!calculate 2+2')")
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
args = parser.parse_args()
|
|
22
22
|
|
|
23
|
-
if __name__ == "__main__":
|
|
24
23
|
match args.command:
|
|
25
24
|
case "start":
|
|
26
25
|
main()
|
|
@@ -73,5 +72,13 @@ if __name__ == "__main__":
|
|
|
73
72
|
|
|
74
73
|
main()
|
|
75
74
|
|
|
75
|
+
case "test":
|
|
76
|
+
from .testing import run_test
|
|
77
|
+
run_test(args.message)
|
|
78
|
+
|
|
76
79
|
case _:
|
|
77
80
|
print(parser.format_help())
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
cli_main()
|
|
@@ -6,10 +6,13 @@ from .data import error_messages, output
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def access_api(url, parameter, error_message, headers=None):
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
try:
|
|
10
|
+
if headers:
|
|
11
|
+
raw = requests.get(url, headers=headers)
|
|
12
|
+
else:
|
|
13
|
+
raw = requests.get(url)
|
|
14
|
+
except requests.exceptions.RequestException as e:
|
|
15
|
+
return (False, str(f"{error_message} ({e})"))
|
|
13
16
|
if raw.status_code == 200:
|
|
14
17
|
try:
|
|
15
18
|
data = raw.json()
|
|
@@ -25,7 +28,10 @@ def access_api(url, parameter, error_message, headers=None):
|
|
|
25
28
|
|
|
26
29
|
# ---------- Commands ----------
|
|
27
30
|
def quote():
|
|
28
|
-
|
|
31
|
+
try:
|
|
32
|
+
quote_response = requests.get("https://zenquotes.io/api/random")
|
|
33
|
+
except requests.exceptions.RequestException as e:
|
|
34
|
+
return f"{error_messages['quote']} ({e})"
|
|
29
35
|
if quote_response.status_code != 200:
|
|
30
36
|
return f"{error_messages['quote']} (HTTP {quote_response.status_code})"
|
|
31
37
|
try:
|
|
@@ -3,6 +3,7 @@ import os
|
|
|
3
3
|
import appdirs
|
|
4
4
|
import discord
|
|
5
5
|
import dotenv
|
|
6
|
+
from discord.ext import commands
|
|
6
7
|
|
|
7
8
|
dotenv.load_dotenv(
|
|
8
9
|
dotenv.find_dotenv(
|
|
@@ -14,4 +15,11 @@ dotenv.load_dotenv(
|
|
|
14
15
|
|
|
15
16
|
intents = discord.Intents.default()
|
|
16
17
|
intents.message_content = True
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
from .data import config # noqa: E402
|
|
20
|
+
|
|
21
|
+
bot = commands.Bot(
|
|
22
|
+
command_prefix=lambda b, m: config["prefix"],
|
|
23
|
+
intents=intents,
|
|
24
|
+
)
|
|
25
|
+
client = bot
|
|
@@ -17,7 +17,7 @@ def ensure_file(path, content):
|
|
|
17
17
|
|
|
18
18
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
19
19
|
if not os.path.isfile(path):
|
|
20
|
-
with open(path, "w") as f:
|
|
20
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
21
21
|
f.write(content)
|
|
22
22
|
|
|
23
23
|
|
|
@@ -31,18 +31,18 @@ def merge_defaults(data, defaults):
|
|
|
31
31
|
|
|
32
32
|
def load_or_create(path, template_content):
|
|
33
33
|
try:
|
|
34
|
-
with open(path, "
|
|
35
|
-
data = tomlkit.
|
|
34
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
35
|
+
data = tomlkit.loads(f.read())
|
|
36
36
|
except FileNotFoundError:
|
|
37
37
|
ensure_file(path, template_content)
|
|
38
|
-
with open(path, "
|
|
39
|
-
data = tomlkit.
|
|
38
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
39
|
+
data = tomlkit.loads(f.read())
|
|
40
40
|
|
|
41
41
|
defaults = tomlkit.loads(template_content)
|
|
42
42
|
merge_defaults(data, defaults)
|
|
43
43
|
|
|
44
44
|
if data != defaults:
|
|
45
|
-
with open(path, "w") as f:
|
|
45
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
46
46
|
tomlkit.dump(data, f)
|
|
47
47
|
|
|
48
48
|
return data
|
|
@@ -56,7 +56,7 @@ keywords: Dict[str, Dict[str, List[str]]] = load_or_create(
|
|
|
56
56
|
get_data_path("keywords.toml"), templates.keywords
|
|
57
57
|
)
|
|
58
58
|
ai: Dict[str, Any] = load_or_create(get_data_path("ai.toml"), templates.ai)
|
|
59
|
-
system_prompt_text = ai["system_prompt"]
|
|
59
|
+
system_prompt_text: str = str(ai["system_prompt"])
|
|
60
60
|
money: Dict[str, Dict[str, int]] = load_or_create(
|
|
61
61
|
get_data_path("money.toml"), r"""members = {}"""
|
|
62
62
|
)
|
|
@@ -72,5 +72,5 @@ ensure_file(get_data_path(".env"), templates.env)
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
def save_toml(data, path):
|
|
75
|
-
with open(path, "w") as f:
|
|
75
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
76
76
|
tomlkit.dump(data, f)
|
|
@@ -176,8 +176,8 @@ async def chat(message):
|
|
|
176
176
|
{"role": "system", "content": system_prompt_text}
|
|
177
177
|
] + messages
|
|
178
178
|
response = groq_client.chat.completions.create(
|
|
179
|
-
model=ai["model"],
|
|
180
|
-
messages=formatted_messages,
|
|
179
|
+
model=cast(str, ai["model"]),
|
|
180
|
+
messages=cast(Any, formatted_messages),
|
|
181
181
|
)
|
|
182
182
|
content = response.choices[0].message.content or ""
|
|
183
183
|
else:
|
|
@@ -6,8 +6,9 @@ from os import path as os_path
|
|
|
6
6
|
import appdirs
|
|
7
7
|
import dotenv
|
|
8
8
|
|
|
9
|
-
from . import
|
|
10
|
-
from .functions import
|
|
9
|
+
from .bot import client
|
|
10
|
+
from .functions import para
|
|
11
|
+
from .process import Comprobot
|
|
11
12
|
|
|
12
13
|
dotenv.load_dotenv(
|
|
13
14
|
dotenv.find_dotenv(
|
|
@@ -17,8 +18,6 @@ dotenv.load_dotenv(
|
|
|
17
18
|
)
|
|
18
19
|
)
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
response = None
|
|
22
21
|
if sys.platform.startswith("win"):
|
|
23
22
|
print("Running on Windows (cmd)")
|
|
24
23
|
elif platform.system() in ["Linux", "Darwin"]:
|
|
@@ -27,9 +26,11 @@ else:
|
|
|
27
26
|
print("Unknown OS")
|
|
28
27
|
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
async def _setup_hook():
|
|
30
|
+
await client.add_cog(Comprobot(client))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
client.setup_hook = _setup_hook
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
@client.event
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from random import choice
|
|
3
|
+
|
|
4
|
+
import discord
|
|
5
|
+
from appdirs import user_cache_dir, user_data_dir
|
|
6
|
+
from discord.ext import commands as ext_commands
|
|
7
|
+
|
|
8
|
+
from . import api
|
|
9
|
+
from . import commands as cmd_module
|
|
10
|
+
from . import money_system
|
|
11
|
+
from .bot import bot
|
|
12
|
+
from .data import active, ai, config, error_messages, keywords, output
|
|
13
|
+
from .functions import chat
|
|
14
|
+
from .moderation import check_message
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _is_admin_or_bot_admin():
|
|
18
|
+
async def predicate(ctx):
|
|
19
|
+
return (
|
|
20
|
+
ctx.author.guild_permissions.administrator
|
|
21
|
+
or ctx.author.id in config["bot_admins"]
|
|
22
|
+
)
|
|
23
|
+
return ext_commands.check(predicate)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Comprobot(ext_commands.Cog):
|
|
27
|
+
|
|
28
|
+
async def cog_before_invoke(self, ctx):
|
|
29
|
+
await ctx.bot.http.send_typing(ctx.channel.id)
|
|
30
|
+
|
|
31
|
+
# ── Commands ─────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
@ext_commands.command(
|
|
34
|
+
name=keywords["commands"]["quote"][0],
|
|
35
|
+
aliases=keywords["commands"]["quote"][1:],
|
|
36
|
+
)
|
|
37
|
+
@ext_commands.check(lambda ctx: active["quote"])
|
|
38
|
+
async def quote_cmd(self, ctx):
|
|
39
|
+
await ctx.send(api.quote())
|
|
40
|
+
|
|
41
|
+
@ext_commands.command(
|
|
42
|
+
name=keywords["commands"]["joke"][0],
|
|
43
|
+
aliases=keywords["commands"]["joke"][1:],
|
|
44
|
+
)
|
|
45
|
+
@ext_commands.check(lambda ctx: active["joke"])
|
|
46
|
+
async def joke_cmd(self, ctx):
|
|
47
|
+
await ctx.send(api.joke())
|
|
48
|
+
|
|
49
|
+
@ext_commands.command(
|
|
50
|
+
name=keywords["commands"]["meme"][0],
|
|
51
|
+
aliases=keywords["commands"]["meme"][1:],
|
|
52
|
+
)
|
|
53
|
+
@ext_commands.check(lambda ctx: active["meme"])
|
|
54
|
+
async def meme_cmd(self, ctx):
|
|
55
|
+
await ctx.send(api.meme())
|
|
56
|
+
|
|
57
|
+
@ext_commands.command(
|
|
58
|
+
name=keywords["commands"]["waifu"][0],
|
|
59
|
+
aliases=keywords["commands"]["waifu"][1:],
|
|
60
|
+
)
|
|
61
|
+
@ext_commands.check(lambda ctx: active["waifu"])
|
|
62
|
+
async def waifu_cmd(self, ctx):
|
|
63
|
+
await ctx.send(api.waifu())
|
|
64
|
+
|
|
65
|
+
@ext_commands.group(
|
|
66
|
+
name=keywords["commands"]["image"][0],
|
|
67
|
+
aliases=keywords["commands"]["image"][1:],
|
|
68
|
+
invoke_without_command=True,
|
|
69
|
+
)
|
|
70
|
+
@ext_commands.check(lambda ctx: active["image"])
|
|
71
|
+
async def image_cmd(self, ctx):
|
|
72
|
+
await ctx.send(error_messages["missing_argument"])
|
|
73
|
+
|
|
74
|
+
@image_cmd.command(
|
|
75
|
+
name=keywords["commands"]["duck"][0],
|
|
76
|
+
aliases=keywords["commands"]["duck"][1:],
|
|
77
|
+
)
|
|
78
|
+
@ext_commands.check(lambda ctx: active["duck"])
|
|
79
|
+
async def duck_cmd(self, ctx):
|
|
80
|
+
await ctx.send(api.duck())
|
|
81
|
+
|
|
82
|
+
@image_cmd.command(
|
|
83
|
+
name=keywords["commands"]["dog"][0],
|
|
84
|
+
aliases=keywords["commands"]["dog"][1:],
|
|
85
|
+
)
|
|
86
|
+
@ext_commands.check(lambda ctx: active["dog"])
|
|
87
|
+
async def dog_cmd(self, ctx):
|
|
88
|
+
await ctx.send(api.dog())
|
|
89
|
+
|
|
90
|
+
@image_cmd.command(
|
|
91
|
+
name=keywords["commands"]["cat"][0],
|
|
92
|
+
aliases=keywords["commands"]["cat"][1:],
|
|
93
|
+
)
|
|
94
|
+
@ext_commands.check(lambda ctx: active["cat"])
|
|
95
|
+
async def cat_cmd(self, ctx):
|
|
96
|
+
await ctx.send(api.cat())
|
|
97
|
+
|
|
98
|
+
@ext_commands.command(
|
|
99
|
+
name=keywords["commands"]["chuck_norris"][0],
|
|
100
|
+
aliases=keywords["commands"]["chuck_norris"][1:],
|
|
101
|
+
)
|
|
102
|
+
@ext_commands.check(lambda ctx: active["chuck_norris"])
|
|
103
|
+
async def chuck_cmd(self, ctx):
|
|
104
|
+
await ctx.send(api.chuck())
|
|
105
|
+
|
|
106
|
+
@ext_commands.command(
|
|
107
|
+
name=keywords["commands"]["fact"][0],
|
|
108
|
+
aliases=keywords["commands"]["fact"][1:],
|
|
109
|
+
)
|
|
110
|
+
@ext_commands.check(lambda ctx: active["fact"])
|
|
111
|
+
async def fact_cmd(self, ctx):
|
|
112
|
+
await ctx.send(api.fact())
|
|
113
|
+
|
|
114
|
+
@ext_commands.command(
|
|
115
|
+
name=keywords["commands"]["bible"][0],
|
|
116
|
+
aliases=keywords["commands"]["bible"][1:],
|
|
117
|
+
)
|
|
118
|
+
@ext_commands.check(lambda ctx: active["bible"])
|
|
119
|
+
async def bible_cmd(self, ctx, book: str | None = None, chapter: int | None = None, verse: int | None = None):
|
|
120
|
+
if book is None:
|
|
121
|
+
await ctx.send(api.bible(True))
|
|
122
|
+
elif chapter is not None and verse is not None:
|
|
123
|
+
await ctx.send(api.bible(False, book, chapter, verse))
|
|
124
|
+
else:
|
|
125
|
+
await ctx.send(error_messages["missing_argument"])
|
|
126
|
+
|
|
127
|
+
@ext_commands.command(
|
|
128
|
+
name=keywords["commands"]["truth"][0],
|
|
129
|
+
aliases=keywords["commands"]["truth"][1:],
|
|
130
|
+
)
|
|
131
|
+
@ext_commands.check(lambda ctx: active["truth"])
|
|
132
|
+
async def truth_cmd(self, ctx, rating: str | None = None):
|
|
133
|
+
result = api.tord("https://api.truthordarebot.xyz/v1/truth", rating)
|
|
134
|
+
if result:
|
|
135
|
+
await ctx.send(choice(output["commands"]["truth"]).replace("{{QUESTION}}", result))
|
|
136
|
+
else:
|
|
137
|
+
await ctx.send(error_messages["truth"])
|
|
138
|
+
|
|
139
|
+
@ext_commands.command(
|
|
140
|
+
name=keywords["commands"]["dare"][0],
|
|
141
|
+
aliases=keywords["commands"]["dare"][1:],
|
|
142
|
+
)
|
|
143
|
+
@ext_commands.check(lambda ctx: active["dare"])
|
|
144
|
+
async def dare_cmd(self, ctx, rating: str | None = None):
|
|
145
|
+
result = api.tord("https://api.truthordarebot.xyz/api/dare", rating)
|
|
146
|
+
if result:
|
|
147
|
+
await ctx.send(choice(output["commands"]["dare"]).replace("{{QUESTION}}", result))
|
|
148
|
+
else:
|
|
149
|
+
await ctx.send(error_messages["dare"])
|
|
150
|
+
|
|
151
|
+
@ext_commands.command(
|
|
152
|
+
name=keywords["commands"]["wyr"][0],
|
|
153
|
+
aliases=keywords["commands"]["wyr"][1:],
|
|
154
|
+
)
|
|
155
|
+
@ext_commands.check(lambda ctx: active["wyr"])
|
|
156
|
+
async def wyr_cmd(self, ctx, rating: str | None = None):
|
|
157
|
+
result = api.tord("https://api.truthordarebot.xyz/api/wyr", rating)
|
|
158
|
+
if result:
|
|
159
|
+
await ctx.send(choice(output["commands"]["wyr"]).replace("{{QUESTION}}", result))
|
|
160
|
+
else:
|
|
161
|
+
await ctx.send(error_messages["wyr"])
|
|
162
|
+
|
|
163
|
+
@ext_commands.command(
|
|
164
|
+
name=keywords["commands"]["never_have_i_ever"][0],
|
|
165
|
+
aliases=keywords["commands"]["never_have_i_ever"][1:],
|
|
166
|
+
)
|
|
167
|
+
@ext_commands.check(lambda ctx: active["never_have_i_ever"])
|
|
168
|
+
async def nhie_cmd(self, ctx, rating: str | None = None):
|
|
169
|
+
result = api.tord("https://api.truthordarebot.xyz/api/nhie", rating)
|
|
170
|
+
if result:
|
|
171
|
+
await ctx.send(choice(output["commands"]["never_have_i_ever"]).replace("{{QUESTION}}", result))
|
|
172
|
+
else:
|
|
173
|
+
await ctx.send(error_messages["never-hie"])
|
|
174
|
+
|
|
175
|
+
@ext_commands.command(
|
|
176
|
+
name=keywords["commands"]["paranoia"][0],
|
|
177
|
+
aliases=keywords["commands"]["paranoia"][1:],
|
|
178
|
+
)
|
|
179
|
+
@ext_commands.check(lambda ctx: active["paranoia"])
|
|
180
|
+
async def paranoia_cmd(self, ctx, rating: str | None = None):
|
|
181
|
+
result = api.tord("https://api.truthordarebot.xyz/api/paranoia", rating)
|
|
182
|
+
if result:
|
|
183
|
+
await ctx.send(choice(output["commands"]["paranoia"]).replace("{{QUESTION}}", result))
|
|
184
|
+
else:
|
|
185
|
+
await ctx.send(error_messages["paranoia"])
|
|
186
|
+
|
|
187
|
+
@ext_commands.command(
|
|
188
|
+
name=keywords["commands"]["qr_code"][0],
|
|
189
|
+
aliases=keywords["commands"]["qr_code"][1:],
|
|
190
|
+
)
|
|
191
|
+
@ext_commands.check(lambda ctx: active["qr_code"])
|
|
192
|
+
async def qr_cmd(self, ctx, link: str | None = None):
|
|
193
|
+
if link:
|
|
194
|
+
await ctx.send(cmd_module.qr(link))
|
|
195
|
+
else:
|
|
196
|
+
await ctx.send(error_messages["missing_argument"])
|
|
197
|
+
|
|
198
|
+
@ext_commands.command(
|
|
199
|
+
name=keywords["commands"]["calculate"][0],
|
|
200
|
+
aliases=keywords["commands"]["calculate"][1:],
|
|
201
|
+
)
|
|
202
|
+
@ext_commands.check(lambda ctx: active["calculate"])
|
|
203
|
+
async def calculate_cmd(self, ctx, *, expression: str | None = None):
|
|
204
|
+
if expression:
|
|
205
|
+
await ctx.send(cmd_module.calculate(expression))
|
|
206
|
+
else:
|
|
207
|
+
await ctx.send(error_messages["missing_argument"])
|
|
208
|
+
|
|
209
|
+
@ext_commands.command(
|
|
210
|
+
name=keywords["commands"]["bitcoin"][0],
|
|
211
|
+
aliases=keywords["commands"]["bitcoin"][1:],
|
|
212
|
+
)
|
|
213
|
+
@ext_commands.check(lambda ctx: active["bitcoin"])
|
|
214
|
+
async def bitcoin_cmd(self, ctx, currency: str = "usd"):
|
|
215
|
+
await ctx.send(api.bitcoin(currency))
|
|
216
|
+
|
|
217
|
+
@ext_commands.command(
|
|
218
|
+
name=keywords["commands"]["ascii_art"][0],
|
|
219
|
+
aliases=keywords["commands"]["ascii_art"][1:],
|
|
220
|
+
)
|
|
221
|
+
async def ascii_cmd(self, ctx):
|
|
222
|
+
await ctx.send(cmd_module.ascii())
|
|
223
|
+
|
|
224
|
+
@ext_commands.command(
|
|
225
|
+
name=keywords["commands"]["currency"][0],
|
|
226
|
+
aliases=keywords["commands"]["currency"][1:],
|
|
227
|
+
)
|
|
228
|
+
@ext_commands.check(lambda ctx: active["currency"])
|
|
229
|
+
async def currency_cmd(self, ctx, amount: str | None = None, from_currency: str | None = None, to_currency: str | None = None):
|
|
230
|
+
if not all([amount, from_currency, to_currency]):
|
|
231
|
+
await ctx.send(error_messages["missing_argument"])
|
|
232
|
+
return
|
|
233
|
+
await ctx.send(api.currency(from_currency, to_currency, amount))
|
|
234
|
+
|
|
235
|
+
# ── Money ─────────────────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
@ext_commands.command(
|
|
238
|
+
name=keywords["money"]["check_balance"][0],
|
|
239
|
+
aliases=keywords["money"]["check_balance"][1:],
|
|
240
|
+
)
|
|
241
|
+
async def check_balance_cmd(self, ctx, username: str | None = None):
|
|
242
|
+
if username:
|
|
243
|
+
await ctx.send(money_system.check_balance(username))
|
|
244
|
+
else:
|
|
245
|
+
await ctx.send(error_messages["missing_argument"])
|
|
246
|
+
|
|
247
|
+
@ext_commands.command(
|
|
248
|
+
name=keywords["money"]["add_money"][0],
|
|
249
|
+
aliases=keywords["money"]["add_money"][1:],
|
|
250
|
+
)
|
|
251
|
+
@_is_admin_or_bot_admin()
|
|
252
|
+
async def add_money_cmd(self, ctx, username: str | None = None, amount: str | None = None):
|
|
253
|
+
if not username or not amount:
|
|
254
|
+
await ctx.send(error_messages["missing_argument"])
|
|
255
|
+
return
|
|
256
|
+
try:
|
|
257
|
+
await ctx.send(money_system.add_money(username, int(amount)))
|
|
258
|
+
except ValueError:
|
|
259
|
+
await ctx.send(f"Invalid amount: {amount}")
|
|
260
|
+
|
|
261
|
+
@ext_commands.command(
|
|
262
|
+
name=keywords["money"]["remove_money"][0],
|
|
263
|
+
aliases=keywords["money"]["remove_money"][1:],
|
|
264
|
+
)
|
|
265
|
+
@_is_admin_or_bot_admin()
|
|
266
|
+
async def remove_money_cmd(self, ctx, username: str | None = None, amount: str | None = None):
|
|
267
|
+
if not username or not amount:
|
|
268
|
+
await ctx.send(error_messages["missing_argument"])
|
|
269
|
+
return
|
|
270
|
+
try:
|
|
271
|
+
await ctx.send(money_system.remove_money(username, int(amount)))
|
|
272
|
+
except ValueError:
|
|
273
|
+
await ctx.send(f"Invalid amount: {amount}")
|
|
274
|
+
|
|
275
|
+
@ext_commands.command(name="purge")
|
|
276
|
+
@ext_commands.check(lambda ctx: active["purge"])
|
|
277
|
+
@ext_commands.has_permissions(administrator=True)
|
|
278
|
+
async def purge_cmd(self, ctx):
|
|
279
|
+
await ctx.channel.purge()
|
|
280
|
+
|
|
281
|
+
# ── Settings ──────────────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
@ext_commands.group(
|
|
284
|
+
name=keywords["settings"]["settings"][0],
|
|
285
|
+
aliases=keywords["settings"]["settings"][1:],
|
|
286
|
+
invoke_without_command=True,
|
|
287
|
+
)
|
|
288
|
+
async def settings_cmd(self, ctx):
|
|
289
|
+
await ctx.send(error_messages["unknown_command"])
|
|
290
|
+
|
|
291
|
+
@settings_cmd.command(
|
|
292
|
+
name=keywords["settings"]["profile_picture"][0],
|
|
293
|
+
aliases=keywords["settings"]["profile_picture"][1:],
|
|
294
|
+
)
|
|
295
|
+
@_is_admin_or_bot_admin()
|
|
296
|
+
async def pfp_cmd(self, ctx):
|
|
297
|
+
if not ctx.message.attachments:
|
|
298
|
+
await ctx.send(error_messages["no_attachment"])
|
|
299
|
+
return
|
|
300
|
+
cache_dir = user_cache_dir("Comprobot", appauthor=False)
|
|
301
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
302
|
+
await ctx.message.attachments[0].save(f"{cache_dir}/pfp.png")
|
|
303
|
+
if bot.user is None:
|
|
304
|
+
await ctx.send(error_messages["bot_unavailable"])
|
|
305
|
+
return
|
|
306
|
+
with open(f"{cache_dir}/pfp.png", "rb") as f:
|
|
307
|
+
await bot.user.edit(avatar=f.read())
|
|
308
|
+
await ctx.send(choice(output["settings"]["profile_picture_applied"]))
|
|
309
|
+
|
|
310
|
+
@settings_cmd.command(
|
|
311
|
+
name=keywords["settings"]["banner"][0],
|
|
312
|
+
aliases=keywords["settings"]["banner"][1:],
|
|
313
|
+
)
|
|
314
|
+
@_is_admin_or_bot_admin()
|
|
315
|
+
async def banner_cmd(self, ctx):
|
|
316
|
+
if not ctx.message.attachments:
|
|
317
|
+
await ctx.send(error_messages["no_attachment"])
|
|
318
|
+
return
|
|
319
|
+
cache_dir = user_cache_dir("Comprobot", appauthor=False)
|
|
320
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
321
|
+
await ctx.message.attachments[0].save(f"{cache_dir}/banner.png")
|
|
322
|
+
if bot.user is None:
|
|
323
|
+
await ctx.send(error_messages["bot_unavailable"])
|
|
324
|
+
return
|
|
325
|
+
with open(f"{cache_dir}/banner.png", "rb") as f:
|
|
326
|
+
await bot.user.edit(banner=f.read())
|
|
327
|
+
await ctx.send(choice(output["settings"]["banner_applied"]))
|
|
328
|
+
|
|
329
|
+
@settings_cmd.command(
|
|
330
|
+
name=keywords["settings"]["change_name"][0],
|
|
331
|
+
aliases=keywords["settings"]["change_name"][1:],
|
|
332
|
+
)
|
|
333
|
+
@_is_admin_or_bot_admin()
|
|
334
|
+
async def name_cmd(self, ctx, *, name: str | None = None):
|
|
335
|
+
if not name:
|
|
336
|
+
await ctx.send(error_messages["missing_argument"])
|
|
337
|
+
return
|
|
338
|
+
if bot.user is None:
|
|
339
|
+
await ctx.send(error_messages["bot_unavailable"])
|
|
340
|
+
return
|
|
341
|
+
await bot.user.edit(username=name)
|
|
342
|
+
await ctx.send(choice(output["settings"]["nickname_applied"]).replace("{{NAME}}", name))
|
|
343
|
+
|
|
344
|
+
@settings_cmd.command(
|
|
345
|
+
name=keywords["settings"]["change_keywords"][0],
|
|
346
|
+
aliases=keywords["settings"]["change_keywords"][1:],
|
|
347
|
+
)
|
|
348
|
+
@_is_admin_or_bot_admin()
|
|
349
|
+
async def keywords_cmd(self, ctx, command_name: str | None = None, *new_keywords):
|
|
350
|
+
if not command_name or not new_keywords:
|
|
351
|
+
await ctx.send(error_messages["missing_argument"])
|
|
352
|
+
return
|
|
353
|
+
from . import data as data_module
|
|
354
|
+
|
|
355
|
+
target_category = None
|
|
356
|
+
for category in data_module.keywords:
|
|
357
|
+
if command_name in data_module.keywords[category]:
|
|
358
|
+
target_category = category
|
|
359
|
+
break
|
|
360
|
+
if target_category is None:
|
|
361
|
+
await ctx.send(error_messages["unknown_argument"])
|
|
362
|
+
return
|
|
363
|
+
data_module.keywords[target_category][command_name] = list(new_keywords)
|
|
364
|
+
data_module.save_toml(
|
|
365
|
+
data_module.keywords,
|
|
366
|
+
f"{user_data_dir('Comprobot')}/keywords.toml",
|
|
367
|
+
)
|
|
368
|
+
await ctx.send(
|
|
369
|
+
choice(output["settings"]["keywords_applied"])
|
|
370
|
+
.replace("{{KEYWORDS}}", " ".join(new_keywords))
|
|
371
|
+
.replace("{{COMMAND}}", command_name)
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# ── Events ────────────────────────────────────────────────────────────────
|
|
375
|
+
|
|
376
|
+
@ext_commands.Cog.listener()
|
|
377
|
+
async def on_message(self, message):
|
|
378
|
+
print(
|
|
379
|
+
"\033[90m"
|
|
380
|
+
+ f"[{message.channel}] "
|
|
381
|
+
+ "\033[36m"
|
|
382
|
+
+ f"{message.author.name}: "
|
|
383
|
+
+ "\033[0m"
|
|
384
|
+
+ message.content
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
if message.author == bot.user:
|
|
388
|
+
return
|
|
389
|
+
|
|
390
|
+
await check_message(message)
|
|
391
|
+
|
|
392
|
+
user_id = bot.user.id if bot.user else None
|
|
393
|
+
|
|
394
|
+
is_reply_to_bot = False
|
|
395
|
+
if message.reference and message.reference.message_id and user_id:
|
|
396
|
+
try:
|
|
397
|
+
ref_msg = await message.channel.fetch_message(message.reference.message_id)
|
|
398
|
+
is_reply_to_bot = ref_msg.author.id == user_id
|
|
399
|
+
except discord.NotFound:
|
|
400
|
+
pass
|
|
401
|
+
|
|
402
|
+
if (
|
|
403
|
+
f"<@{user_id}>" in message.content
|
|
404
|
+
or (is_reply_to_bot and ai["answer_to_reply"])
|
|
405
|
+
) and ai["activate_ai"]:
|
|
406
|
+
async with message.channel.typing():
|
|
407
|
+
response = await chat(message)
|
|
408
|
+
if response:
|
|
409
|
+
content = str(response)
|
|
410
|
+
for chunk in [content[i : i + 2000] for i in range(0, len(content), 2000)]:
|
|
411
|
+
await message.channel.send(chunk)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import inspect
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any, cast
|
|
5
|
+
|
|
6
|
+
from discord.ext import commands as ext_commands
|
|
7
|
+
|
|
8
|
+
from .data import config
|
|
9
|
+
from .process import Comprobot
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class _FakeHTTP:
|
|
13
|
+
async def send_typing(self, _):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _FakePermissions:
|
|
18
|
+
administrator = True
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class _FakeAuthor:
|
|
22
|
+
id = 0
|
|
23
|
+
name = "tester"
|
|
24
|
+
guild_permissions = _FakePermissions()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _FakeChannel:
|
|
28
|
+
id = 0
|
|
29
|
+
|
|
30
|
+
async def purge(self):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class _FakeBotUser:
|
|
35
|
+
id = 1
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _FakeBot:
|
|
39
|
+
http = _FakeHTTP()
|
|
40
|
+
user = _FakeBotUser()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class _FakeMessage:
|
|
44
|
+
attachments = []
|
|
45
|
+
reference = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class _FakeCtx:
|
|
49
|
+
def __init__(self):
|
|
50
|
+
self.author = _FakeAuthor()
|
|
51
|
+
self.channel = _FakeChannel()
|
|
52
|
+
self.message = _FakeMessage()
|
|
53
|
+
self.bot = _FakeBot()
|
|
54
|
+
|
|
55
|
+
async def send(self, content):
|
|
56
|
+
print(content)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _find_command(cog, parts):
|
|
60
|
+
"""Return (command, remaining_args) or (None, [])."""
|
|
61
|
+
if not parts:
|
|
62
|
+
return None, []
|
|
63
|
+
|
|
64
|
+
cmd_name = parts[0].lower()
|
|
65
|
+
remaining = parts[1:]
|
|
66
|
+
|
|
67
|
+
for command in cog.__cog_commands__:
|
|
68
|
+
if command.parent is not None:
|
|
69
|
+
continue
|
|
70
|
+
names = [command.name] + list(command.aliases or [])
|
|
71
|
+
if cmd_name not in names:
|
|
72
|
+
continue
|
|
73
|
+
if isinstance(command, ext_commands.Group) and remaining:
|
|
74
|
+
sub = command.all_commands.get(remaining[0].lower())
|
|
75
|
+
if sub:
|
|
76
|
+
return sub, remaining[1:]
|
|
77
|
+
return command, remaining
|
|
78
|
+
|
|
79
|
+
return None, []
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
async def _run(message: str) -> int:
|
|
83
|
+
prefix = config.get("prefix", "!")
|
|
84
|
+
if not message.startswith(prefix):
|
|
85
|
+
print(f"Error: message must start with prefix '{prefix}'")
|
|
86
|
+
return 1
|
|
87
|
+
|
|
88
|
+
parts = message[len(prefix):].split()
|
|
89
|
+
if not parts:
|
|
90
|
+
print("Error: empty command")
|
|
91
|
+
return 1
|
|
92
|
+
|
|
93
|
+
cog = Comprobot()
|
|
94
|
+
ctx: Any = _FakeCtx()
|
|
95
|
+
await cog.cog_before_invoke(ctx)
|
|
96
|
+
|
|
97
|
+
command, args = _find_command(cog, parts)
|
|
98
|
+
if command is None:
|
|
99
|
+
print(f"Error: unknown command '{parts[0]}'")
|
|
100
|
+
return 1
|
|
101
|
+
|
|
102
|
+
sig = inspect.signature(command.callback)
|
|
103
|
+
params = list(sig.parameters.values())[2:] # skip self, ctx
|
|
104
|
+
kw_only = [p for p in params if p.kind == inspect.Parameter.KEYWORD_ONLY]
|
|
105
|
+
|
|
106
|
+
fn = cast(Any, command.callback)
|
|
107
|
+
try:
|
|
108
|
+
if kw_only and args:
|
|
109
|
+
await fn(cog, ctx, **{kw_only[0].name: " ".join(args)})
|
|
110
|
+
else:
|
|
111
|
+
await fn(cog, ctx, *args)
|
|
112
|
+
except Exception as e:
|
|
113
|
+
print(f"Error: {e}")
|
|
114
|
+
return 1
|
|
115
|
+
|
|
116
|
+
return 0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def run_test(message: str) -> None:
|
|
120
|
+
import io
|
|
121
|
+
if isinstance(sys.stdout, io.TextIOWrapper):
|
|
122
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
123
|
+
sys.exit(asyncio.run(_run(message)))
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from random import choice
|
|
3
|
-
from typing import Any, cast
|
|
4
|
-
|
|
5
|
-
import discord
|
|
6
|
-
from appdirs import user_cache_dir, user_data_dir
|
|
7
|
-
|
|
8
|
-
from . import api, commands, data, money_system
|
|
9
|
-
from .bot import client
|
|
10
|
-
from .data import active, ai, config, error_messages, keywords, output
|
|
11
|
-
from .functions import chat
|
|
12
|
-
from .moderation import check_message
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
async def command(message) -> str | None | Any:
|
|
16
|
-
|
|
17
|
-
command_parts = message.content[len(config["command_prefix"]) :].strip().split()
|
|
18
|
-
|
|
19
|
-
command = command_parts[0]
|
|
20
|
-
args = command_parts[1:]
|
|
21
|
-
|
|
22
|
-
if command in keywords["commands"]["quote"] and active["quote"]:
|
|
23
|
-
return api.quote()
|
|
24
|
-
elif command in keywords["commands"]["joke"] and active["joke"]:
|
|
25
|
-
return api.joke()
|
|
26
|
-
elif command in keywords["commands"]["meme"] and active["meme"]:
|
|
27
|
-
return api.meme()
|
|
28
|
-
elif command in keywords["commands"]["waifu"] and active["waifu"]:
|
|
29
|
-
return api.waifu()
|
|
30
|
-
elif command in keywords["commands"]["image"] and active["image"]:
|
|
31
|
-
if args[0] in keywords["commands"]["duck"] and active["duck"]:
|
|
32
|
-
return api.duck()
|
|
33
|
-
elif args[0] in keywords["commands"]["dog"] and active["dog"]:
|
|
34
|
-
return api.dog()
|
|
35
|
-
elif args[0] in keywords["commands"]["cat"] and active["cat"]:
|
|
36
|
-
return api.cat()
|
|
37
|
-
elif not args:
|
|
38
|
-
return error_messages["missing_argument"]
|
|
39
|
-
else:
|
|
40
|
-
return error_messages["unknown_argument"]
|
|
41
|
-
elif command in keywords["commands"]["chuck_norris"] and active["chuck_norris"]:
|
|
42
|
-
return api.chuck()
|
|
43
|
-
elif command in keywords["commands"]["fact"] and active["fact"]:
|
|
44
|
-
return api.fact()
|
|
45
|
-
elif command in keywords["commands"]["bible"] and active["bible"]:
|
|
46
|
-
if not args:
|
|
47
|
-
return api.bible(True)
|
|
48
|
-
if len(args) >= 3:
|
|
49
|
-
return api.bible(False, args[0], args[1], args[2])
|
|
50
|
-
return error_messages["passage_not_found"]
|
|
51
|
-
elif command in keywords["commands"]["truth"] and active["truth"]:
|
|
52
|
-
return api.tord(
|
|
53
|
-
"https://api.truthordarebot.xyz/v1/truth", args[0] if args else None
|
|
54
|
-
)
|
|
55
|
-
elif command in keywords["commands"]["dare"] and active["dare"]:
|
|
56
|
-
return api.tord(
|
|
57
|
-
"https://api.truthordarebot.xyz/api/dare", args[0] if args else None
|
|
58
|
-
)
|
|
59
|
-
elif command in keywords["commands"]["wyr"] and active["wyr"]:
|
|
60
|
-
return api.tord(
|
|
61
|
-
"https://api.truthordarebot.xyz/api/wyr", args[0] if args else None
|
|
62
|
-
)
|
|
63
|
-
elif (
|
|
64
|
-
command in keywords["commands"]["never_have_i_ever"]
|
|
65
|
-
and active["never_have_i_ever"]
|
|
66
|
-
):
|
|
67
|
-
return api.tord(
|
|
68
|
-
"https://api.truthordarebot.xyz/api/nhie", args[0] if args else None
|
|
69
|
-
)
|
|
70
|
-
elif command in keywords["commands"]["paranoia"] and active["paranoia"]:
|
|
71
|
-
return api.tord(
|
|
72
|
-
"https://api.truthordarebot.xyz/api/paranoia", args[0] if args else None
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
elif command in keywords["commands"]["qr_code"] and active["qr_code"]:
|
|
76
|
-
if args:
|
|
77
|
-
return commands.qr(args[0])
|
|
78
|
-
else:
|
|
79
|
-
return error_messages["missing_argument"]
|
|
80
|
-
|
|
81
|
-
elif command in keywords["commands"]["calculate"]:
|
|
82
|
-
if args:
|
|
83
|
-
return commands.calculate(args[0])
|
|
84
|
-
else:
|
|
85
|
-
return error_messages["missing_argument"]
|
|
86
|
-
|
|
87
|
-
elif command in keywords["commands"]["bitcoin"] and active["bitcoin"]:
|
|
88
|
-
return api.bitcoin(args[0] if args else "usd")
|
|
89
|
-
|
|
90
|
-
elif command in keywords["commands"]["ascii_art"]:
|
|
91
|
-
return commands.ascii()
|
|
92
|
-
|
|
93
|
-
elif command in keywords["commands"]["currency"] and active["currency"]:
|
|
94
|
-
if not len(args) >= 3:
|
|
95
|
-
return error_messages["missing_argument"]
|
|
96
|
-
return api.currency(args[1], args[2], args[0])
|
|
97
|
-
|
|
98
|
-
elif command in keywords["money"]["check_balance"]:
|
|
99
|
-
if args:
|
|
100
|
-
return money_system.check_balance(args[0])
|
|
101
|
-
else:
|
|
102
|
-
return error_messages["missing_argument"]
|
|
103
|
-
elif command in keywords["money"]["add_money"] and (
|
|
104
|
-
message.author.guild_permissions.administrator or config["bot_admins"]
|
|
105
|
-
):
|
|
106
|
-
if len(args) >= 2:
|
|
107
|
-
try:
|
|
108
|
-
amount = int(args[1])
|
|
109
|
-
return money_system.add_money(args[0], amount)
|
|
110
|
-
except ValueError:
|
|
111
|
-
return f"Invalid amount: {args[1]}"
|
|
112
|
-
else:
|
|
113
|
-
return "No amount given."
|
|
114
|
-
elif command in keywords["money"]["remove_money"] and (
|
|
115
|
-
message.author.guild_permissions.administrator or config["bot_admins"]
|
|
116
|
-
):
|
|
117
|
-
if len(args) >= 2:
|
|
118
|
-
try:
|
|
119
|
-
amount = int(args[1])
|
|
120
|
-
return money_system.remove_money(args[0], amount)
|
|
121
|
-
except ValueError:
|
|
122
|
-
return f"Invalid amount: {args[1]}"
|
|
123
|
-
else:
|
|
124
|
-
return "No amount given."
|
|
125
|
-
|
|
126
|
-
elif command == "purge" and active["purge"]:
|
|
127
|
-
if message.author.guild_permissions.administrator:
|
|
128
|
-
await message.channel.purge()
|
|
129
|
-
return "All messages deleted."
|
|
130
|
-
|
|
131
|
-
else:
|
|
132
|
-
return None
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
async def settings(message):
|
|
136
|
-
|
|
137
|
-
command_parts = message.content[len(config["settings_prefix"]) :].strip().split()
|
|
138
|
-
|
|
139
|
-
command = command_parts[0]
|
|
140
|
-
args = command_parts[1:]
|
|
141
|
-
|
|
142
|
-
cache_dir = user_cache_dir("Comprobot", appauthor=False)
|
|
143
|
-
os.makedirs(cache_dir, exist_ok=True)
|
|
144
|
-
|
|
145
|
-
if command in keywords["settings"]["profile_picture"]:
|
|
146
|
-
if not message.attachments:
|
|
147
|
-
return error_messages["no_attachments"]
|
|
148
|
-
if client is None or client.user is None:
|
|
149
|
-
return error_messages["bot_unavailable"]
|
|
150
|
-
new_pfp = message.attachments[0]
|
|
151
|
-
await new_pfp.save(f"{cache_dir}/pfp.png")
|
|
152
|
-
with open(f"{cache_dir}/pfp.png", "rb") as image_file:
|
|
153
|
-
image_data = image_file.read()
|
|
154
|
-
await client.user.edit(avatar=image_data)
|
|
155
|
-
return choice(output["settings"]["profile_picture_applied"])
|
|
156
|
-
|
|
157
|
-
elif command in keywords["settings"]["banner"]:
|
|
158
|
-
if not message.attachments:
|
|
159
|
-
return error_messages["no_attachments"]
|
|
160
|
-
if client is None or client.user is None:
|
|
161
|
-
return error_messages["bot_unavailable"]
|
|
162
|
-
new_banner = message.attachments[0]
|
|
163
|
-
await new_banner.save(f"{cache_dir}/banner.png")
|
|
164
|
-
with open(f"{cache_dir}/banner.png", "rb") as image_file:
|
|
165
|
-
image_data = image_file.read()
|
|
166
|
-
await client.user.edit(banner=image_data)
|
|
167
|
-
return choice(output["settings"]["banner_applied"])
|
|
168
|
-
|
|
169
|
-
elif command in keywords["settings"]["change_name"]:
|
|
170
|
-
if len(args) < 2:
|
|
171
|
-
return error_messages["missing_argument"]
|
|
172
|
-
if client is None or client.user is None:
|
|
173
|
-
return error_messages["bot_unavailable"]
|
|
174
|
-
await client.user.edit(username=args[1])
|
|
175
|
-
return choice(output["settings"]["nickname_applied"]).replace(
|
|
176
|
-
"{{NAME}}", " ".join(args[1:])
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
elif command in keywords["settings"]["change_keywords"]:
|
|
180
|
-
if len(args) < 2:
|
|
181
|
-
return error_messages["missing_argument"]
|
|
182
|
-
|
|
183
|
-
target_category = None
|
|
184
|
-
for category in keywords:
|
|
185
|
-
if args[0] in keywords[category]:
|
|
186
|
-
target_category = category
|
|
187
|
-
break
|
|
188
|
-
|
|
189
|
-
if target_category is None:
|
|
190
|
-
return error_messages["unknown_argument"]
|
|
191
|
-
|
|
192
|
-
keywords[target_category][args[0]] = args[1:]
|
|
193
|
-
|
|
194
|
-
data.save_toml(keywords, f"{user_data_dir('Comprobot')}/keywords.toml")
|
|
195
|
-
|
|
196
|
-
return (
|
|
197
|
-
choice(output["settings"]["keywords_applied"])
|
|
198
|
-
.replace("{{KEYWORDS}}", " ".join(args[1:]))
|
|
199
|
-
.replace("{{COMMAND}}", args[0])
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
else:
|
|
203
|
-
return error_messages["unknown_command"]
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
async def games(message):
|
|
207
|
-
pass
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
async def process(message):
|
|
211
|
-
print(
|
|
212
|
-
"\033[90m"
|
|
213
|
-
+ f"[{message.channel}] "
|
|
214
|
-
+ "\033[36m"
|
|
215
|
-
+ f"{message.author.name}: "
|
|
216
|
-
+ "\033[0m"
|
|
217
|
-
+ message.content
|
|
218
|
-
)
|
|
219
|
-
|
|
220
|
-
global response
|
|
221
|
-
|
|
222
|
-
if message.author == client.user:
|
|
223
|
-
return
|
|
224
|
-
response = None
|
|
225
|
-
|
|
226
|
-
await check_message(message)
|
|
227
|
-
|
|
228
|
-
if message.content.startswith(cast(str, config["command_prefix"])):
|
|
229
|
-
async with message.channel.typing():
|
|
230
|
-
response = await command(message)
|
|
231
|
-
elif message.content.startswith(cast(str, config["settings_prefix"])):
|
|
232
|
-
async with message.channel.typing():
|
|
233
|
-
response = await settings(message)
|
|
234
|
-
|
|
235
|
-
is_reply_to_bot = False
|
|
236
|
-
user_id = client.user.id if client.user else None
|
|
237
|
-
if message.reference and message.reference.message_id and user_id:
|
|
238
|
-
try:
|
|
239
|
-
ref_msg = await message.channel.fetch_message(message.reference.message_id)
|
|
240
|
-
is_reply_to_bot = ref_msg.author.id == user_id
|
|
241
|
-
except discord.NotFound:
|
|
242
|
-
pass
|
|
243
|
-
|
|
244
|
-
if (
|
|
245
|
-
f"<@{user_id}>" in message.content
|
|
246
|
-
or (is_reply_to_bot if ai["answer_to_reply"] else False)
|
|
247
|
-
and ai["activate_ai"]
|
|
248
|
-
):
|
|
249
|
-
async with message.channel.typing():
|
|
250
|
-
response = await chat(message)
|
|
251
|
-
|
|
252
|
-
if response:
|
|
253
|
-
async with message.channel.typing():
|
|
254
|
-
content = str(response)
|
|
255
|
-
for chunk in [content[i : i + 2000] for i in range(0, len(content), 2000)]:
|
|
256
|
-
await message.channel.send(chunk)
|
comprobot-2.1.0/README.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Comprobot
|
|
2
|
-
|
|
3
|
-
Comprobot is a highly-customizable, open-source Discord bot that you can run on your own server.
|
|
4
|
-
|
|
5
|
-
It’s built with Python, has a wide range of fun and useful commands, and is designed to be easy to extend. You can add new commands, customize outputs , or change the behaviour of existing ones. You can also easily edit the keywords of existing commands and customize their outputs.
|
|
6
|
-
|
|
7
|
-
The bot also comes with built-in AI capabilities when pinging the bot, with Ollama, Groq and Gemini as available providers.
|
|
8
|
-
|
|
9
|
-
## Documentation
|
|
10
|
-
|
|
11
|
-
You can find the whole documentation [here](https://badluma.github.io/Comprobot-Docs/).
|
|
12
|
-
|
|
13
|
-
## License
|
|
14
|
-
|
|
15
|
-
MIT
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
README.md
|
|
2
|
-
pyproject.toml
|
|
3
|
-
Comprobot/__init__.py
|
|
4
|
-
Comprobot/__main__.py
|
|
5
|
-
Comprobot/api.py
|
|
6
|
-
Comprobot/bot.py
|
|
7
|
-
Comprobot/commands.py
|
|
8
|
-
Comprobot/data.py
|
|
9
|
-
Comprobot/functions.py
|
|
10
|
-
Comprobot/main.py
|
|
11
|
-
Comprobot/moderation.py
|
|
12
|
-
Comprobot/money_system.py
|
|
13
|
-
Comprobot/onboarding.py
|
|
14
|
-
Comprobot/process.py
|
|
15
|
-
Comprobot/templates.py
|
|
16
|
-
comprobot.egg-info/PKG-INFO
|
|
17
|
-
comprobot.egg-info/SOURCES.txt
|
|
18
|
-
comprobot.egg-info/dependency_links.txt
|
|
19
|
-
comprobot.egg-info/entry_points.txt
|
|
20
|
-
comprobot.egg-info/requires.txt
|
|
21
|
-
comprobot.egg-info/top_level.txt
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Comprobot
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|