comprobot 1.0.0__py3-none-any.whl

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/__init__.py ADDED
File without changes
Comprobot/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .main import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
Comprobot/api.py ADDED
@@ -0,0 +1,200 @@
1
+ import requests
2
+
3
+ from data import error_messages
4
+
5
+
6
+ def access_api(url, parameter, error_message, headers=None):
7
+ if headers:
8
+ raw = requests.get(url, headers=headers)
9
+ else:
10
+ raw = requests.get(url)
11
+ if raw.status_code == 200:
12
+ try:
13
+ data = raw.json()
14
+ response = data[parameter]
15
+ except (requests.exceptions.JSONDecodeError, KeyError):
16
+ response = str(f"{error_message}")
17
+ except Exception as e:
18
+ response = str(f"{error_message} (Error {str(e)})")
19
+ else:
20
+ response = str(f"{error_message} (HTTP {raw.status_code})")
21
+
22
+ return response
23
+
24
+
25
+ # ---------- Commands ----------
26
+ def quote():
27
+ quote_response = requests.get("https://zenquotes.io/api/random")
28
+ try:
29
+ data = quote_response.json()
30
+ fetched_quote = data[0]["q"]
31
+ author = data[0]["a"]
32
+ response = f"""{fetched_quote}\n~{author}"""
33
+ except (requests.exceptions.JSONDecodeError, KeyError, IndexError):
34
+ response = error_messages["quote"]
35
+ return response
36
+
37
+
38
+ def meme():
39
+ return access_api("https://meme-api.com/gimme", "url", error_messages["meme"])
40
+
41
+
42
+ def waifu():
43
+ waifu1 = access_api(
44
+ "https://api.waifu.pics/sfw/waifu", "url", error_messages["waifu"]
45
+ )
46
+ waifu2 = access_api(
47
+ "https://api.waifu.pics/sfw/waifu", "url", error_messages["waifu"]
48
+ )
49
+ return f"""### Which one is better?
50
+
51
+ {waifu1}
52
+ {waifu2}"""
53
+
54
+
55
+ def duck():
56
+ return access_api("https://random-d.uk/api/random", "url", error_messages["duck"])
57
+
58
+
59
+ def dog():
60
+ return access_api("https://random.dog/woojson", "url", error_messages["dog"])
61
+
62
+
63
+ def cat():
64
+ raw = requests.get("https://api.thecatapi.com/v1/images/search")
65
+ if raw.status_code == 200:
66
+ try:
67
+ data = raw.json()
68
+ response = data[0]["url"]
69
+ except (requests.exceptions.JSONDecodeError, KeyError, IndexError):
70
+ response = error_messages["cat"]
71
+ else:
72
+ response = error_messages["cat"]
73
+ return response
74
+
75
+
76
+ def chuck():
77
+ return access_api(
78
+ "https://api.chucknorris.io/jokes/random", "value", error_messages["chuck"]
79
+ )
80
+
81
+
82
+ def fact():
83
+ return access_api(
84
+ "https://uselessfacts.jsph.pl/api/v2/facts/random",
85
+ "text",
86
+ error_messages["fact"],
87
+ )
88
+
89
+
90
+ def bible(
91
+ is_random, book_arg: str = "John", chapter_arg: int = 16, verse_arg: int = 32
92
+ ):
93
+
94
+ if is_random:
95
+ url = "https://bible-api.com/data/web/random"
96
+ else:
97
+ url = f"https://bible-api.com/{book_arg} {chapter_arg}:{verse_arg}"
98
+
99
+ bible_response = requests.get(url)
100
+ if bible_response.status_code == 200:
101
+ try:
102
+ data = bible_response.json()
103
+ if "random_verse" in data:
104
+ verse = data["random_verse"]
105
+ response = f"{verse['text']}\n{verse['book']} {verse['chapter']}:{verse['verse']}"
106
+ elif "text" in data and "reference" in data:
107
+ response = f"{data['text']}\n{data['reference']}"
108
+ else:
109
+ response = error_messages["passage_not_found"].replace(
110
+ r"{{PASSAGE}}", f"{book_arg} {chapter_arg}:{verse_arg}"
111
+ )
112
+ except (requests.exceptions.JSONDecodeError, KeyError):
113
+ response = error_messages["bible"]
114
+ elif bible_response.status_code == 404:
115
+ response = error_messages["passage_not_found"].replace(
116
+ r"{{PASSAGE}}", f"{book_arg} {chapter_arg}:{verse_arg}"
117
+ )
118
+ else:
119
+ response = f"{error_messages['bible']} (HTTP {bible_response.status_code})"
120
+ return response
121
+
122
+
123
+ def bitcoin(currency_parameter):
124
+ currency = currency_parameter.lower() if len(str(currency_parameter)) > 1 else "usd"
125
+ bitcoin_price = requests.get(
126
+ f"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies={currency}"
127
+ )
128
+ if bitcoin_price.status_code == 200:
129
+ data = bitcoin_price.json()
130
+ if "bitcoin" in data and currency in data["bitcoin"]:
131
+ response = f"bitcoin is at {data['bitcoin'][currency]} {currency} rn"
132
+ else:
133
+ response = error_messages["currency"]
134
+ else:
135
+ response = error_messages["bitcoin"]
136
+ return response
137
+
138
+
139
+ def tord(url, rating, max_retries=10):
140
+ for _ in range(max_retries):
141
+ response = requests.get(url)
142
+ if response.status_code != 200:
143
+ continue
144
+ data = response.json()
145
+ if not rating or data.get("rating") == rating:
146
+ return data["question"]
147
+ return None
148
+
149
+
150
+ def joke():
151
+ raw = requests.get("https://official-joke-api.appspot.com/jokes/random")
152
+ if raw.status_code == 200:
153
+ try:
154
+ data = raw.json()
155
+ response = f"{data['setup']} ||{data['punchline']}||"
156
+ except (requests.exceptions.JSONDecodeError, KeyError):
157
+ response = error_messages["joke"]
158
+ else:
159
+ response = error_messages["joke"]
160
+ return response
161
+
162
+
163
+ def currency(currency1, currency2, amount):
164
+ try:
165
+ amount = float(amount)
166
+ except (TypeError, ValueError):
167
+ return error_messages["currency"]
168
+
169
+ currency1 = currency1.lower()
170
+ currency2 = currency2.lower()
171
+
172
+ available_currencies = requests.get(
173
+ "https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies.json"
174
+ )
175
+
176
+ if available_currencies.status_code != 200:
177
+ return error_messages["unavailable"]
178
+
179
+ available_currencies = available_currencies.json()
180
+
181
+ if currency1 not in available_currencies:
182
+ return f"{error_messages['currency']} ({currency1})"
183
+ if currency2 not in available_currencies:
184
+ return f"{error_messages['currency']} ({currency2})"
185
+
186
+ raw_response = requests.get(
187
+ f"https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/{currency1}.json"
188
+ )
189
+
190
+ if raw_response.status_code != 200:
191
+ return error_messages["unavailable"]
192
+
193
+ raw_response = raw_response.json()
194
+
195
+ if currency1 not in raw_response or currency2 not in raw_response[currency1]:
196
+ return error_messages["currency"]
197
+
198
+ rate = raw_response[currency1][currency2]
199
+ response = rate * amount
200
+ return f"{amount} {currency1.upper()} = {response:.2f} {currency2.upper()}"
Comprobot/bot.py ADDED
@@ -0,0 +1,16 @@
1
+ import discord
2
+ import os
3
+ import dotenv
4
+ import appdirs
5
+
6
+ dotenv.load_dotenv(
7
+ dotenv.find_dotenv(
8
+ os.path.join(
9
+ appdirs.user_data_dir(appname="Comprobot", appauthor=False), ".env"
10
+ )
11
+ )
12
+ )
13
+
14
+ intents = discord.Intents.default()
15
+ intents.message_content = True
16
+ client = discord.Client(intents=intents)
Comprobot/commands.py ADDED
@@ -0,0 +1,20 @@
1
+ import random
2
+
3
+ from data import config, error_messages
4
+
5
+
6
+ def qr(link):
7
+ return f"https://api.qrserver.com/v1/create-qr-code/?size=150x150&data={link}"
8
+
9
+
10
+ def calculate(calculation):
11
+ try:
12
+ result = eval(calculation)
13
+ response = f"Result: {result}"
14
+ except Exception as e:
15
+ response = f"{error_messages['calculate']} (error {str(e)})"
16
+ return response
17
+
18
+
19
+ def ascii():
20
+ return random.choice(config["ascii_art"])
Comprobot/data.py ADDED
@@ -0,0 +1,79 @@
1
+ import os
2
+ from typing import Any, Dict, List
3
+
4
+ import appdirs
5
+ import tomlkit
6
+
7
+ import templates
8
+
9
+
10
+ def _get_data_path(filename):
11
+
12
+ base_dir = appdirs.user_data_dir(appname="Comprobot", appauthor=False)
13
+ return os.path.join(base_dir, filename)
14
+
15
+
16
+ def _ensure_file(path, content):
17
+
18
+ os.makedirs(os.path.dirname(path), exist_ok=True)
19
+ if not os.path.isfile(path):
20
+ with open(path, "w") as f:
21
+ f.write(content)
22
+
23
+
24
+ def _merge_defaults(data, defaults):
25
+ for key, value in defaults.items():
26
+ if key not in data:
27
+ data[key] = value
28
+ elif isinstance(value, dict) and isinstance(data.get(key), dict):
29
+ _merge_defaults(data[key], value)
30
+
31
+
32
+ def _load_or_create(path, template_content):
33
+ try:
34
+ with open(path, "rb") as f:
35
+ data = tomlkit.load(f)
36
+ except FileNotFoundError:
37
+ _ensure_file(path, template_content)
38
+ with open(path, "rb") as f:
39
+ data = tomlkit.load(f)
40
+
41
+ defaults = tomlkit.loads(template_content)
42
+ _merge_defaults(data, defaults)
43
+
44
+ if data != defaults:
45
+ with open(path, "w") as f:
46
+ tomlkit.dump(data, f)
47
+
48
+ return data
49
+
50
+
51
+ ai_str = templates.ai
52
+
53
+ error_messages: Dict[str, str] = _load_or_create(
54
+ _get_data_path("error-messages.toml"), templates.error_messages
55
+ )
56
+ success_messages: Dict[str, str] = _load_or_create(
57
+ _get_data_path("success_messages.toml"), templates.success_messages
58
+ )
59
+ config: Dict[str, Any] = _load_or_create(
60
+ _get_data_path("config.toml"), templates.config
61
+ )
62
+ keywords: Dict[str, Dict[str, List[str]]] = _load_or_create(
63
+ _get_data_path("keywords.toml"), templates.keywords
64
+ )
65
+ ai: Dict[str, Any] = _load_or_create(_get_data_path("ai.toml"), ai_str)
66
+ system_prompt_text = ai["system_prompt"]
67
+ money: Dict[str, Dict[str, int]] = _load_or_create(
68
+ _get_data_path("money.toml"), r"""balances = {}"""
69
+ )
70
+ active: Dict[str, bool] = _load_or_create(
71
+ _get_data_path("active.toml"), templates.active
72
+ )
73
+
74
+ _ensure_file(_get_data_path(".env"), templates.env_template)
75
+
76
+
77
+ def save_toml(data, path):
78
+ with open(path, "w") as f:
79
+ tomlkit.dump(data, f)
Comprobot/functions.py ADDED
@@ -0,0 +1,182 @@
1
+ import os
2
+ import re
3
+ from typing import Any, Dict, List, cast
4
+
5
+ import appdirs
6
+ import discord
7
+ import groq
8
+ import ollama
9
+ from google import genai
10
+ from google.genai.errors import ClientError
11
+
12
+ from bot import client
13
+ from data import ai, system_prompt_text
14
+
15
+
16
+ def para(count=1):
17
+ for i in range(count):
18
+ print()
19
+
20
+
21
+ def clear():
22
+ if os.name == "nt":
23
+ os.system("cls")
24
+ else:
25
+ os.system("clear")
26
+
27
+
28
+ async def direct_msg(message, author_message):
29
+ user = await client.fetch_user(author_message.author.id)
30
+ dm_channel = user.dm_channel
31
+ if dm_channel is None:
32
+ dm_channel = await user.create_dm()
33
+ try:
34
+ await dm_channel.send(message)
35
+ except (discord.Forbidden, discord.HTTPException):
36
+ print(f"Couldn't DM {author_message.author.name}.")
37
+
38
+
39
+ def demoji(text):
40
+ emoji_pattern = re.compile(
41
+ "["
42
+ "\U0001f600-\U0001f64f" # emoticons
43
+ "\U0001f300-\U0001f5ff" # symbols & pictographs
44
+ "\U0001f680-\U0001f6ff" # transport & map symbols
45
+ "\U0001f1e0-\U0001f1ff" # flags (iOS)
46
+ "\U00002702-\U000027b0"
47
+ "\U000024c2-\U0001f251"
48
+ "\U0001f926-\U0001f937"
49
+ "\U00010000-\U0010ffff"
50
+ "\u200d"
51
+ "\u2640-\u2642"
52
+ "\u2600-\u2b55"
53
+ "\u23cf"
54
+ "\u23e9"
55
+ "\u231a"
56
+ "\ufe0f" # variation selectors
57
+ "\u3030"
58
+ "]+",
59
+ flags=re.UNICODE,
60
+ )
61
+ return emoji_pattern.sub("", text)
62
+
63
+
64
+ def chat(message):
65
+ messages: List[Dict[str, Any]] = []
66
+ user_id = client.user.id if client.user else ""
67
+
68
+ if ai["provider"].lower() in ("ollama", "groq"):
69
+ messages.append(
70
+ {
71
+ "role": "user",
72
+ "content": message.content.replace(f"<@{user_id}>", ""),
73
+ }
74
+ )
75
+ elif ai["provider"].lower() == "gemini":
76
+ messages.append(
77
+ {
78
+ "role": "user",
79
+ "parts": [{"text": message.content.replace(f"<@{user_id}>", "")}],
80
+ }
81
+ )
82
+ else:
83
+ raise ValueError(f"Unknown provider: {ai['provider']}")
84
+
85
+ if ai["provider"].lower() == "ollama":
86
+ messages.insert(
87
+ 0,
88
+ {
89
+ "role": "system",
90
+ "content": system_prompt_text,
91
+ },
92
+ )
93
+ response = ollama.chat(model=cast(str, ai["model"]), messages=messages)
94
+ content: str = response.message.content or ""
95
+ elif ai["provider"].lower() == "gemini":
96
+ gemini_client = genai.Client(api_key=os.getenv("GEMINI"))
97
+ system_instruction = None
98
+ formatted_messages = []
99
+ for m in messages:
100
+ if m["role"] == "system":
101
+ system_instruction = m["content"]
102
+ else:
103
+ formatted_messages.append(
104
+ {
105
+ "role": m["role"],
106
+ "parts": [
107
+ {"text": m["content"] if "content" in m else m["parts"][0]}
108
+ ],
109
+ }
110
+ )
111
+ config = None
112
+ if system_instruction:
113
+ config = genai.types.GenerateContentConfig(
114
+ system_instruction=system_instruction
115
+ )
116
+ try:
117
+ response = gemini_client.models.generate_content(
118
+ model=ai["model"], contents=formatted_messages, config=config
119
+ )
120
+ content = response.text or ""
121
+ except ClientError as e:
122
+ if "NOT_FOUND" in str(e) or "404" in str(e):
123
+ available = gemini_client.models.list()
124
+ print(f"Available models: {[m.name for m in available]}")
125
+ raise
126
+ elif ai["provider"].lower() == "groq":
127
+ groq_client = groq.Groq(api_key=os.getenv("GROQ"))
128
+ formatted_messages = [
129
+ {"role": "system", "content": system_prompt_text},
130
+ messages[0],
131
+ ]
132
+ response = groq_client.chat.completions.create(
133
+ model=ai["model"], messages=formatted_messages
134
+ )
135
+ content = response.choices[0].message.content or ""
136
+ else:
137
+ raise ValueError(f"Unknown provider: {ai['provider']}")
138
+
139
+ if ai["provider"].lower() == "groq":
140
+ role = "assistant"
141
+ elif ai["provider"].lower() == "ollama":
142
+ role = "assistant"
143
+ elif ai["provider"].lower() == "gemini":
144
+ role = "model"
145
+ else:
146
+ raise ValueError(f"Unknown provider: {ai['provider']}")
147
+
148
+ messages.append(
149
+ {
150
+ "role": role,
151
+ "content": content,
152
+ }
153
+ )
154
+
155
+ max_total = 1 + cast(int, ai["max_messages_context"])
156
+ while len(messages) > max_total:
157
+ messages.pop(1)
158
+
159
+ if ai["remove_emojis"]:
160
+ content = demoji(content)
161
+ if ai["lower_response"]:
162
+ content = content.lower()
163
+
164
+ content = re.sub(r"<think>.*?</think>", "", content, flags=re.DOTALL).strip()
165
+ return content
166
+
167
+
168
+ def create(path, content, is_dir=False):
169
+ base_dir = appdirs.user_data_dir(appname="Comprobot", appauthor=False)
170
+
171
+ if not os.path.isdir(base_dir):
172
+ os.makedirs(base_dir, exist_ok=True)
173
+
174
+ final_path = os.path.join(base_dir, path)
175
+
176
+ if is_dir:
177
+ os.makedirs(final_path, exist_ok=True)
178
+ else:
179
+ os.makedirs(os.path.dirname(final_path), exist_ok=True)
180
+ if not os.path.isfile(final_path):
181
+ with open(final_path, "w") as file:
182
+ file.write(content)
Comprobot/main.py ADDED
@@ -0,0 +1,95 @@
1
+ import platform
2
+ import sys
3
+ from os import getenv as os_getenv
4
+ from os import path as os_path
5
+ from typing import cast
6
+
7
+ import appdirs
8
+ import discord
9
+ import dotenv
10
+
11
+ import process
12
+ from data import ai, config
13
+ from functions import chat, client, para
14
+
15
+ dotenv.load_dotenv(
16
+ dotenv.find_dotenv(
17
+ os_path.join(
18
+ appdirs.user_data_dir(appname="Comprobot", appauthor=False), ".env"
19
+ )
20
+ )
21
+ )
22
+
23
+
24
+ response = None
25
+ if sys.platform.startswith("win"):
26
+ print("Running on Windows (cmd)")
27
+ elif platform.system() in ["Linux", "Darwin"]:
28
+ pass
29
+ else:
30
+ print("Unknown OS")
31
+
32
+
33
+ @client.event
34
+ async def on_message(message):
35
+
36
+ print(
37
+ "\033[90m"
38
+ + f"[{message.channel}] "
39
+ + "\033[36m"
40
+ + f"{message.author.name}: "
41
+ + "\033[0m"
42
+ + message.content
43
+ )
44
+
45
+ global response
46
+
47
+ if message.author == client.user:
48
+ return
49
+ response = None
50
+
51
+ if message.content.startswith(cast(str, config["command_prefix"])):
52
+ async with message.channel.typing():
53
+ response = await process.command(message)
54
+ elif message.content.startswith(cast(str, config["settings_prefix"])):
55
+ async with message.channel.typing():
56
+ response = await process.settings(message)
57
+
58
+ is_reply_to_bot = False
59
+ user_id = client.user.id if client.user else None
60
+ if message.reference and message.reference.message_id and user_id:
61
+ try:
62
+ ref_msg = await message.channel.fetch_message(message.reference.message_id)
63
+ is_reply_to_bot = ref_msg.author.id == user_id
64
+ except discord.NotFound:
65
+ pass
66
+
67
+ if (f"<@{user_id}>" in message.content or is_reply_to_bot) and ai.get(
68
+ "activate_ai", False
69
+ ):
70
+ async with message.channel.typing():
71
+ response = chat(message)
72
+
73
+ if response:
74
+ async with message.channel.typing():
75
+ content = str(response)
76
+ for chunk in [content[i : i + 2000] for i in range(0, len(content), 2000)]:
77
+ await message.channel.send(chunk)
78
+
79
+
80
+ @client.event
81
+ async def on_ready():
82
+ user_name = client.user.name if client.user else "Unknown"
83
+ print(f"Logged in as {user_name}")
84
+ para()
85
+
86
+
87
+ def main():
88
+ print(
89
+ f"Configuration directory: {appdirs.user_data_dir(appname='Comprobot', appauthor=False)}"
90
+ )
91
+ token = os_getenv("BOT_TOKEN")
92
+ if token:
93
+ client.run(token)
94
+ else:
95
+ print("Error: BOT_TOKEN not found in environment variables")
@@ -0,0 +1,42 @@
1
+ import discord
2
+ from functions import direct_msg
3
+
4
+ async def ban(message):
5
+ member = message.guild.get_member(message.author.id)
6
+ if member is None:
7
+ try:
8
+ member = await message.guild.fetch_member(message.author.id)
9
+ except discord.NotFound:
10
+ print(f"Could not fetch member {message.author.name}.")
11
+
12
+ try:
13
+ try:
14
+ await member.ban(reason=f"Sending banned text")
15
+ except discord.Forbidden:
16
+ print(f"Insufficient permissions to ban {message.author.name}.")
17
+ await direct_msg(
18
+ f"Your account has been banned because your message contains banned text",
19
+ message,
20
+ )
21
+ except (discord.Forbidden, discord.HTTPException):
22
+ print(f"Couldn't DM {message.author.name}")
23
+
24
+ async def kick(message):
25
+ member = message.guild.get_member(message.author.id)
26
+ if member is None:
27
+ try:
28
+ member = await message.guild.fetch_member(message.author.id)
29
+ except discord.NotFound:
30
+ print(f"Could not fetch member {message.author.name}.")
31
+
32
+ try:
33
+ try:
34
+ await member.kick(reason=f"Sending banned text")
35
+ except (discord.Forbidden, discord.HTTPException):
36
+ print(f"Insufficient permissions to kick {message.author.name}")
37
+ await direct_msg(
38
+ f"Your account has been kicked because your message contains banned text",
39
+ message
40
+ )
41
+ except (discord.Forbidden, discord.HTTPException):
42
+ print(f"Couldn't DM {message.author.name}")
@@ -0,0 +1,35 @@
1
+ import data
2
+ from appdirs import user_data_dir
3
+
4
+
5
+ def add_money(username, amount):
6
+ data.money["members"][username] = data.money["members"].get(username, 0) + amount
7
+ data.save_toml(data.money, f"{user_data_dir('Comprobot')}/.dontchange/money.toml")
8
+ return f"{data.money['members'][username]}{data.config['money_symbol']} added to to {username}"
9
+
10
+
11
+ def remove_money(username, amount):
12
+ current = data.money["members"].get(username, 0)
13
+ if current < amount:
14
+ data.money["members"][username] = 0
15
+ data.save_toml(
16
+ data.money, f"{user_data_dir('Comprobot')}/.dontchange/money.toml"
17
+ )
18
+ return f"{username} doesn't have enough money. They now have 0{data.config['money_symbol']}."
19
+ else:
20
+ data.money["members"][username] -= amount
21
+ data.save_toml(
22
+ data.money, f"{user_data_dir('Comprobot')}/.dontchange/money.toml"
23
+ )
24
+ return f"{amount} subtracted from {username}. They now have {data.money['members'][username]}{data.config['money_symbol']}."
25
+
26
+
27
+ def check_balance(username):
28
+ try:
29
+ return f"{data.money['members'][username]}{data.config['money_symbol']}"
30
+ except KeyError:
31
+ data.money["members"][username] = 0
32
+ data.save_toml(
33
+ data.money, f"{user_data_dir('Comprobot')}/.dontchange/money.toml"
34
+ )
35
+ return f"0{data.config['money_symbol']}"
Comprobot/process.py ADDED
@@ -0,0 +1,197 @@
1
+ import os
2
+ from typing import Any
3
+
4
+ from appdirs import user_cache_dir, user_data_dir
5
+
6
+ import api
7
+ import commands
8
+ import data
9
+ import money_system
10
+ from bot import client
11
+ from data import active, config, error_messages, keywords, success_messages
12
+
13
+
14
+ async def command(ctx) -> str | None | Any:
15
+
16
+ command_parts = ctx.content[len(config["command_prefix"]) :].strip().split()
17
+
18
+ command = command_parts[0]
19
+ args = command_parts[1:]
20
+
21
+ if command in keywords["commands"]["quote"] and active["quote"]:
22
+ return api.quote()
23
+ elif command in keywords["commands"]["joke"] and active["joke"]:
24
+ return api.joke()
25
+ elif command in keywords["commands"]["meme"] and active["meme"]:
26
+ return api.meme()
27
+ elif command in keywords["commands"]["waifu"] and active["waifu"]:
28
+ return api.waifu()
29
+ elif command in keywords["commands"]["image"] and active["image"]:
30
+ if args[0] in keywords["commands"]["duck"] and active["duck"]:
31
+ return api.duck()
32
+ elif args[0] in keywords["commands"]["dog"] and active["dog"]:
33
+ return api.dog()
34
+ elif args[0] in keywords["commands"]["cat"] and active["cat"]:
35
+ return api.cat()
36
+ elif not args:
37
+ return error_messages["missing_argument"]
38
+ else:
39
+ return error_messages["unknown_argument"]
40
+ elif command in keywords["commands"]["chuck_norris"] and active["chuck_norris"]:
41
+ return api.chuck()
42
+ elif command in keywords["commands"]["fact"] and active["fact"]:
43
+ return api.fact()
44
+ elif command in keywords["commands"]["bible"] and active["bible"]:
45
+ if not args:
46
+ return api.bible(True)
47
+ if len(args) >= 3:
48
+ return api.bible(False, args[0], args[1], args[2])
49
+ return error_messages["passage_not_found"]
50
+ elif command in keywords["commands"]["truth"] and active["truth"]:
51
+ return api.tord(
52
+ "https://api.truthordarebot.xyz/v1/truth", args[0] if args else None
53
+ )
54
+ elif command in keywords["commands"]["dare"] and active["dare"]:
55
+ return api.tord(
56
+ "https://api.truthordarebot.xyz/api/dare", args[0] if args else None
57
+ )
58
+ elif command in keywords["commands"]["wyr"] and active["wyr"]:
59
+ return api.tord(
60
+ "https://api.truthordarebot.xyz/api/wyr", args[0] if args else None
61
+ )
62
+ elif (
63
+ command in keywords["commands"]["never_have_i_ever"]
64
+ and active["never_have_i_ever"]
65
+ ):
66
+ return api.tord(
67
+ "https://api.truthordarebot.xyz/api/nhie", args[0] if args else None
68
+ )
69
+ elif command in keywords["commands"]["paranoia"] and active["paranoia"]:
70
+ return api.tord(
71
+ "https://api.truthordarebot.xyz/api/paranoia", args[0] if args else None
72
+ )
73
+
74
+ elif command in keywords["commands"]["qr_code"] and active["qr_code"]:
75
+ if args:
76
+ return commands.qr(args[0])
77
+ else:
78
+ return error_messages["missing_argument"]
79
+
80
+ elif command in keywords["commands"]["calculate"]:
81
+ if args:
82
+ return commands.calculate(args[0])
83
+ else:
84
+ return error_messages["missing_argument"]
85
+
86
+ elif command in keywords["commands"]["ascii_art"]:
87
+ return commands.ascii()
88
+
89
+ elif command in keywords["commands"]["currency"] and active["currency"]:
90
+ if not len(args) >= 3:
91
+ return error_messages["missing_argument"]
92
+ return api.currency(args[0], args[1], args[2])
93
+
94
+ elif command in keywords["money"]["check_balance"]:
95
+ if args:
96
+ return money_system.check_balance(args[0])
97
+ else:
98
+ return error_messages["missing_argument"]
99
+ elif command in keywords["money"]["add_money"] and (
100
+ ctx.author.guild_permissions.administrator or config["bot_admins"]
101
+ ):
102
+ if len(args) >= 2:
103
+ try:
104
+ amount = int(args[1])
105
+ return money_system.add_money(args[0], amount)
106
+ except ValueError:
107
+ return f"Invalid amount: {args[1]}"
108
+ else:
109
+ return "No amount given."
110
+ elif command in keywords["money"]["remove_money"] and (
111
+ ctx.author.guild_permissions.administrator or config["bot_admins"]
112
+ ):
113
+ if len(args) >= 2:
114
+ try:
115
+ amount = int(args[1])
116
+ return money_system.remove_money(args[0], amount)
117
+ except ValueError:
118
+ return f"Invalid amount: {args[1]}"
119
+ else:
120
+ return "No amount given."
121
+
122
+ elif command == "purge" and active["purge"]:
123
+ if ctx.author.guild_permissions.administrator:
124
+ await ctx.channel.purge()
125
+ return "All messages deleted."
126
+
127
+ else:
128
+ return None
129
+
130
+
131
+ async def settings(ctx):
132
+
133
+ command_parts = ctx.content[len(config["settings_prefix"]) :].strip().split()
134
+
135
+ command = command_parts[0]
136
+ args = command_parts[1:]
137
+
138
+ cache_dir = user_cache_dir("Comprobot", appauthor=False)
139
+ os.makedirs(cache_dir, exist_ok=True)
140
+
141
+ if command in keywords["settings"]["profile_picture"]:
142
+ if not ctx.attachments:
143
+ return error_messages["no_attachments"]
144
+ if client is None or client.user is None:
145
+ return error_messages["bot_unavailable"]
146
+ new_pfp = ctx.attachments[0]
147
+ await new_pfp.save(f"{cache_dir}/pfp.png")
148
+ with open(f"{cache_dir}/pfp.png", "rb") as image_file:
149
+ image_data = image_file.read()
150
+ await client.user.edit(avatar=image_data)
151
+ return success_messages["profile_picture_applied"]
152
+
153
+ elif command in keywords["settings"]["banner"]:
154
+ if not ctx.attachments:
155
+ return error_messages["no_attachments"]
156
+ if client is None or client.user is None:
157
+ return error_messages["bot_unavailable"]
158
+ new_banner = ctx.attachments[0]
159
+ await new_banner.save(f"{cache_dir}/banner.png")
160
+ with open(f"{cache_dir}/banner.png", "rb") as image_file:
161
+ image_data = image_file.read()
162
+ await client.user.edit(banner=image_data)
163
+ return success_messages["banner_applied"]
164
+
165
+ elif command in keywords["settings"]["change_name"]:
166
+ if len(args) < 2:
167
+ return error_messages["missing_argument"]
168
+ if client is None or client.user is None:
169
+ return error_messages["bot_unavailable"]
170
+ await client.user.edit(username=args[1])
171
+ return success_messages["nickname_applied"]
172
+
173
+ elif command in keywords["settings"]["change_keywords"]:
174
+ if len(args) < 2:
175
+ return error_messages["missing_argument"]
176
+
177
+ target_category = None
178
+ for category in keywords:
179
+ if args[0] in keywords[category]:
180
+ target_category = category
181
+ break
182
+
183
+ if target_category is None:
184
+ return error_messages["unknown_argument"]
185
+
186
+ keywords[target_category][args[0]] = args[1:]
187
+
188
+ data.save_toml(keywords, f"{user_data_dir('Comprobot')}/keywords.toml")
189
+
190
+ return success_messages["keywords_applied"]
191
+
192
+ else:
193
+ return error_messages["unknown_command"]
194
+
195
+
196
+ async def games(ctx):
197
+ pass
Comprobot/templates.py ADDED
@@ -0,0 +1,137 @@
1
+ ai = r'''activate_ai = false
2
+
3
+ provider = "groq" # Available providers: "ollama", "gemini", "groq"
4
+ model = "qwen/qwen3-32b"
5
+ max_messages_context = 10
6
+ remove_emojis = true
7
+ lower_response = true
8
+
9
+ system_prompt = """
10
+ You are a helpful assistant that gives short, helpful answers.
11
+ Your answers can maximally be 1000 characters long.
12
+ """'''
13
+
14
+ config = r"""
15
+ commmand_prefix = "!"
16
+ settings_prefix = "s!"
17
+ music_prefix = "m!"
18
+
19
+ money_symbol = "$"
20
+ bot_admins = []
21
+ ascii_art = []
22
+ """
23
+
24
+ error_messages = r'''quote = "Failed to get a quote."
25
+ joke = "Failed to get a joke."
26
+ meme = "Failed to get a meme."
27
+ waifu = "Failed to get a waifu image."
28
+ duck = "Failed to get a duck image."
29
+ dog = "Failed to get a dog image."
30
+ cat = "Failed to get a cat image."
31
+ chuck = "Failed to get a Chuck Norris joke."
32
+ fact = "Failed to get a fact."
33
+ bible = "Failed to get a bible verse."
34
+ passage_not_found = "Couldn't find bible passage: {{PASSAGE}}"
35
+ truth = "Failed to get a truth question."
36
+ dare = "Failed to get a dare question."
37
+ wyr = "Failed to get a Would You Rather question."
38
+ never-hie = "Failed to get a Never Have I Ever question."
39
+ paranoia = "Failed to get a paranoia question."
40
+ calculate = "Invalid calculation. Use +-*/"
41
+ bitcoin = "Failed to get the current bitcoin price."
42
+ currency = "Unknown currency."
43
+ unavailable = "API unavailable."
44
+
45
+ unknown_command = "Unknown command."
46
+ unknown_argument = "Unknown argument."
47
+ missing_argument = "Missing argument."
48
+ no_attachment = "No attachment given."
49
+ bot_unavailable = "Bot not available."'''
50
+
51
+ keywords = r"""[commands]
52
+ quote = ["quote"]
53
+ joke = ["joke"]
54
+ meme = ["meme"]
55
+ waifu = ["waifu"]
56
+ image = ["image", "picture"]
57
+ duck = ["duck"]
58
+ dog = ["dog"]
59
+ cat = ["cat"]
60
+ chuck_norris = ["chuck", "norris", "chucknorris"]
61
+ fact = ["fact"]
62
+ bible = ["bible"]
63
+ calculate = ["calculate", "calc"]
64
+ bitcoin = ["bitcoin", "btc"]
65
+ currency = ["currency", "convert", "conv"]
66
+ qr_code = ["qr_code", "qr"]
67
+ ascii_art = ["ascii", "art"]
68
+
69
+ truth = ["truth"]
70
+ dare = ["dare"]
71
+ wyr = ["wyr"]
72
+ never_have_i_ever = ["never-have-i-ever", "nhie"]
73
+ paranoia = ["paranoia"]
74
+
75
+ [settings]
76
+ settings = ["config", "set", "settings"]
77
+ profile_picture = ["pfp", "picture", "pic"]
78
+ banner = ["banner"]
79
+ change_name = ["name", "nickname"]
80
+ change_keywords = ["keywords", "key"]
81
+
82
+ [money]
83
+ add_money = ["add", "add_money"]
84
+ remove_money = ["remove", "rm", "remove_money"]
85
+ check_balance = ["check", "check_balance", "balance"]"""
86
+
87
+ moderation = r""""""
88
+
89
+ success_messages = r"""profile_picture_applied = "Profile picture applied successfully."
90
+ banner_applied = "Banner applied successfully."
91
+ nickname_applied = "Name applied successfully."
92
+ bio_applied = "Bio applied successfully."
93
+ keywords_applied = "Custom keywords applied successfully."
94
+ """
95
+
96
+ env_template = r"""BOT_TOKEN=
97
+ GEMINI=
98
+ GROQ=
99
+ """
100
+
101
+ create_commands = r"""create("config.toml", config)
102
+ create("error-messages.toml", error_messages)
103
+ create("success_messages.toml", success_messages)
104
+ create("keywords.toml", keywords)
105
+ create("ai.toml", ai)
106
+ create("moderation.toml", moderation)
107
+ create("data/.do_not_touch/money.toml", money)
108
+ create("data/.do_not_touch/conversation_history.toml", conversation_history)
109
+ create(".env", env_template)"""
110
+
111
+ active = r"""
112
+ quote = true
113
+ joke = true
114
+ meme = true
115
+ waifu = true
116
+ image = true
117
+ duck = true
118
+ dog = true
119
+ cat = true
120
+ chuck_norris = true
121
+ fact = true
122
+ bible = true
123
+ calculate = true
124
+ bitcoin = true
125
+ currency = true
126
+ qr_code = true
127
+
128
+ truth = true
129
+ dare = true
130
+ wyr = true
131
+ never_have_i_ever = true
132
+ paranoia = true
133
+
134
+ nsfw = false
135
+
136
+ purge = true
137
+ """
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: comprobot
3
+ Version: 1.0.0
4
+ Summary: A self-hostable Discord bot built for maximum customization.
5
+ Author: badluma
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/badluma/comprobot
8
+ Project-URL: Repository, https://github.com/badluma/comprobot
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: discord.py[voice]
15
+ Requires-Dist: python-dotenv
16
+ Requires-Dist: requests
17
+ Requires-Dist: tomlkit
18
+ Requires-Dist: appdirs
19
+ Requires-Dist: ollama
20
+ Requires-Dist: google-genai
21
+ Requires-Dist: groq
22
+ Provides-Extra: dev
23
+ Requires-Dist: ruff; extra == "dev"
24
+
25
+ ## Description
26
+
27
+ Comprobot is a highly-customizable, open-source Discord bot that you can run on your own server.
28
+
29
+ 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.
30
+
31
+ The bot also comes with built-in AI capabilities when pinging the bot, with Ollama, Groq and Gemini as available providers.
32
+
33
+ ## Documentation
34
+
35
+ You can find the whole documentation [here](https://badluma.github.io/Comprobot-Docs/).
36
+
37
+ ## License
38
+
39
+ MIT
@@ -0,0 +1,17 @@
1
+ Comprobot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ Comprobot/__main__.py,sha256=Vdhw8YA1K3wPMlbJQYL5WqvRzAKVeZ16mZQFO9VRmCo,62
3
+ Comprobot/api.py,sha256=VPpx8MPTRBmvqnJaZ1Sp_cwLqSi8_kVhe9LfJooz65k,6233
4
+ Comprobot/bot.py,sha256=MZAlsH85LeLfQ0tNXIUi0LnWW1kc8olDb01bp00-Pqo,328
5
+ Comprobot/commands.py,sha256=Drqz6zJSRQrhHaShtnCI9Aa7r-ILF9-BEPhB5UCHOU4,444
6
+ Comprobot/data.py,sha256=Vh01ePPwF9VaUUKFDvfjeecxFPz5hQONrq6mGdqv2Zw,2116
7
+ Comprobot/functions.py,sha256=ZrpXuGj-27y0bxfr4i8jIMekok12xvu5SDsVUhSVgx4,5391
8
+ Comprobot/main.py,sha256=A40QCmONS6G3R4JH455f2qz10WH44bN79X13g_hglSY,2482
9
+ Comprobot/moderation.py,sha256=Y4g9uIAK5MzaG6uj_9CtZDyzxVnCSmVDuXe9yZ7xkTs,1547
10
+ Comprobot/money_system.py,sha256=KxLry0fdfvbDKTeK1ogKPMlUrHOu__FmiyNymWYAZN8,1383
11
+ Comprobot/process.py,sha256=1QBrNpvF_dqmdNFox_7SmHurOe5zfqQLtsgdG5-4PLU,7153
12
+ Comprobot/templates.py,sha256=q9U_CFYqyhomP9Laka_XvoxLwr_6_8vpq9tsQUYGSCs,4312
13
+ comprobot-1.0.0.dist-info/METADATA,sha256=xDyruGc51JRNe2EAsi7tmiQY5EIrotsxTi88SVYD2sA,1391
14
+ comprobot-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
15
+ comprobot-1.0.0.dist-info/entry_points.txt,sha256=EJVO_FmI-sDLot5tDo5X3-JjO2WNWSopCzrCPCf1F1o,54
16
+ comprobot-1.0.0.dist-info/top_level.txt,sha256=JTdFBqXgm2Qndrp60n3lwY4UbOJR3Z0HJ7LTwiDHJdo,10
17
+ comprobot-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ comprobot = Comprobot.__main__:main
@@ -0,0 +1 @@
1
+ Comprobot