Compare commits

...
Sign in to create a new pull request.

16 commits

Author SHA1 Message Date
b08b224476 Merge branch 'dev' into license_change 2025-06-15 06:54:56 -07:00
7c4fd5fc41 Merge pull request 'any-env-and-bind-address' (#58) from any-env-and-bind-address into dev
Reviewed-on: #58
Reviewed-by: VD15 <valkyriedev15@gmail.com>
2025-06-13 14:54:27 -07:00
Moon
24bfe88dc1 rm obsolete print 2025-06-14 05:57:01 +09:00
Moon
1a35750d0a restrict characters in KEMOVERSE_ENV 2025-06-14 05:51:49 +09:00
Moon
d416ae1b2d rm limitation on KEMOVERSE_ENV from another place. 2025-06-14 05:47:43 +09:00
Moon
7fd4d5db25 indicate on startup if whitelisting is enabled. 2025-06-14 05:17:39 +09:00
Moon
f4f847e577 make sure UseWhitelist is a boolean, default to True 2025-06-14 05:14:40 +09:00
Moon
2ef70801c7 fix config module ref 2025-06-14 05:08:59 +09:00
Moon
fdf21b3f5f rm check for env dev or prod so any can be used 2025-06-14 04:43:43 +09:00
49e49bd187 Merge branch 'dev' into license_change 2025-06-13 06:56:21 -07:00
1984b67523 Merge pull request 'fix-missing-weight-column in dev' (#57) from fix-missing-weight-column into dev
Reviewed-on: #57
Reviewed-by: VD15 <valkyriedev15@gmail.com>
2025-06-13 04:14:53 -07:00
Moon
77d4fa13bb rm validation of removed weight 2025-06-13 19:31:23 +09:00
Moon
bd287b096a rm reference to weight column. 2025-06-13 18:47:54 +09:00
w
2b194f3c9e readme changes for license 2025-06-11 22:42:57 -03:00
w
df76fb9131 adding license to main files 2025-06-11 22:29:06 -03:00
w
81c890a83d license change 2025-06-11 22:06:17 -03:00
11 changed files with 517 additions and 393 deletions

722
LICENSE

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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):

View file

@ -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'])

View file

@ -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

View file

@ -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],

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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")"

View file

@ -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)