Added pydocstyle (D) rules to Ruff and fixed all violations.
CI / Formatting (push) Successful in 12s
CI / Linting (push) Successful in 13s
CI / Tests (Python 3.12) (push) Successful in 26s
CI / Tests (Python 3.13) (push) Successful in 25s
CI / Tests (Python 3.14) (push) Successful in 25s
CI / Type Checking (push) Successful in 26s
CI / Formatting (push) Successful in 12s
CI / Linting (push) Successful in 13s
CI / Tests (Python 3.12) (push) Successful in 26s
CI / Tests (Python 3.13) (push) Successful in 25s
CI / Tests (Python 3.14) (push) Successful in 25s
CI / Type Checking (push) Successful in 26s
This commit is contained in:
@@ -83,8 +83,7 @@ def on_command(
|
||||
requires_moderator: bool = False,
|
||||
cooldown: int | float = 0,
|
||||
) -> Callable[[CommandHandler], CommandHandler]:
|
||||
"""
|
||||
Decorator to register a command handler.
|
||||
"""Register a command handler.
|
||||
|
||||
:param name: Primary command name (case-insensitive).
|
||||
:param aliases: Optional list of alternative names.
|
||||
|
||||
+19
-35
@@ -36,8 +36,7 @@ class Config:
|
||||
config_path: str | Path = "config.yaml",
|
||||
overrides: dict[str, Any] | None = None,
|
||||
):
|
||||
"""
|
||||
Initialize the configuration manager.
|
||||
"""Initialize the configuration manager.
|
||||
|
||||
:param config_path: Path to the YAML config file.
|
||||
:param overrides: CLI overrides (keys match property names).
|
||||
@@ -91,9 +90,9 @@ class Config:
|
||||
default: Any = _UNSET,
|
||||
type_fn: type = str,
|
||||
) -> Any:
|
||||
"""
|
||||
Resolve a setting through the priority chain:
|
||||
CLI arg > env var > config file > default.
|
||||
"""Resolve a setting through the priority chain.
|
||||
|
||||
Priority order: CLI arg > env var > config file > default.
|
||||
|
||||
When *default* is a ``str``, the resolved value is coerced to ``str``
|
||||
(the implicit *type_fn*). For non-string types, pass both *default*
|
||||
@@ -123,8 +122,7 @@ class Config:
|
||||
|
||||
@property
|
||||
def webhook_secret(self) -> str:
|
||||
"""
|
||||
Secret string for the webhook URL path.
|
||||
"""Secret string for the webhook URL path.
|
||||
|
||||
The webhook endpoint is always /webhook/<secret>. A cryptographically
|
||||
secure value is generated on first run if not explicitly configured.
|
||||
@@ -139,8 +137,7 @@ class Config:
|
||||
|
||||
@property
|
||||
def webhook_path(self) -> str:
|
||||
"""
|
||||
URL path where Owncast sends webhooks.
|
||||
"""URL path where Owncast sends webhooks.
|
||||
|
||||
Always returns /webhook/<secret>. The secret is auto-generated
|
||||
on first run if not configured.
|
||||
@@ -207,8 +204,7 @@ class Config:
|
||||
|
||||
@property
|
||||
def public_base_url(self) -> str:
|
||||
"""
|
||||
Public base URL for Owlbot's web server.
|
||||
"""Public base URL for Owlbot's web server.
|
||||
|
||||
Used to construct URLs for module routes and the webhook endpoint.
|
||||
Falls back to ``owncast.url`` if not explicitly set.
|
||||
@@ -224,8 +220,7 @@ class Config:
|
||||
|
||||
@property
|
||||
def storage_dir(self) -> Path:
|
||||
"""
|
||||
Directory for module database files.
|
||||
"""Directory for module database files.
|
||||
|
||||
Each module gets its own database file named '<module_name>.db'.
|
||||
Defaults to 'data/' in the working directory.
|
||||
@@ -242,8 +237,7 @@ class Config:
|
||||
|
||||
@property
|
||||
def modules_dir(self) -> Path:
|
||||
"""
|
||||
Directory containing user modules.
|
||||
"""Directory containing user modules.
|
||||
|
||||
Built-in modules are loaded from the package regardless of this setting.
|
||||
Defaults to 'modules/' in the working directory.
|
||||
@@ -260,8 +254,7 @@ class Config:
|
||||
|
||||
@property
|
||||
def log_dir(self) -> Path | None:
|
||||
"""
|
||||
Directory for the log file.
|
||||
"""Directory for the log file.
|
||||
|
||||
When set, an ``owlbot.log`` file is written to this directory in
|
||||
addition to stdout. Returns ``None`` when unset (stdout only).
|
||||
@@ -405,8 +398,7 @@ class Config:
|
||||
logger.info(f"Configuration saved to: {self.config_path.absolute()}")
|
||||
|
||||
def is_module_enabled(self, module_name: str) -> bool:
|
||||
"""
|
||||
Check if a module is enabled.
|
||||
"""Check if a module is enabled.
|
||||
|
||||
Modules are enabled by default unless explicitly disabled with
|
||||
``modules.<name>.enabled: false`` in the config file.
|
||||
@@ -424,8 +416,7 @@ class Config:
|
||||
return True
|
||||
|
||||
def get_module_config(self, module_name: str) -> dict[str, Any]:
|
||||
"""
|
||||
Get the configuration dict for a module.
|
||||
"""Get the configuration dict for a module.
|
||||
|
||||
Returns merged defaults and config file values, with config file
|
||||
values taking precedence.
|
||||
@@ -443,8 +434,7 @@ class Config:
|
||||
return {**defaults, **config}
|
||||
|
||||
def set_module_config(self, module_name: str, config: dict[str, Any]) -> None:
|
||||
"""
|
||||
Update the configuration for a module at runtime and persist to disk.
|
||||
"""Update the configuration for a module at runtime and persist to disk.
|
||||
|
||||
:param module_name: The module name.
|
||||
:param config: Dict of configuration values to set.
|
||||
@@ -459,8 +449,7 @@ class Config:
|
||||
def register_module_defaults(
|
||||
self, module_name: str, defaults: dict[str, Any]
|
||||
) -> None:
|
||||
"""
|
||||
Register default configuration values for a module.
|
||||
"""Register default configuration values for a module.
|
||||
|
||||
Called by modules during setup to declare their expected config keys
|
||||
and default values. Missing keys are backfilled into the config file
|
||||
@@ -516,8 +505,7 @@ class ModuleConfig:
|
||||
"""Pre-scoped configuration for a specific module."""
|
||||
|
||||
def __init__(self, config: Config, module_name: str):
|
||||
"""
|
||||
Initialize a module-scoped configuration.
|
||||
"""Initialize a module-scoped configuration.
|
||||
|
||||
:param config: The parent Config object.
|
||||
:param module_name: The name of the module this config
|
||||
@@ -537,8 +525,7 @@ class ModuleConfig:
|
||||
return self._config.public_base_url
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
"""
|
||||
Get a config value by key.
|
||||
"""Get a config value by key.
|
||||
|
||||
:param key: The configuration key.
|
||||
:param default: Value to return if key is not found.
|
||||
@@ -547,16 +534,14 @@ class ModuleConfig:
|
||||
return self.as_dict().get(key, default)
|
||||
|
||||
def as_dict(self) -> dict[str, Any]:
|
||||
"""
|
||||
Get the full config dict for this module.
|
||||
"""Get the full config dict for this module.
|
||||
|
||||
:return: Dict of all configuration values.
|
||||
"""
|
||||
return self._config.get_module_config(self._module_name)
|
||||
|
||||
def set(self, key: str, value: Any) -> None:
|
||||
"""
|
||||
Set a config value at runtime and persist to disk.
|
||||
"""Set a config value at runtime and persist to disk.
|
||||
|
||||
:param key: The configuration key.
|
||||
:param value: The value to set.
|
||||
@@ -566,8 +551,7 @@ class ModuleConfig:
|
||||
self._config.set_module_config(self._module_name, current)
|
||||
|
||||
def register_defaults(self, defaults: dict[str, Any]) -> None:
|
||||
"""
|
||||
Register default values for this module's config.
|
||||
"""Register default values for this module's config.
|
||||
|
||||
Called during setup() to declare expected config keys and their
|
||||
default values.
|
||||
|
||||
+7
-12
@@ -44,8 +44,7 @@ if TYPE_CHECKING:
|
||||
|
||||
@dataclass
|
||||
class ModuleContext:
|
||||
"""
|
||||
Shared services available to all module handlers.
|
||||
"""Shared services available to all module handlers.
|
||||
|
||||
Created once per module during loading and reused for all handler
|
||||
invocations. This bundles the common dependencies that every handler needs.
|
||||
@@ -83,6 +82,7 @@ class ModuleContext:
|
||||
logger: logging.Logger = field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""Derive the module-scoped logger from the module name."""
|
||||
self.logger = logging.getLogger(f"owlbot.modules.{self.module_name}")
|
||||
|
||||
|
||||
@@ -96,8 +96,7 @@ class PropagationState:
|
||||
|
||||
@dataclass
|
||||
class EventContext[E]:
|
||||
"""
|
||||
Context passed to event handlers.
|
||||
"""Context passed to event handlers.
|
||||
|
||||
Each handler invocation receives its own EventContext instance with the event
|
||||
data and access to shared services via the module context.
|
||||
@@ -165,16 +164,14 @@ class EventContext[E]:
|
||||
|
||||
@property
|
||||
def propagation_stopped(self) -> bool:
|
||||
"""
|
||||
Check if event propagation has been stopped by a handler.
|
||||
"""Check if event propagation has been stopped by a handler.
|
||||
|
||||
:return: True if stop_propagation() was called by any handler.
|
||||
"""
|
||||
return self._propagation.stopped
|
||||
|
||||
def stop_propagation(self, reason: str | None = None) -> None:
|
||||
"""
|
||||
Stop event from being dispatched to remaining handlers and commands.
|
||||
"""Stop event from being dispatched to remaining handlers and commands.
|
||||
|
||||
Once called, no further handlers will be invoked for this event, and
|
||||
command dispatch (for CHAT events) will be skipped.
|
||||
@@ -189,8 +186,7 @@ class EventContext[E]:
|
||||
|
||||
@dataclass
|
||||
class CommandContext:
|
||||
"""
|
||||
Context passed to command handlers.
|
||||
"""Context passed to command handlers.
|
||||
|
||||
Provides access to the parsed command data, original chat event context,
|
||||
and shared services. Like EventContext and RouteContext, all ModuleContext
|
||||
@@ -289,8 +285,7 @@ class CommandContext:
|
||||
|
||||
@dataclass
|
||||
class RouteContext:
|
||||
"""
|
||||
Context passed to HTTP route handlers.
|
||||
"""Context passed to HTTP route handlers.
|
||||
|
||||
Similar to EventContext but includes the aiohttp request object
|
||||
for accessing HTTP-specific data (body, headers, query params).
|
||||
|
||||
+14
-28
@@ -43,8 +43,7 @@ class EventType(StrEnum):
|
||||
|
||||
|
||||
def _parse_timestamp(ts: str | None) -> datetime | None:
|
||||
"""
|
||||
Parse an ISO 8601 timestamp string into a datetime.
|
||||
"""Parse an ISO 8601 timestamp string into a datetime.
|
||||
|
||||
Handles timezone suffixes and the Go zero-value timestamp that Owncast
|
||||
sends for missing/unset timestamps.
|
||||
@@ -91,8 +90,7 @@ class User:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> User:
|
||||
"""
|
||||
Create a User from webhook JSON data.
|
||||
"""Create a User from webhook JSON data.
|
||||
|
||||
:param data: The user data from the webhook payload.
|
||||
:return: A populated User instance.
|
||||
@@ -111,8 +109,7 @@ class User:
|
||||
|
||||
@property
|
||||
def is_moderator(self) -> bool:
|
||||
"""
|
||||
Check if the user has moderator privileges.
|
||||
"""Check if the user has moderator privileges.
|
||||
|
||||
:return: True if the user has the MODERATOR scope.
|
||||
"""
|
||||
@@ -133,8 +130,7 @@ class ChatEvent:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> ChatEvent:
|
||||
"""
|
||||
Create a ChatEvent from webhook JSON data.
|
||||
"""Create a ChatEvent from webhook JSON data.
|
||||
|
||||
:param data: The event data from the webhook payload.
|
||||
:return: A populated ChatEvent instance.
|
||||
@@ -168,8 +164,7 @@ class UserJoinedEvent:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> UserJoinedEvent:
|
||||
"""
|
||||
Create a UserJoinedEvent from webhook JSON data.
|
||||
"""Create a UserJoinedEvent from webhook JSON data.
|
||||
|
||||
:param data: The event data from the webhook payload.
|
||||
:return: A populated UserJoinedEvent instance.
|
||||
@@ -193,8 +188,7 @@ class UserPartedEvent:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> UserPartedEvent:
|
||||
"""
|
||||
Create a UserPartedEvent from webhook JSON data.
|
||||
"""Create a UserPartedEvent from webhook JSON data.
|
||||
|
||||
:param data: The event data from the webhook payload.
|
||||
:return: A populated UserPartedEvent instance.
|
||||
@@ -219,8 +213,7 @@ class NameChangedEvent:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> NameChangedEvent:
|
||||
"""
|
||||
Create a NameChangedEvent from webhook JSON data.
|
||||
"""Create a NameChangedEvent from webhook JSON data.
|
||||
|
||||
Note: The user object contains the OLD display name in user.display_name.
|
||||
|
||||
@@ -248,8 +241,7 @@ class StreamStartedEvent:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> StreamStartedEvent:
|
||||
"""
|
||||
Create a StreamStartedEvent from webhook JSON data.
|
||||
"""Create a StreamStartedEvent from webhook JSON data.
|
||||
|
||||
:param data: The event data from the webhook payload.
|
||||
:return: A populated StreamStartedEvent instance.
|
||||
@@ -275,8 +267,7 @@ class StreamStoppedEvent:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> StreamStoppedEvent:
|
||||
"""
|
||||
Create a StreamStoppedEvent from webhook JSON data.
|
||||
"""Create a StreamStoppedEvent from webhook JSON data.
|
||||
|
||||
:param data: The event data from the webhook payload.
|
||||
:return: A populated StreamStoppedEvent instance.
|
||||
@@ -305,8 +296,7 @@ class StreamStatus:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> StreamStatus:
|
||||
"""
|
||||
Create a StreamStatus from webhook JSON data.
|
||||
"""Create a StreamStatus from webhook JSON data.
|
||||
|
||||
:param data: The status data from the webhook payload.
|
||||
:return: A populated StreamStatus instance.
|
||||
@@ -336,8 +326,7 @@ class StreamTitleUpdatedEvent:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> StreamTitleUpdatedEvent:
|
||||
"""
|
||||
Create a StreamTitleUpdatedEvent from webhook JSON data.
|
||||
"""Create a StreamTitleUpdatedEvent from webhook JSON data.
|
||||
|
||||
:param data: The event data from the webhook payload.
|
||||
:return: A populated StreamTitleUpdatedEvent instance.
|
||||
@@ -364,8 +353,7 @@ class VisibilityUpdateEvent:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> VisibilityUpdateEvent:
|
||||
"""
|
||||
Create a VisibilityUpdateEvent from webhook JSON data.
|
||||
"""Create a VisibilityUpdateEvent from webhook JSON data.
|
||||
|
||||
:param data: The event data from the webhook payload.
|
||||
:return: A populated VisibilityUpdateEvent instance.
|
||||
@@ -407,8 +395,7 @@ _EVENT_TYPE_MAP: dict[EventType, type[Event]] = {
|
||||
|
||||
|
||||
def parse_event(data: dict[str, Any]) -> _ParsedEvent | None:
|
||||
"""
|
||||
Parse a raw webhook payload into a typed event.
|
||||
"""Parse a raw webhook payload into a typed event.
|
||||
|
||||
:param data: The JSON payload from Owncast's webhook.
|
||||
:return: Tuple of (event_type, event_instance), or None if the event type
|
||||
@@ -431,8 +418,7 @@ def parse_event(data: dict[str, Any]) -> _ParsedEvent | None:
|
||||
|
||||
|
||||
def log_event(event_type: EventType, event: Event) -> None:
|
||||
"""
|
||||
Log event details for debugging and monitoring.
|
||||
"""Log event details for debugging and monitoring.
|
||||
|
||||
:param event_type: The type of event being logged.
|
||||
:param event: The parsed event instance.
|
||||
|
||||
@@ -43,8 +43,7 @@ type EventHandler = "Callable[[EventContext[Any]], Awaitable[None]]"
|
||||
|
||||
|
||||
class Priority(IntEnum):
|
||||
"""
|
||||
Standard priority levels for event handlers.
|
||||
"""Standard priority levels for event handlers.
|
||||
|
||||
Higher values run first. Handlers at the same priority level run in
|
||||
registration order. Custom numeric values can be used for fine-grained control.
|
||||
@@ -61,8 +60,7 @@ def on_event(
|
||||
*event_types: EventType,
|
||||
priority: int = Priority.NORMAL,
|
||||
) -> Callable[[EventHandler], EventHandler]:
|
||||
"""
|
||||
Decorator to register a function as a handler for one or more event types.
|
||||
"""Register a function as a handler for one or more event types.
|
||||
|
||||
The decorated function will be called whenever an event of the specified
|
||||
type(s) is received. Handlers are executed sequentially in priority order
|
||||
|
||||
@@ -31,8 +31,7 @@ type LifecycleHandler = "Callable[[ModuleContext], Awaitable[None]]"
|
||||
|
||||
|
||||
def on_setup(func: LifecycleHandler) -> LifecycleHandler:
|
||||
"""
|
||||
Decorator to mark a function as a module setup hook.
|
||||
"""Mark a function as a module setup hook.
|
||||
|
||||
The decorated function will be called during module loading with a
|
||||
``ModuleContext``. Setup hooks run inside a storage transaction that
|
||||
@@ -52,8 +51,7 @@ def on_setup(func: LifecycleHandler) -> LifecycleHandler:
|
||||
|
||||
|
||||
def on_teardown(func: LifecycleHandler) -> LifecycleHandler:
|
||||
"""
|
||||
Decorator to mark a function as a module teardown hook.
|
||||
"""Mark a function as a module teardown hook.
|
||||
|
||||
The decorated function will be called during module unload or bot
|
||||
shutdown with the same ``ModuleContext`` from setup.
|
||||
|
||||
@@ -186,8 +186,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
def __init__(
|
||||
self, base_url: str, username: str, password: str, http_client: HttpClient
|
||||
):
|
||||
"""
|
||||
Initialize the admin client.
|
||||
"""Initialize the admin client.
|
||||
|
||||
:param base_url: The Owncast server URL (e.g., "https://stream.logal.dev").
|
||||
:param username: Admin username.
|
||||
@@ -203,24 +202,21 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def get_status(self) -> dict[str, Any]:
|
||||
"""
|
||||
Get the current server status including stream info and viewer count.
|
||||
"""Get the current server status including stream info and viewer count.
|
||||
|
||||
:return: Server status dict.
|
||||
"""
|
||||
return dict(await self._get("/api/admin/status"))
|
||||
|
||||
async def get_active_viewers(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get a list of currently active viewers.
|
||||
"""Get a list of currently active viewers.
|
||||
|
||||
:return: Viewer list as returned by the Owncast API.
|
||||
"""
|
||||
return list(await self._get("/api/admin/viewers"))
|
||||
|
||||
async def get_viewers_over_time(self, window_start: int) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get viewer count data over time for charting.
|
||||
"""Get viewer count data over time for charting.
|
||||
|
||||
:param window_start: Unix timestamp (seconds since epoch)
|
||||
for the start of the window.
|
||||
@@ -233,24 +229,21 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def get_hardware_stats(self) -> dict[str, Any]:
|
||||
"""
|
||||
Get server hardware statistics (CPU, memory, disk).
|
||||
"""Get server hardware statistics (CPU, memory, disk).
|
||||
|
||||
:return: Hardware stats dict.
|
||||
"""
|
||||
return dict(await self._get("/api/admin/hardwarestats"))
|
||||
|
||||
async def get_server_config(self) -> dict[str, Any]:
|
||||
"""
|
||||
Get the full server configuration.
|
||||
"""Get the full server configuration.
|
||||
|
||||
:return: Server configuration dict.
|
||||
"""
|
||||
return dict(await self._get("/api/admin/serverconfig"))
|
||||
|
||||
async def disconnect_stream(self) -> dict[str, Any]:
|
||||
"""
|
||||
Disconnect the current inbound stream.
|
||||
"""Disconnect the current inbound stream.
|
||||
|
||||
:return: API response confirming the disconnect.
|
||||
"""
|
||||
@@ -258,16 +251,14 @@ class OwncastAdminClient(OwncastClient):
|
||||
return dict(await self._get("/api/admin/disconnect"))
|
||||
|
||||
async def get_chat_messages(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get chat messages from the admin perspective.
|
||||
"""Get chat messages from the admin perspective.
|
||||
|
||||
:return: Chat message list as returned by the Owncast API.
|
||||
"""
|
||||
return list(await self._get("/api/admin/chat/messages"))
|
||||
|
||||
async def get_connected_chat_clients(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get currently connected chat clients.
|
||||
"""Get currently connected chat clients.
|
||||
|
||||
:return: Client list as returned by the Owncast API.
|
||||
"""
|
||||
@@ -276,8 +267,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
async def set_message_visibility(
|
||||
self, message_ids: list[str], visible: bool
|
||||
) -> str:
|
||||
"""
|
||||
Hide or show chat messages.
|
||||
"""Hide or show chat messages.
|
||||
|
||||
:param message_ids: List of message IDs to modify.
|
||||
:param visible: True to show messages, False to hide them.
|
||||
@@ -292,8 +282,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_user_enabled(self, user_id: str, enabled: bool) -> str:
|
||||
"""
|
||||
Enable or disable a chat user.
|
||||
"""Enable or disable a chat user.
|
||||
|
||||
:param user_id: The user ID to modify.
|
||||
:param enabled: True to enable, False to disable the user.
|
||||
@@ -307,16 +296,14 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def get_disabled_users(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get a list of disabled chat users.
|
||||
"""Get a list of disabled chat users.
|
||||
|
||||
:return: User list as returned by the Owncast API.
|
||||
"""
|
||||
return list(await self._get("/api/admin/chat/users/disabled"))
|
||||
|
||||
async def set_user_moderator(self, user_id: str, is_mod: bool) -> str:
|
||||
"""
|
||||
Grant or revoke moderator status for a user.
|
||||
"""Grant or revoke moderator status for a user.
|
||||
|
||||
:param user_id: The user ID to modify.
|
||||
:param is_mod: True to grant moderator, False to revoke.
|
||||
@@ -330,16 +317,14 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def get_moderators(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get a list of moderator users.
|
||||
"""Get a list of moderator users.
|
||||
|
||||
:return: User list as returned by the Owncast API.
|
||||
"""
|
||||
return list(await self._get("/api/admin/chat/users/moderators"))
|
||||
|
||||
async def ban_ip_address(self, ip: str) -> str:
|
||||
"""
|
||||
Ban an IP address from chat.
|
||||
"""Ban an IP address from chat.
|
||||
|
||||
:param ip: The IP address to ban.
|
||||
:return: Success message from the server.
|
||||
@@ -348,8 +333,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
return await self._post("/api/admin/chat/users/ipbans/create", {"value": ip})
|
||||
|
||||
async def unban_ip_address(self, ip: str) -> str:
|
||||
"""
|
||||
Remove an IP address ban.
|
||||
"""Remove an IP address ban.
|
||||
|
||||
:param ip: The IP address to unban.
|
||||
:return: Success message from the server.
|
||||
@@ -358,16 +342,14 @@ class OwncastAdminClient(OwncastClient):
|
||||
return await self._post("/api/admin/chat/users/ipbans/remove", {"value": ip})
|
||||
|
||||
async def get_ip_address_bans(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get a list of banned IP addresses.
|
||||
"""Get a list of banned IP addresses.
|
||||
|
||||
:return: IP ban list as returned by the Owncast API.
|
||||
"""
|
||||
return list(await self._get("/api/admin/chat/users/ipbans"))
|
||||
|
||||
async def set_stream_title(self, title: str) -> str:
|
||||
"""
|
||||
Set the stream title.
|
||||
"""Set the stream title.
|
||||
|
||||
:param title: The new stream title.
|
||||
:return: Success message from the server.
|
||||
@@ -377,8 +359,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_server_name(self, name: str) -> str:
|
||||
"""
|
||||
Set the server name.
|
||||
"""Set the server name.
|
||||
|
||||
:param name: The new server name.
|
||||
:return: Success message from the server.
|
||||
@@ -388,8 +369,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_server_summary(self, summary: str) -> str:
|
||||
"""
|
||||
Set the server summary.
|
||||
"""Set the server summary.
|
||||
|
||||
:param summary: The new server summary.
|
||||
:return: Success message from the server.
|
||||
@@ -399,8 +379,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_welcome_message(self, message: str) -> str:
|
||||
"""
|
||||
Set the welcome message shown to new viewers.
|
||||
"""Set the welcome message shown to new viewers.
|
||||
|
||||
:param message: The new welcome message.
|
||||
:return: Success message from the server.
|
||||
@@ -410,8 +389,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_offline_message(self, message: str) -> str:
|
||||
"""
|
||||
Set the message shown when the stream is offline.
|
||||
"""Set the message shown when the stream is offline.
|
||||
|
||||
:param message: The new offline message.
|
||||
:return: Success message from the server.
|
||||
@@ -421,8 +399,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_page_content(self, content: str) -> str:
|
||||
"""
|
||||
Set the custom page content (HTML/markdown below the player).
|
||||
"""Set the custom page content (HTML/markdown below the player).
|
||||
|
||||
:param content: The page content.
|
||||
:return: Success message from the server.
|
||||
@@ -432,8 +409,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_admin_password(self, password: str) -> str:
|
||||
"""
|
||||
Change the admin password.
|
||||
"""Change the admin password.
|
||||
|
||||
:param password: The new admin password.
|
||||
:return: Success message from the server.
|
||||
@@ -443,8 +419,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_server_url(self, url: str) -> str:
|
||||
"""
|
||||
Set the public server URL.
|
||||
"""Set the public server URL.
|
||||
|
||||
:param url: The new server URL.
|
||||
:return: Success message from the server.
|
||||
@@ -454,8 +429,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_custom_styles(self, css: str) -> str:
|
||||
"""
|
||||
Set custom CSS styles for the web interface.
|
||||
"""Set custom CSS styles for the web interface.
|
||||
|
||||
:param css: The CSS string.
|
||||
:return: Success message from the server.
|
||||
@@ -465,8 +439,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_custom_javascript(self, js: str) -> str:
|
||||
"""
|
||||
Set custom JavaScript for the web interface.
|
||||
"""Set custom JavaScript for the web interface.
|
||||
|
||||
:param js: The JavaScript string.
|
||||
:return: Success message from the server.
|
||||
@@ -476,8 +449,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_socket_host_override(self, host: str) -> str:
|
||||
"""
|
||||
Set the WebSocket host override.
|
||||
"""Set the WebSocket host override.
|
||||
|
||||
:param host: The WebSocket host override value.
|
||||
:return: Success message from the server.
|
||||
@@ -487,8 +459,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_ffmpeg_path(self, path: str) -> str:
|
||||
"""
|
||||
Set the path to the ffmpeg binary.
|
||||
"""Set the path to the ffmpeg binary.
|
||||
|
||||
:param path: The ffmpeg binary path.
|
||||
:return: Success message from the server.
|
||||
@@ -498,8 +469,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_video_serving_endpoint(self, endpoint: str) -> str:
|
||||
"""
|
||||
Set the video serving endpoint (e.g., for CDN).
|
||||
"""Set the video serving endpoint (e.g., for CDN).
|
||||
|
||||
:param endpoint: The video serving endpoint URL.
|
||||
:return: Success message from the server.
|
||||
@@ -509,8 +479,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_video_codec(self, codec: str) -> str:
|
||||
"""
|
||||
Set the video codec.
|
||||
"""Set the video codec.
|
||||
|
||||
:param codec: The video codec name.
|
||||
:return: Success message from the server.
|
||||
@@ -520,8 +489,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_chat_disabled(self, disabled: bool) -> str:
|
||||
"""
|
||||
Enable or disable the chat.
|
||||
"""Enable or disable the chat.
|
||||
|
||||
:param disabled: True to disable chat, False to enable it.
|
||||
:return: Success message from the server.
|
||||
@@ -531,8 +499,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_chat_join_messages_enabled(self, enabled: bool) -> str:
|
||||
"""
|
||||
Enable or disable chat join messages.
|
||||
"""Enable or disable chat join messages.
|
||||
|
||||
:param enabled: True to show join messages, False to hide them.
|
||||
:return: Success message from the server.
|
||||
@@ -542,8 +509,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_chat_established_mode(self, enabled: bool) -> str:
|
||||
"""
|
||||
Enable or disable established user mode for chat.
|
||||
"""Enable or disable established user mode for chat.
|
||||
|
||||
:param enabled: True to enable established mode.
|
||||
:return: Success message from the server.
|
||||
@@ -555,8 +521,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_chat_spam_protection(self, enabled: bool) -> str:
|
||||
"""
|
||||
Enable or disable chat spam protection.
|
||||
"""Enable or disable chat spam protection.
|
||||
|
||||
:param enabled: True to enable spam protection.
|
||||
:return: Success message from the server.
|
||||
@@ -568,8 +533,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_chat_slur_filter(self, enabled: bool) -> str:
|
||||
"""
|
||||
Enable or disable the chat slur filter.
|
||||
"""Enable or disable the chat slur filter.
|
||||
|
||||
:param enabled: True to enable the slur filter.
|
||||
:return: Success message from the server.
|
||||
@@ -579,8 +543,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_nsfw(self, nsfw: bool) -> str:
|
||||
"""
|
||||
Set the NSFW flag for the server.
|
||||
"""Set the NSFW flag for the server.
|
||||
|
||||
:param nsfw: True to mark the server as NSFW.
|
||||
:return: Success message from the server.
|
||||
@@ -588,8 +551,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
return await self._set_config_value("/api/admin/config/nsfw", nsfw, "NSFW flag")
|
||||
|
||||
async def set_directory_enabled(self, enabled: bool) -> str:
|
||||
"""
|
||||
Enable or disable listing in the Owncast directory.
|
||||
"""Enable or disable listing in the Owncast directory.
|
||||
|
||||
:param enabled: True to enable directory listing.
|
||||
:return: Success message from the server.
|
||||
@@ -599,8 +561,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_hide_viewer_count(self, hide: bool) -> str:
|
||||
"""
|
||||
Show or hide the viewer count.
|
||||
"""Show or hide the viewer count.
|
||||
|
||||
:param hide: True to hide the viewer count.
|
||||
:return: Success message from the server.
|
||||
@@ -610,8 +571,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_disable_search_indexing(self, disabled: bool) -> str:
|
||||
"""
|
||||
Enable or disable search engine indexing.
|
||||
"""Enable or disable search engine indexing.
|
||||
|
||||
:param disabled: True to disable search indexing.
|
||||
:return: Success message from the server.
|
||||
@@ -623,8 +583,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_forbidden_usernames(self, names: list[str]) -> str:
|
||||
"""
|
||||
Set the list of forbidden usernames.
|
||||
"""Set the list of forbidden usernames.
|
||||
|
||||
:param names: List of forbidden username strings.
|
||||
:return: Success message from the server.
|
||||
@@ -634,8 +593,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_suggested_usernames(self, names: list[str]) -> str:
|
||||
"""
|
||||
Set the list of suggested usernames for new viewers.
|
||||
"""Set the list of suggested usernames for new viewers.
|
||||
|
||||
:param names: List of suggested username strings.
|
||||
:return: Success message from the server.
|
||||
@@ -645,8 +603,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_tags(self, tags: list[str]) -> str:
|
||||
"""
|
||||
Set the server tags.
|
||||
"""Set the server tags.
|
||||
|
||||
:param tags: List of tag strings.
|
||||
:return: Success message from the server.
|
||||
@@ -654,8 +611,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
return await self._set_config_value("/api/admin/config/tags", tags, "tags")
|
||||
|
||||
async def set_federation_blocked_domains(self, domains: list[str]) -> str:
|
||||
"""
|
||||
Set the list of blocked federation domains.
|
||||
"""Set the list of blocked federation domains.
|
||||
|
||||
:param domains: List of domain strings to block.
|
||||
:return: Success message from the server.
|
||||
@@ -667,8 +623,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_stream_keys(self, keys: list[StreamKey]) -> str:
|
||||
"""
|
||||
Set the stream keys.
|
||||
"""Set the stream keys.
|
||||
|
||||
:param keys: List of :class:`StreamKey` objects.
|
||||
:return: Success message from the server.
|
||||
@@ -680,8 +635,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_video_variants(self, variants: list[VideoVariant]) -> str:
|
||||
"""
|
||||
Set the video output variants (quality levels).
|
||||
"""Set the video output variants (quality levels).
|
||||
|
||||
:param variants: List of :class:`VideoVariant` objects.
|
||||
:return: Success message from the server.
|
||||
@@ -693,8 +647,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_social_handles(self, handles: list[SocialHandle]) -> str:
|
||||
"""
|
||||
Set the social media handles displayed on the page.
|
||||
"""Set the social media handles displayed on the page.
|
||||
|
||||
:param handles: List of :class:`SocialHandle` objects.
|
||||
:return: Success message from the server.
|
||||
@@ -706,8 +659,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_external_actions(self, actions: list[ExternalAction]) -> str:
|
||||
"""
|
||||
Set the external actions (buttons/links in the player).
|
||||
"""Set the external actions (buttons/links in the player).
|
||||
|
||||
:param actions: List of :class:`ExternalAction` objects.
|
||||
:return: Success message from the server.
|
||||
@@ -727,8 +679,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
bucket: str,
|
||||
region: str,
|
||||
) -> str:
|
||||
"""
|
||||
Set the S3 storage configuration.
|
||||
"""Set the S3 storage configuration.
|
||||
|
||||
:param enabled: Whether S3 storage is enabled.
|
||||
:param endpoint: The S3 endpoint URL.
|
||||
@@ -757,8 +708,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
webhook: str,
|
||||
go_live_message: str,
|
||||
) -> str:
|
||||
"""
|
||||
Set the Discord notification configuration.
|
||||
"""Set the Discord notification configuration.
|
||||
|
||||
:param enabled: Whether Discord notifications are enabled.
|
||||
:param webhook: The Discord webhook URL.
|
||||
@@ -780,8 +730,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
enabled: bool,
|
||||
go_live_message: str,
|
||||
) -> str:
|
||||
"""
|
||||
Set the browser notification configuration.
|
||||
"""Set the browser notification configuration.
|
||||
|
||||
:param enabled: Whether browser notifications are enabled.
|
||||
:param go_live_message: The message shown when going live.
|
||||
@@ -797,8 +746,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_color_variables(self, variables: dict[str, Any]) -> str:
|
||||
"""
|
||||
Set the custom color variables for the web interface.
|
||||
"""Set the custom color variables for the web interface.
|
||||
|
||||
:param variables: Color variables dict.
|
||||
:return: Success message from the server.
|
||||
@@ -808,8 +756,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_stream_latency(self, level: int) -> str:
|
||||
"""
|
||||
Set the stream latency level.
|
||||
"""Set the stream latency level.
|
||||
|
||||
:param level: Latency level value.
|
||||
:return: Success message from the server.
|
||||
@@ -819,8 +766,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_rtmp_port(self, port: int) -> str:
|
||||
"""
|
||||
Set the RTMP server port.
|
||||
"""Set the RTMP server port.
|
||||
|
||||
:param port: The RTMP port number.
|
||||
:return: Success message from the server.
|
||||
@@ -830,8 +776,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_web_server_port(self, port: int) -> str:
|
||||
"""
|
||||
Set the web server port.
|
||||
"""Set the web server port.
|
||||
|
||||
:param port: The web server port number.
|
||||
:return: Success message from the server.
|
||||
@@ -841,8 +786,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_web_server_ip(self, ip: str) -> str:
|
||||
"""
|
||||
Set the web server bind IP address.
|
||||
"""Set the web server bind IP address.
|
||||
|
||||
:param ip: The IP address to bind to.
|
||||
:return: Success message from the server.
|
||||
@@ -852,8 +796,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_federation_enabled(self, enabled: bool) -> str:
|
||||
"""
|
||||
Enable or disable federation (ActivityPub).
|
||||
"""Enable or disable federation (ActivityPub).
|
||||
|
||||
:param enabled: True to enable federation.
|
||||
:return: Success message from the server.
|
||||
@@ -863,8 +806,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_federation_username(self, name: str) -> str:
|
||||
"""
|
||||
Set the federation (ActivityPub) username.
|
||||
"""Set the federation (ActivityPub) username.
|
||||
|
||||
:param name: The federation username.
|
||||
:return: Success message from the server.
|
||||
@@ -874,8 +816,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_federation_go_live_message(self, message: str) -> str:
|
||||
"""
|
||||
Set the message sent to followers when going live.
|
||||
"""Set the message sent to followers when going live.
|
||||
|
||||
:param message: The go-live notification message.
|
||||
:return: Success message from the server.
|
||||
@@ -887,8 +828,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def set_logo(self, base64_data_url: str) -> str:
|
||||
"""
|
||||
Set the server logo from a base64 data URL.
|
||||
"""Set the server logo from a base64 data URL.
|
||||
|
||||
:param base64_data_url: Data URL string
|
||||
(e.g., ``data:image/png;base64,iVBOR...``).
|
||||
@@ -899,8 +839,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def upload_emoji(self, name: str, data_base64: str) -> str:
|
||||
"""
|
||||
Upload a custom emoji.
|
||||
"""Upload a custom emoji.
|
||||
|
||||
:param name: The emoji name.
|
||||
:param data_base64: Base64-encoded image data for the emoji.
|
||||
@@ -912,8 +851,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def delete_emoji(self, name: str) -> str:
|
||||
"""
|
||||
Delete a custom emoji.
|
||||
"""Delete a custom emoji.
|
||||
|
||||
:param name: The emoji name to delete.
|
||||
:return: Success message from the server.
|
||||
@@ -922,16 +860,14 @@ class OwncastAdminClient(OwncastClient):
|
||||
return await self._post("/api/admin/emoji/delete", {"name": name})
|
||||
|
||||
async def get_webhooks(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get all registered webhooks.
|
||||
"""Get all registered webhooks.
|
||||
|
||||
:return: Webhook list as returned by the Owncast API.
|
||||
"""
|
||||
return list(await self._get("/api/admin/webhooks"))
|
||||
|
||||
async def create_webhook(self, url: str, events: list[str]) -> str:
|
||||
"""
|
||||
Create a new webhook.
|
||||
"""Create a new webhook.
|
||||
|
||||
:param url: The URL to send webhook events to.
|
||||
:param events: List of event type strings to subscribe to.
|
||||
@@ -943,8 +879,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def delete_webhook(self, webhook_id: int) -> str:
|
||||
"""
|
||||
Delete a webhook.
|
||||
"""Delete a webhook.
|
||||
|
||||
:param webhook_id: The ID of the webhook to delete.
|
||||
:return: Success message from the server.
|
||||
@@ -953,16 +888,14 @@ class OwncastAdminClient(OwncastClient):
|
||||
return await self._post("/api/admin/webhooks/delete", {"id": webhook_id})
|
||||
|
||||
async def get_access_tokens(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get all access tokens.
|
||||
"""Get all access tokens.
|
||||
|
||||
:return: Token list as returned by the Owncast API.
|
||||
"""
|
||||
return list(await self._get("/api/admin/accesstokens"))
|
||||
|
||||
async def create_access_token(self, name: str, scopes: list[str]) -> str:
|
||||
"""
|
||||
Create a new access token.
|
||||
"""Create a new access token.
|
||||
|
||||
:param name: Display name for the token.
|
||||
:param scopes: List of permission scope strings.
|
||||
@@ -974,8 +907,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def delete_access_token(self, token: str) -> str:
|
||||
"""
|
||||
Delete an access token.
|
||||
"""Delete an access token.
|
||||
|
||||
:param token: The token string to delete.
|
||||
:return: Success message from the server.
|
||||
@@ -985,8 +917,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
return await self._post("/api/admin/accesstokens/delete", {"token": token})
|
||||
|
||||
async def get_followers(self, offset: int = 0, limit: int = 25) -> dict[str, Any]:
|
||||
"""
|
||||
Get a paginated list of followers.
|
||||
"""Get a paginated list of followers.
|
||||
|
||||
:param offset: Number of followers to skip.
|
||||
:param limit: Maximum number of followers to return.
|
||||
@@ -999,24 +930,21 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def get_pending_follow_requests(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get pending follow requests.
|
||||
"""Get pending follow requests.
|
||||
|
||||
:return: Follower request list as returned by the Owncast API.
|
||||
"""
|
||||
return list(await self._get("/api/admin/followers/pending"))
|
||||
|
||||
async def get_blocked_followers(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get blocked and rejected followers.
|
||||
"""Get blocked and rejected followers.
|
||||
|
||||
:return: Follower list as returned by the Owncast API.
|
||||
"""
|
||||
return list(await self._get("/api/admin/followers/blocked"))
|
||||
|
||||
async def approve_follower(self, actor_iri: str, approved: bool) -> str:
|
||||
"""
|
||||
Approve or reject a follow request.
|
||||
"""Approve or reject a follow request.
|
||||
|
||||
:param actor_iri: The ActivityPub actor IRI of the follower.
|
||||
:param approved: True to approve, False to reject.
|
||||
@@ -1030,8 +958,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
)
|
||||
|
||||
async def send_federated_message(self, message: str) -> str:
|
||||
"""
|
||||
Send a message to all followers via federation.
|
||||
"""Send a message to all followers via federation.
|
||||
|
||||
:param message: The message text to send.
|
||||
:return: Success message from the server.
|
||||
@@ -1040,24 +967,21 @@ class OwncastAdminClient(OwncastClient):
|
||||
return await self._post("/api/admin/federation/send", {"value": message})
|
||||
|
||||
async def get_logs(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get server logs.
|
||||
"""Get server logs.
|
||||
|
||||
:return: Log entry list as returned by the Owncast API.
|
||||
"""
|
||||
return list(await self._get("/api/admin/logs"))
|
||||
|
||||
async def get_warnings(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get server warning and error logs.
|
||||
"""Get server warning and error logs.
|
||||
|
||||
:return: Log entry list as returned by the Owncast API.
|
||||
"""
|
||||
return list(await self._get("/api/admin/logs/warnings"))
|
||||
|
||||
async def get_playback_metrics(self) -> dict[str, Any]:
|
||||
"""
|
||||
Get playback quality metrics.
|
||||
"""Get playback quality metrics.
|
||||
|
||||
:return: Playback metrics as returned by the Owncast API.
|
||||
"""
|
||||
@@ -1066,8 +990,7 @@ class OwncastAdminClient(OwncastClient):
|
||||
async def _set_config_value(
|
||||
self, endpoint: str, value: Any, description: str
|
||||
) -> str:
|
||||
"""
|
||||
Set a configuration value via POST with a ``{"value": ...}`` body.
|
||||
"""Set a configuration value via POST with a ``{"value": ...}`` body.
|
||||
|
||||
:param endpoint: The config API endpoint path.
|
||||
:param value: The value to set.
|
||||
|
||||
@@ -52,8 +52,7 @@ class OwncastError(Exception):
|
||||
"""Raised when an Owncast API request fails."""
|
||||
|
||||
def __init__(self, status: int, message: str):
|
||||
"""
|
||||
Initialize the error.
|
||||
"""Initialize the error.
|
||||
|
||||
:param status: HTTP status code from the failed request, or 0 if the
|
||||
request failed due to a connection error before receiving a response.
|
||||
@@ -72,8 +71,7 @@ class OwncastClient:
|
||||
"""
|
||||
|
||||
def __init__(self, base_url: str, access_token: str, http_client: HttpClient):
|
||||
"""
|
||||
Initialize the Owncast client.
|
||||
"""Initialize the Owncast client.
|
||||
|
||||
:param base_url: The Owncast server URL (e.g., "https://stream.logal.dev").
|
||||
:param access_token: API access token from Owncast admin settings.
|
||||
@@ -94,8 +92,7 @@ class OwncastClient:
|
||||
return self._base_url
|
||||
|
||||
async def get_status(self) -> dict[str, Any]:
|
||||
"""
|
||||
Get the public server status.
|
||||
"""Get the public server status.
|
||||
|
||||
This is a public endpoint that does not require authentication.
|
||||
Returns server info including version, online status, and viewer count.
|
||||
@@ -106,8 +103,7 @@ class OwncastClient:
|
||||
return dict(await self._get("/api/status"))
|
||||
|
||||
async def send_message(self, body: str) -> str:
|
||||
"""
|
||||
Send a chat message visible to all viewers.
|
||||
"""Send a chat message visible to all viewers.
|
||||
|
||||
:param body: The message text (supports markdown).
|
||||
:return: Success message from the server.
|
||||
@@ -116,8 +112,7 @@ class OwncastClient:
|
||||
return await self._post("/api/integrations/chat/send", {"body": body})
|
||||
|
||||
async def send_system_message(self, body: str) -> str:
|
||||
"""
|
||||
Send a system message visible to all viewers.
|
||||
"""Send a system message visible to all viewers.
|
||||
|
||||
System messages are styled differently from regular chat (typically
|
||||
italicized or dimmed) and are used for announcements or notifications.
|
||||
@@ -129,8 +124,7 @@ class OwncastClient:
|
||||
return await self._post("/api/integrations/chat/system", {"body": body})
|
||||
|
||||
async def send_action(self, body: str) -> str:
|
||||
"""
|
||||
Send an action message (like IRC /me).
|
||||
"""Send an action message (like IRC /me).
|
||||
|
||||
Action messages display as "*BotName does something*" and are used
|
||||
for describing actions rather than speech.
|
||||
@@ -142,8 +136,7 @@ class OwncastClient:
|
||||
return await self._post("/api/integrations/chat/action", {"body": body})
|
||||
|
||||
async def send_system_message_to_client(self, client_id: int, body: str) -> str:
|
||||
"""
|
||||
Send a private system message to a specific viewer.
|
||||
"""Send a private system message to a specific viewer.
|
||||
|
||||
The message is only visible to the targeted client, useful for
|
||||
welcome messages or private notifications.
|
||||
@@ -160,8 +153,7 @@ class OwncastClient:
|
||||
async def set_message_visibility(
|
||||
self, message_ids: list[str], visible: bool
|
||||
) -> str:
|
||||
"""
|
||||
Hide or show chat messages (moderation).
|
||||
"""Hide or show chat messages (moderation).
|
||||
|
||||
Hidden messages are removed from the chat display for all viewers.
|
||||
This is typically used for moderation purposes.
|
||||
@@ -179,8 +171,7 @@ class OwncastClient:
|
||||
)
|
||||
|
||||
async def get_chat_history(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Fetch recent chat messages.
|
||||
"""Fetch recent chat messages.
|
||||
|
||||
:return: List of recent chat message objects with user info and content.
|
||||
"""
|
||||
@@ -188,8 +179,7 @@ class OwncastClient:
|
||||
return list(await self._get("/api/integrations/chat"))
|
||||
|
||||
async def get_connected_clients(self) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get list of currently connected viewers.
|
||||
"""Get list of currently connected viewers.
|
||||
|
||||
:return: List of connected client objects with user info and connection details.
|
||||
"""
|
||||
@@ -197,8 +187,7 @@ class OwncastClient:
|
||||
return list(await self._get("/api/integrations/clients"))
|
||||
|
||||
async def set_stream_title(self, title: str) -> str:
|
||||
"""
|
||||
Update the stream title.
|
||||
"""Update the stream title.
|
||||
|
||||
:param title: The new stream title.
|
||||
:return: Success message from the server.
|
||||
@@ -207,8 +196,7 @@ class OwncastClient:
|
||||
return await self._post("/api/integrations/streamtitle", {"value": title})
|
||||
|
||||
async def _post(self, endpoint: str, data: dict[str, Any] | None = None) -> str:
|
||||
"""
|
||||
Send a POST request to the Owncast API.
|
||||
"""Send a POST request to the Owncast API.
|
||||
|
||||
Owncast POST endpoints return ``{"success": true, "message": "..."}``.
|
||||
This method validates the response and returns just the message string.
|
||||
@@ -270,8 +258,7 @@ class OwncastClient:
|
||||
raise OwncastError(0, str(e)) from e
|
||||
|
||||
async def _get(self, endpoint: str, params: dict[str, Any] | None = None) -> Any:
|
||||
"""
|
||||
Send a GET request to the Owncast API.
|
||||
"""Send a GET request to the Owncast API.
|
||||
|
||||
:param endpoint: The API endpoint path.
|
||||
:param params: Optional query parameters.
|
||||
|
||||
@@ -62,8 +62,7 @@ def on_route(
|
||||
*,
|
||||
methods: list[str] | None = None,
|
||||
) -> Callable[[RouteHandler], RouteHandler]:
|
||||
"""
|
||||
Decorator to register an HTTP route handler.
|
||||
"""Register an HTTP route handler.
|
||||
|
||||
Routes are namespaced under /owlbot/<module_name>/<path>.
|
||||
|
||||
|
||||
+13
-24
@@ -34,8 +34,7 @@ class StorageError(Exception):
|
||||
|
||||
|
||||
class ModuleStorage:
|
||||
"""
|
||||
Module-scoped async SQLite storage backed by a lazy connection pool.
|
||||
"""Module-scoped async SQLite storage backed by a lazy connection pool.
|
||||
|
||||
Each module gets its own isolated database file. Connections are created
|
||||
lazily and pooled up to ``pool_size``. WAL mode is enabled so that
|
||||
@@ -54,8 +53,7 @@ class ModuleStorage:
|
||||
"""
|
||||
|
||||
def __init__(self, storage_dir: Path, module_name: str, pool_size: int = 4):
|
||||
"""
|
||||
Initialize the storage API.
|
||||
"""Initialize the storage API.
|
||||
|
||||
:param storage_dir: Directory where module databases are stored.
|
||||
:param module_name: Name of the module this storage belongs to.
|
||||
@@ -92,8 +90,7 @@ class ModuleStorage:
|
||||
|
||||
@asynccontextmanager
|
||||
async def transaction(self) -> AsyncIterator[ModuleStorage]:
|
||||
"""
|
||||
Context manager for explicit transaction control within a handler.
|
||||
"""Context manager for explicit transaction control within a handler.
|
||||
|
||||
Use this when you need multiple operations to succeed or fail together
|
||||
within a single handler. Commits on success, rolls back on exception.
|
||||
@@ -105,6 +102,7 @@ class ModuleStorage:
|
||||
# Both committed together, or both rolled back on error
|
||||
|
||||
:return: This ModuleStorage instance.
|
||||
|
||||
"""
|
||||
self._logger.debug("Explicit transaction started.")
|
||||
try:
|
||||
@@ -119,8 +117,7 @@ class ModuleStorage:
|
||||
sql: str,
|
||||
parameters: tuple[Any, ...] | dict[str, Any] = (),
|
||||
) -> aiosqlite.Cursor:
|
||||
"""
|
||||
Execute a SQL statement.
|
||||
"""Execute a SQL statement.
|
||||
|
||||
:param sql: SQL statement (use ? or :name for parameters).
|
||||
:param parameters: Query parameters (tuple for ?, dict for :name).
|
||||
@@ -140,8 +137,7 @@ class ModuleStorage:
|
||||
sql: str,
|
||||
parameters: list[tuple[Any, ...]] | list[dict[str, Any]],
|
||||
) -> aiosqlite.Cursor:
|
||||
"""
|
||||
Execute a SQL statement with multiple parameter sets.
|
||||
"""Execute a SQL statement with multiple parameter sets.
|
||||
|
||||
Useful for batch inserts/updates.
|
||||
|
||||
@@ -166,8 +162,7 @@ class ModuleStorage:
|
||||
sql: str,
|
||||
parameters: tuple[Any, ...] | dict[str, Any] = (),
|
||||
) -> aiosqlite.Row | None:
|
||||
"""
|
||||
Execute a query and fetch one row.
|
||||
"""Execute a query and fetch one row.
|
||||
|
||||
:param sql: SELECT statement.
|
||||
:param parameters: Query parameters.
|
||||
@@ -189,8 +184,7 @@ class ModuleStorage:
|
||||
sql: str,
|
||||
parameters: tuple[Any, ...] | dict[str, Any] = (),
|
||||
) -> list[aiosqlite.Row]:
|
||||
"""
|
||||
Execute a query and fetch all rows.
|
||||
"""Execute a query and fetch all rows.
|
||||
|
||||
:param sql: SELECT statement.
|
||||
:param parameters: Query parameters.
|
||||
@@ -212,8 +206,7 @@ class ModuleStorage:
|
||||
sql: str,
|
||||
parameters: tuple[Any, ...] | dict[str, Any] = (),
|
||||
) -> Any | None:
|
||||
"""
|
||||
Execute a query and fetch a single value.
|
||||
"""Execute a query and fetch a single value.
|
||||
|
||||
:param sql: SELECT statement returning one column.
|
||||
:param parameters: Query parameters.
|
||||
@@ -260,8 +253,7 @@ class ModuleStorage:
|
||||
|
||||
@asynccontextmanager
|
||||
async def _connection(self) -> AsyncIterator[aiosqlite.Connection]:
|
||||
"""
|
||||
Async context manager that provides a connection.
|
||||
"""Async context manager that provides a connection.
|
||||
|
||||
If already inside a ``_checkout``, yields the checked-out connection
|
||||
without releasing it. Otherwise acquires a standalone connection from
|
||||
@@ -285,8 +277,7 @@ class ModuleStorage:
|
||||
|
||||
@asynccontextmanager
|
||||
async def _checkout(self) -> AsyncIterator[None]:
|
||||
"""
|
||||
Check out a connection from the pool for the duration of a handler.
|
||||
"""Check out a connection from the pool for the duration of a handler.
|
||||
|
||||
Sets a ContextVar so that all storage operations within the handler
|
||||
reuse the same connection.
|
||||
@@ -300,8 +291,7 @@ class ModuleStorage:
|
||||
self._release(conn)
|
||||
|
||||
async def _commit(self) -> None:
|
||||
"""
|
||||
Commit the current transaction (internal use by bot).
|
||||
"""Commit the current transaction (internal use by bot).
|
||||
|
||||
Called automatically after each handler completes successfully.
|
||||
"""
|
||||
@@ -311,8 +301,7 @@ class ModuleStorage:
|
||||
self._logger.debug("Transaction committed.")
|
||||
|
||||
async def _rollback(self) -> None:
|
||||
"""
|
||||
Rollback the current transaction (internal use by bot).
|
||||
"""Rollback the current transaction (internal use by bot).
|
||||
|
||||
Called automatically if a handler throws an exception.
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user