Compare commits
16 commits
master
...
license_ch
Author | SHA1 | Date | |
---|---|---|---|
b08b224476 | |||
7c4fd5fc41 | |||
|
24bfe88dc1 | ||
|
1a35750d0a | ||
|
d416ae1b2d | ||
|
7fd4d5db25 | ||
|
f4f847e577 | ||
|
2ef70801c7 | ||
|
fdf21b3f5f | ||
49e49bd187 | |||
1984b67523 | |||
|
77d4fa13bb | ||
|
bd287b096a | ||
2b194f3c9e | |||
df76fb9131 | |||
81c890a83d |
11 changed files with 517 additions and 393 deletions
|
@ -58,7 +58,6 @@ def add_card(
|
|||
card_id = insert_card(
|
||||
stripped_name,
|
||||
rarity,
|
||||
RARITY_TO_WEIGHT[rarity],
|
||||
file_id
|
||||
)
|
||||
return card_id, file_id
|
||||
|
|
|
@ -1,9 +1,25 @@
|
|||
#Kemoverse - a gacha-style bot for the Fediverse.
|
||||
#Copyright © 2025 Waifu
|
||||
#
|
||||
#This program is free software: you can redistribute it and/or modify
|
||||
#it under the terms of the GNU Affero General Public License as
|
||||
#published by the Free Software Foundation, either version 3 of the
|
||||
#License, or (at your option) any later version.
|
||||
#
|
||||
#This program is distributed in the hope that it will be useful,
|
||||
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
#GNU Affero General Public License for more details.
|
||||
#
|
||||
#You should have received a copy of the GNU Affero General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import time
|
||||
import misskey as misskey
|
||||
from client import client_connection
|
||||
import db_utils as db
|
||||
|
||||
from config import NOTIFICATION_POLL_INTERVAL
|
||||
from config import NOTIFICATION_POLL_INTERVAL, USE_WHITELIST
|
||||
from notification import process_notifications
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -15,6 +31,10 @@ if __name__ == '__main__':
|
|||
# Setup default administrators
|
||||
db.setup_administrators()
|
||||
|
||||
# Show whitelist status
|
||||
whitelist_status = "enabled" if USE_WHITELIST else "disabled"
|
||||
print(f'Instance whitelisting: {whitelist_status}')
|
||||
|
||||
print('Listening for notifications...')
|
||||
while True:
|
||||
if not process_notifications(client):
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
'''Essentials for the bot to function'''
|
||||
import configparser
|
||||
import json
|
||||
import re
|
||||
from os import environ, path
|
||||
|
||||
|
||||
|
@ -13,8 +14,10 @@ def get_config_file() -> str:
|
|||
env: str | None = environ.get('KEMOVERSE_ENV')
|
||||
if not env:
|
||||
raise ConfigError('Error: KEMOVERSE_ENV is unset')
|
||||
if not (env in ['prod', 'dev']):
|
||||
raise ConfigError(f'Error: Invalid environment: {env}')
|
||||
|
||||
# Validate environment name contains only alphanumeric, dash, and underscore
|
||||
if not re.match(r'^[a-zA-Z0-9_-]+$', env):
|
||||
raise ValueError(f'KEMOVERSE_ENV "{env}" contains invalid characters. Only alphanumeric, dash (-), and underscore (_) are allowed.')
|
||||
|
||||
config_path: str = f'config_{env}.ini'
|
||||
|
||||
|
@ -23,6 +26,50 @@ def get_config_file() -> str:
|
|||
return config_path
|
||||
|
||||
|
||||
def normalize_user(user_string: str) -> str:
|
||||
"""
|
||||
Normalizes a user string to the format @user@domain.tld where domain is lowercase and user is case-sensitive
|
||||
|
||||
Args:
|
||||
user_string: User string in various formats
|
||||
|
||||
Returns:
|
||||
Normalized user string
|
||||
|
||||
Raises:
|
||||
ValueError: If the user string is invalid or domain is malformed
|
||||
"""
|
||||
if not user_string or not user_string.strip():
|
||||
raise ValueError("User string cannot be empty")
|
||||
|
||||
user_string = user_string.strip()
|
||||
|
||||
# Add leading @ if missing
|
||||
if not user_string.startswith('@'):
|
||||
user_string = '@' + user_string
|
||||
|
||||
# Split into user and domain parts
|
||||
parts = user_string[1:].split('@', 1) # Remove leading @ and split
|
||||
if len(parts) != 2:
|
||||
raise ValueError(f"Invalid user format: {user_string}. Expected @user@domain.tld")
|
||||
|
||||
username, domain = parts
|
||||
|
||||
if not username:
|
||||
raise ValueError("Username cannot be empty")
|
||||
|
||||
if not domain:
|
||||
raise ValueError("Domain cannot be empty")
|
||||
|
||||
# Validate domain format (basic check for valid domain structure)
|
||||
domain_pattern = r'^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$'
|
||||
if not re.match(domain_pattern, domain):
|
||||
raise ValueError(f"Invalid domain format: {domain}")
|
||||
|
||||
# Return normalized format: @user@domain.tld (domain lowercase, user case-sensitive)
|
||||
return f"@{username}@{domain.lower()}"
|
||||
|
||||
|
||||
def get_rarity_to_weight(
|
||||
config_section: configparser.SectionProxy) -> dict[int, float]:
|
||||
"""Parses Rarity_X keys from config and returns a {rarity: weight} dict."""
|
||||
|
@ -38,19 +85,26 @@ config = configparser.ConfigParser()
|
|||
config.read(get_config_file())
|
||||
|
||||
# Username for the bot
|
||||
USER = config['credentials']['User'].lower()
|
||||
if 'User' not in config['credentials'] or not config['credentials']['User'].strip():
|
||||
raise ConfigError("User must be specified in config.ini under [credentials]")
|
||||
|
||||
USER = normalize_user(config['credentials']['User'])
|
||||
# API key for the bot
|
||||
KEY = config['credentials']['Token']
|
||||
# Bot's Misskey instance URL
|
||||
INSTANCE = config['credentials']['Instance'].lower()
|
||||
|
||||
# Web server port
|
||||
WEB_PORT = config['application'].getint('WebPort', 5000)
|
||||
BIND_ADDRESS = config['application'].get('BindAddress', '127.0.0.1')
|
||||
|
||||
# Fedi handles in the traditional 'user@domain.tld' style, allows these users
|
||||
# to use extra admin exclusive commands with the bot
|
||||
ADMINS = json.loads(config['application']['DefaultAdmins'])
|
||||
# SQLite Database location
|
||||
DB_PATH = config['application']['DatabaseLocation']
|
||||
DB_PATH = config['application'].get('DatabaseLocation', './gacha_game.db')
|
||||
# Whether to enable the instance whitelist
|
||||
USE_WHITELIST = config['application']['UseWhitelist']
|
||||
USE_WHITELIST = config['application'].getboolean('UseWhitelist', True)
|
||||
|
||||
NOTIFICATION_POLL_INTERVAL = int(config['notification']['PollInterval'])
|
||||
NOTIFICATION_BATCH_SIZE = int(config['notification']['BatchSize'])
|
||||
|
|
|
@ -150,12 +150,11 @@ def is_player_administrator(username: str) -> bool:
|
|||
|
||||
|
||||
def insert_card(
|
||||
name: str, rarity: int, weight: float, file_id: str) -> int:
|
||||
name: str, rarity: int, file_id: str) -> int:
|
||||
'''Inserts a card'''
|
||||
CURSOR.execute(
|
||||
'INSERT INTO cards (name, rarity, weight, file_id) VALUES \
|
||||
(?, ?, ?, ?)',
|
||||
(name, rarity, weight, file_id)
|
||||
'INSERT INTO cards (name, rarity, file_id) VALUES (?, ?, ?)',
|
||||
(name, rarity, file_id)
|
||||
)
|
||||
card_id = CURSOR.lastrowid
|
||||
return card_id if card_id else 0
|
||||
|
|
|
@ -120,12 +120,6 @@ in order: name, rarity',
|
|||
be a number between 1 and 5',
|
||||
'attachment_urls': None
|
||||
}
|
||||
if not (is_float(arguments[2]) and 0.0 < float(arguments[2]) <= 1.0):
|
||||
return {
|
||||
'message': f'{author} Invalid drop weight: \'{arguments[2]}\' \
|
||||
must be a decimal value between 0.0 and 1.0',
|
||||
'attachment_urls': None
|
||||
}
|
||||
|
||||
card_id, file_id = add_card(
|
||||
name=arguments[0],
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
DefaultAdmins = ["@localadmin", "@remoteadmin@example.tld"]
|
||||
; SQLite Database location
|
||||
DatabaseLocation = ./gacha_game.db
|
||||
; Web server port (default: 5000)
|
||||
WebPort = 5000
|
||||
; Web server bind address (default: 127.0.0.1, set to 0.0.0.0 to listen on all interfaces)
|
||||
BindAddress = 127.0.0.1
|
||||
|
||||
; Whether to lmit access to the bot via an instance whitelist
|
||||
; The whitelist can be adjusted via the application
|
||||
UseWhitelist = False
|
||||
|
|
17
readme.md
17
readme.md
|
@ -61,6 +61,23 @@ A gacha-style bot for the Fediverse built with Python. Users can roll for charac
|
|||
|
||||
The bot is meant to feel *light, fun, and competitive*. Mixing social, gacha and duel tactics.
|
||||
|
||||
## 📝 License
|
||||
|
||||
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
|
||||
|
||||
This means:
|
||||
|
||||
- You are free to use, modify, and redistribute this software under the same license.
|
||||
|
||||
- If you modify and deploy the software (for example, as part of a web service), you must also make the source code of your modified version available to users of that service.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
The full license text can be found here: https://www.gnu.org/licenses/agpl-3.0.html
|
||||
|
||||
The AGPL is designed to ensure that software freedom is preserved, especially in networked environments. If you improve this project or build something on top of it, please give back to the community by sharing your changes too.
|
||||
|
||||
Unless another license is listed, every file in the project is licensed under the GNU General Public License version 3 (or at your option), any later version.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
|
||||
|
|
23
setup_db.py
23
setup_db.py
|
@ -1,3 +1,19 @@
|
|||
#Kemoverse - a gacha-style bot for the Fediverse.
|
||||
#Copyright © 2025 Waifu
|
||||
#
|
||||
#This program is free software: you can redistribute it and/or modify
|
||||
#it under the terms of the GNU Affero General Public License as
|
||||
#published by the Free Software Foundation, either version 3 of the
|
||||
#License, or (at your option) any later version.
|
||||
#
|
||||
#This program is distributed in the hope that it will be useful,
|
||||
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
#GNU Affero General Public License for more details.
|
||||
#
|
||||
#You should have received a copy of the GNU Affero General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import sqlite3
|
||||
import traceback
|
||||
import os
|
||||
|
@ -57,16 +73,14 @@ def perform_migration(cursor: sqlite3.Cursor, migration: tuple[int, str]) -> Non
|
|||
def get_db_path() -> str | DBNotFoundError:
|
||||
'''Gets the DB path from config.ini'''
|
||||
env = os.environ.get('KEMOVERSE_ENV')
|
||||
if not (env and env in ['prod', 'dev']):
|
||||
raise KemoverseEnvUnset
|
||||
|
||||
print(f'Running in "{env}" mode')
|
||||
|
||||
config_path = f'config_{env}.ini'
|
||||
|
||||
if not os.path.isfile(config_path):
|
||||
raise ConfigError(f'Could not find {config_path}')
|
||||
|
||||
print(f'Running in "{env}" mode')
|
||||
|
||||
config = ConfigParser()
|
||||
config.read(config_path)
|
||||
db_path = config['application']['DatabaseLocation']
|
||||
|
@ -96,7 +110,6 @@ def main():
|
|||
return
|
||||
except KemoverseEnvUnset:
|
||||
print('Error: KEMOVERSE_ENV is either not set or has an invalid value.')
|
||||
print('Please set KEMOVERSE_ENV to either "dev" or "prod" before running.')
|
||||
print(traceback.format_exc())
|
||||
return
|
||||
|
||||
|
|
16
startup.sh
16
startup.sh
|
@ -1,5 +1,21 @@
|
|||
#!/bin/bash
|
||||
|
||||
#Kemoverse - a gacha-style bot for the Fediverse.
|
||||
#Copyright © 2025 Waifu
|
||||
#
|
||||
#This program is free software: you can redistribute it and/or modify
|
||||
#it under the terms of the GNU Affero General Public License as
|
||||
#published by the Free Software Foundation, either version 3 of the
|
||||
#License, or (at your option) any later version.
|
||||
#
|
||||
#This program is distributed in the hope that it will be useful,
|
||||
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
#GNU Affero General Public License for more details.
|
||||
#
|
||||
#You should have received a copy of the GNU Affero General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
# Navigate to the project directory (optional)
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
|
|
27
web/app.py
27
web/app.py
|
@ -1,10 +1,31 @@
|
|||
import sqlite3
|
||||
#Kemoverse - a gacha-style bot for the Fediverse.
|
||||
#Copyright © 2025 Waifu
|
||||
#
|
||||
#This program is free software: you can redistribute it and/or modify
|
||||
#it under the terms of the GNU Affero General Public License as
|
||||
#published by the Free Software Foundation, either version 3 of the
|
||||
#License, or (at your option) any later version.
|
||||
#
|
||||
#This program is distributed in the hope that it will be useful,
|
||||
#but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
#GNU Affero General Public License for more details.
|
||||
#
|
||||
#You should have received a copy of the GNU Affero General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import sqlite3
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to Python path so we can import from bot/
|
||||
sys.path.append(str(Path(__file__).parent.parent))
|
||||
|
||||
from bot.config import WEB_PORT, BIND_ADDRESS, DB_PATH
|
||||
from flask import Flask, render_template, abort
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
app = Flask(__name__)
|
||||
DB_PATH = "./gacha_game.db" # Adjust path if needed
|
||||
|
||||
def get_db_connection():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
|
@ -68,4 +89,4 @@ def submit_character():
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
app.run(host=BIND_ADDRESS, port=WEB_PORT, debug=True)
|
||||
|
|
Loading…
Add table
Reference in a new issue