feat: user-customizable event voice rules (gift/blind box/enter/follow/like/guard/super chat)
- New event_rules.py: load/save event_rules.json, robust template renderer (missing vars -> empty, empty template -> muted), guard level mapping - danmaku_handler.py: custom SEND_GIFT callback to extract blind_gift_name; implements all event callbacks feeding the TTS queue per user rules - server.py: GET/POST /api/rules, load rules at startup, pass to client - Web UI: 'Event Voice Rules' panel (per-event toggle + template + save) - main.py CLI loads rules; build.py bundles event_rules.json - skip_free_gift option to mute silver free gifts - Rebuild portable zip (83.8 MB)
This commit is contained in:
@@ -18,6 +18,7 @@ from chinese2kana import chinese_to_kana, filter_kana
|
||||
from tts import TTSBridge
|
||||
from audio_player import play_wav_async, get_output_devices
|
||||
from bili_login import BiliLoginSession
|
||||
from event_rules import DEFAULT_RULES, load_rules, save_rules, rules_path
|
||||
|
||||
logger = logging.getLogger("bililive-tts-server")
|
||||
|
||||
@@ -27,6 +28,7 @@ def _get_static_dir() -> Path:
|
||||
return Path(__file__).parent / "static"
|
||||
|
||||
STATIC_DIR = _get_static_dir()
|
||||
event_rules = load_rules()
|
||||
|
||||
|
||||
class TTSService:
|
||||
@@ -68,7 +70,8 @@ class TTSService:
|
||||
await self._bridge.start()
|
||||
self._danmaku_client = DanmakuClient(
|
||||
room_id=room_id, queue=self._queue,
|
||||
cookies=self._cookies, message_format=message_format,
|
||||
cookies=self._cookies, rules=event_rules,
|
||||
message_format=message_format,
|
||||
)
|
||||
await self._danmaku_client.start()
|
||||
self.running = True
|
||||
@@ -240,6 +243,42 @@ async def api_logout(request: web.Request) -> web.Response:
|
||||
return web.json_response({"status": "logged_out"})
|
||||
|
||||
|
||||
async def api_rules_get(request: web.Request) -> web.Response:
|
||||
return web.json_response(event_rules)
|
||||
|
||||
|
||||
async def api_rules_post(request: web.Request) -> web.Response:
|
||||
global event_rules
|
||||
try:
|
||||
new_rules = await request.json()
|
||||
except Exception:
|
||||
return web.json_response({"error": "Invalid JSON"}, status=400)
|
||||
|
||||
from event_rules import ALL_EVENTS, DEFAULT_RULES
|
||||
merged = {
|
||||
"enabled": dict(DEFAULT_RULES["enabled"]),
|
||||
"templates": dict(DEFAULT_RULES["templates"]),
|
||||
"skip_free_gift": True,
|
||||
}
|
||||
if isinstance(new_rules.get("enabled"), dict):
|
||||
for k in ALL_EVENTS:
|
||||
if isinstance(new_rules["enabled"].get(k), bool):
|
||||
merged["enabled"][k] = new_rules["enabled"][k]
|
||||
if isinstance(new_rules.get("templates"), dict):
|
||||
for k in ALL_EVENTS:
|
||||
if isinstance(new_rules["templates"].get(k), str):
|
||||
merged["templates"][k] = new_rules["templates"][k]
|
||||
if isinstance(new_rules.get("skip_free_gift"), bool):
|
||||
merged["skip_free_gift"] = new_rules["skip_free_gift"]
|
||||
|
||||
try:
|
||||
save_rules(merged)
|
||||
except OSError as e:
|
||||
return web.json_response({"error": f"Failed to save rules: {e}"}, status=500)
|
||||
event_rules = merged
|
||||
return web.json_response({"status": "saved", "rules": event_rules})
|
||||
|
||||
|
||||
def create_app() -> web.Application:
|
||||
app = web.Application()
|
||||
app.router.add_get("/", index_handler)
|
||||
@@ -251,6 +290,8 @@ def create_app() -> web.Application:
|
||||
app.router.add_get("/api/qr/poll", api_qr_poll)
|
||||
app.router.add_get("/api/login/status", api_login_status)
|
||||
app.router.add_get("/api/logout", api_logout)
|
||||
app.router.add_get("/api/rules", api_rules_get)
|
||||
app.router.add_post("/api/rules", api_rules_post)
|
||||
return app
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user