# retoor <retoor@molodetz.nl>
from __future__ import annotations
import json
import logging
from typing import Any
from devplacepy.database import (
CUSTOMIZATION_LANGS,
CUSTOMIZATION_PREF_COLUMNS,
delete_custom_override,
get_custom_override,
get_customization_prefs,
list_custom_overrides,
set_custom_override,
set_customization_pref,
)
from devplacepy.services.devii.errors import ToolInputError
logger = logging.getLogger("devii.customization")
PREVIEW_CHARS = 160
class CustomizationController:
def __init__(self, owner_kind: str, owner_id: str) -> None:
self._owner_kind = owner_kind
self._owner_id = owner_id
async def dispatch(self, name: str, arguments: dict[str, Any]) -> str:
if name == "customize_list":
return self._list()
if name == "customize_get":
return self._get(arguments)
if name == "customize_set_css":
return self._set(arguments, "css", "css")
if name == "customize_set_js":
return self._set(arguments, "js", "js")
if name == "customize_reset":
return self._reset(arguments)
if name == "customize_set_enabled":
return self._set_enabled(arguments)
raise ToolInputError(f"Unknown customization tool: {name}")
def _scope(self, arguments: dict[str, Any]) -> str:
scope = str(arguments.get("scope", "")).strip()
if not scope:
raise ToolInputError(
"A 'scope' is required: 'global' or a page-type string."
)
return scope
def _lang(self, arguments: dict[str, Any]) -> str | None:
raw = arguments.get("lang")
if raw is None or str(raw).strip() == "":
return None
lang = str(raw).strip().lower()
if lang not in CUSTOMIZATION_LANGS:
raise ToolInputError("'lang' must be 'css' or 'js'.")
return lang
def _list(self) -> str:
rows = list_custom_overrides(self._owner_kind, self._owner_id)
items = [
{
"scope": row.get("scope"),
"lang": row.get("lang"),
"enabled": bool(row.get("enabled", 1)),
"chars": len(row.get("code") or ""),
"preview": (row.get("code") or "")[:PREVIEW_CHARS],
"updated_at": row.get("updated_at"),
}
for row in rows
]
prefs = get_customization_prefs(self._owner_kind, self._owner_id)
return json.dumps(
{
"status": "success",
"customizations": items,
"disabled_global": prefs["disable_global"],
"disabled_pagetype": prefs["disable_pagetype"],
},
ensure_ascii=False,
)
def _get(self, arguments: dict[str, Any]) -> str:
scope = self._scope(arguments)
lang = self._lang(arguments)
if lang is None:
raise ToolInputError("'lang' must be 'css' or 'js'.")
row = get_custom_override(self._owner_kind, self._owner_id, scope, lang)
if not row:
return json.dumps(
{"status": "success", "scope": scope, "lang": lang, "code": None},
ensure_ascii=False,
)
return json.dumps(
{
"status": "success",
"scope": scope,
"lang": lang,
"enabled": bool(row.get("enabled", 1)),
"code": row.get("code") or "",
},
ensure_ascii=False,
)
def _set(self, arguments: dict[str, Any], lang: str, code_key: str) -> str:
scope = self._scope(arguments)
code = arguments.get(code_key)
if code is None or str(code).strip() == "":
raise ToolInputError(f"'{code_key}' code is required.")
set_custom_override(self._owner_kind, self._owner_id, scope, lang, str(code))
return json.dumps(
{
"status": "success",
"saved": True,
"scope": scope,
"lang": lang,
"note": "Saved. Call reload_page so the user sees it applied by the server.",
},
ensure_ascii=False,
)
def _category(self, arguments: dict[str, Any]) -> str:
category = str(arguments.get("category", "")).strip().lower()
if category not in CUSTOMIZATION_PREF_COLUMNS:
raise ToolInputError("'category' must be 'global' or 'pagetype'.")
return category
def _enabled(self, arguments: dict[str, Any]) -> bool:
raw = arguments.get("enabled")
if isinstance(raw, bool):
return raw
return str(raw).strip().lower() in ("true", "1", "yes", "on")
def _set_enabled(self, arguments: dict[str, Any]) -> str:
if self._owner_kind != "user":
raise ToolInputError(
"Suppressing customizations is only available for signed-in users."
)
category = self._category(arguments)
enabled = self._enabled(arguments)
set_customization_pref(self._owner_id, category, disabled=not enabled)
return json.dumps(
{
"status": "success",
"category": category,
"enabled": enabled,
"note": "Saved. Call reload_page so the user sees it applied by the server.",
},
ensure_ascii=False,
)
def _reset(self, arguments: dict[str, Any]) -> str:
scope = self._scope(arguments)
lang = self._lang(arguments)
if scope == "all":
removed = delete_custom_override(
self._owner_kind, self._owner_id, scope=None, lang=lang
)
else:
removed = delete_custom_override(
self._owner_kind, self._owner_id, scope=scope, lang=lang
)
return json.dumps(
{
"status": "success",
"removed": removed,
"scope": scope,
"lang": lang or "all",
"note": "Removed. Call reload_page so the user sees the default restored.",
},
ensure_ascii=False,
)