CI / Formatting (push) Successful in 6s
CI / Linting (push) Successful in 5s
CI / Tests (Python 3.12) (push) Successful in 2m55s
CI / Tests (Python 3.13) (push) Successful in 2m46s
CI / Tests (Python 3.14) (push) Successful in 2m42s
CI / Type Checking (push) Successful in 10s
CI / Spelling (push) Successful in 9s
689 lines
26 KiB
Python
689 lines
26 KiB
Python
# Copyright 2026 Logan Fick
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Recording stubs for the Owncast HTTP clients."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING, Any, NamedTuple
|
|
|
|
if TYPE_CHECKING:
|
|
from owlbot.api.owncast_admin_client import (
|
|
ExternalAction,
|
|
OwncastAdminClient,
|
|
SocialHandle,
|
|
StreamKey,
|
|
VideoVariant,
|
|
)
|
|
from owlbot.api.owncast_client import OwncastClient
|
|
|
|
# Stubs inherit from the real classes at type-check time so that code
|
|
# passing a stub where the real instance is expected (notably plugin.py)
|
|
# type-checks. At runtime the base is ``object`` to avoid the real
|
|
# ``__init__`` requiring live HTTP/config dependencies.
|
|
_ClientBase = OwncastClient
|
|
_AdminBase = OwncastAdminClient
|
|
else:
|
|
_ClientBase = object
|
|
_AdminBase = object
|
|
|
|
|
|
class RecordedCall(NamedTuple):
|
|
"""A single recorded method invocation on a recording stub.
|
|
|
|
Compares equal to a plain ``(name, kwargs)`` tuple, so assertions written
|
|
against tuple literals continue to work.
|
|
"""
|
|
|
|
name: str
|
|
kwargs: dict[str, Any]
|
|
|
|
|
|
class RecordingOwncastClient(_ClientBase):
|
|
"""Stub mirroring :class:`~owlbot.api.owncast_client.OwncastClient`.
|
|
|
|
Each public async method records ``(method_name, kwargs_dict)`` to
|
|
:attr:`calls` and returns an empty default of the declared return type.
|
|
"""
|
|
|
|
def __init__(self, base_url: str = "http://localhost:8080") -> None:
|
|
"""Initialize with an empty call log and the given base URL."""
|
|
self.calls: list[RecordedCall] = []
|
|
self._base_url = base_url.rstrip("/")
|
|
|
|
@property
|
|
def base_url(self) -> str:
|
|
"""The Owncast server base URL passed at construction."""
|
|
return self._base_url
|
|
|
|
async def send_system_message(self, body: str, *, unsanitized: bool = False) -> str:
|
|
"""Send a system message visible to all viewers."""
|
|
self.calls.append(
|
|
RecordedCall(
|
|
"send_system_message", {"body": body, "unsanitized": unsanitized}
|
|
)
|
|
)
|
|
return ""
|
|
|
|
async def send_system_message_to_client(
|
|
self, client_id: int, body: str, *, unsanitized: bool = False
|
|
) -> str:
|
|
"""Send a private system message to a specific viewer."""
|
|
self.calls.append(
|
|
RecordedCall(
|
|
"send_system_message_to_client",
|
|
{"client_id": client_id, "body": body, "unsanitized": unsanitized},
|
|
)
|
|
)
|
|
return ""
|
|
|
|
async def send_user_message(self) -> str:
|
|
"""Send a chat message on behalf of a user (deprecated by Owncast)."""
|
|
self.calls.append(RecordedCall("send_user_message", {}))
|
|
return ""
|
|
|
|
async def send_message(self, body: str, *, unsanitized: bool = False) -> str:
|
|
"""Send a chat message visible to all viewers."""
|
|
self.calls.append(
|
|
RecordedCall("send_message", {"body": body, "unsanitized": unsanitized})
|
|
)
|
|
return ""
|
|
|
|
async def send_action(self, body: str, *, unsanitized: bool = False) -> str:
|
|
"""Send an action message (like IRC /me)."""
|
|
self.calls.append(
|
|
RecordedCall("send_action", {"body": body, "unsanitized": unsanitized})
|
|
)
|
|
return ""
|
|
|
|
async def set_message_visibility(
|
|
self, message_ids: list[str], *, visible: bool
|
|
) -> str:
|
|
"""Hide or show chat messages (moderation)."""
|
|
self.calls.append(
|
|
RecordedCall(
|
|
"set_message_visibility",
|
|
{"message_ids": message_ids, "visible": visible},
|
|
)
|
|
)
|
|
return ""
|
|
|
|
async def get_status(self) -> dict[str, Any]:
|
|
"""Get the public server status."""
|
|
self.calls.append(RecordedCall("get_status", {}))
|
|
return {}
|
|
|
|
async def set_stream_title(self, title: str) -> str:
|
|
"""Update the stream title."""
|
|
self.calls.append(RecordedCall("set_stream_title", {"title": title}))
|
|
return ""
|
|
|
|
async def get_chat_history(self) -> list[dict[str, Any]]:
|
|
"""Fetch recent chat messages."""
|
|
self.calls.append(RecordedCall("get_chat_history", {}))
|
|
return []
|
|
|
|
async def get_connected_clients(self) -> list[dict[str, Any]]:
|
|
"""Get list of currently connected viewers."""
|
|
self.calls.append(RecordedCall("get_connected_clients", {}))
|
|
return []
|
|
|
|
async def get_user_details(self, user_id: str) -> dict[str, Any]:
|
|
"""Get moderation details for a chat user."""
|
|
self.calls.append(RecordedCall("get_user_details", {"user_id": user_id}))
|
|
return {}
|
|
|
|
|
|
class RecordingOwncastAdminClient(_AdminBase):
|
|
"""Stub mirroring :class:`~owlbot.api.owncast_admin_client.OwncastAdminClient`.
|
|
|
|
Each public async method records ``(method_name, kwargs_dict)`` to
|
|
:attr:`calls` and returns an empty default of the declared return type.
|
|
"""
|
|
|
|
def __init__(self, base_url: str = "http://localhost:8080") -> None:
|
|
"""Initialize with an empty call log and the given base URL."""
|
|
self.calls: list[RecordedCall] = []
|
|
self._base_url = base_url.rstrip("/")
|
|
|
|
@property
|
|
def base_url(self) -> str:
|
|
"""The Owncast server base URL passed at construction."""
|
|
return self._base_url
|
|
|
|
async def get_status(self) -> dict[str, Any]:
|
|
"""Get the current server status including stream info and viewer count."""
|
|
self.calls.append(RecordedCall("get_status", {}))
|
|
return {}
|
|
|
|
async def disconnect_stream(self) -> dict[str, Any]:
|
|
"""Disconnect the current inbound stream."""
|
|
self.calls.append(RecordedCall("disconnect_stream", {}))
|
|
return {}
|
|
|
|
async def get_server_config(self) -> dict[str, Any]:
|
|
"""Get the full server configuration."""
|
|
self.calls.append(RecordedCall("get_server_config", {}))
|
|
return {}
|
|
|
|
async def get_viewers_over_time(self, window_start: int) -> list[dict[str, Any]]:
|
|
"""Get viewer count data over time for charting."""
|
|
self.calls.append(
|
|
RecordedCall("get_viewers_over_time", {"window_start": window_start})
|
|
)
|
|
return []
|
|
|
|
async def get_active_viewers(self) -> list[dict[str, Any]]:
|
|
"""Get a list of currently active viewers."""
|
|
self.calls.append(RecordedCall("get_active_viewers", {}))
|
|
return []
|
|
|
|
async def get_hardware_stats(self) -> dict[str, Any]:
|
|
"""Get server hardware statistics (CPU, memory, disk)."""
|
|
self.calls.append(RecordedCall("get_hardware_stats", {}))
|
|
return {}
|
|
|
|
async def get_connected_chat_clients(self) -> list[dict[str, Any]]:
|
|
"""Get currently connected chat clients."""
|
|
self.calls.append(RecordedCall("get_connected_chat_clients", {}))
|
|
return []
|
|
|
|
async def get_chat_messages(self) -> list[dict[str, Any]]:
|
|
"""Get chat messages from the admin perspective."""
|
|
self.calls.append(RecordedCall("get_chat_messages", {}))
|
|
return []
|
|
|
|
async def set_message_visibility(
|
|
self, message_ids: list[str], *, visible: bool
|
|
) -> str:
|
|
"""Hide or show chat messages."""
|
|
self.calls.append(
|
|
RecordedCall(
|
|
"set_message_visibility",
|
|
{"message_ids": message_ids, "visible": visible},
|
|
)
|
|
)
|
|
return ""
|
|
|
|
async def set_user_enabled(self, user_id: str, *, enabled: bool) -> str:
|
|
"""Enable or disable a chat user."""
|
|
self.calls.append(
|
|
RecordedCall("set_user_enabled", {"user_id": user_id, "enabled": enabled})
|
|
)
|
|
return ""
|
|
|
|
async def get_disabled_users(self) -> list[dict[str, Any]]:
|
|
"""Get a list of disabled chat users."""
|
|
self.calls.append(RecordedCall("get_disabled_users", {}))
|
|
return []
|
|
|
|
async def ban_ip_address(self, ip: str) -> str:
|
|
"""Ban an IP address from chat."""
|
|
self.calls.append(RecordedCall("ban_ip_address", {"ip": ip}))
|
|
return ""
|
|
|
|
async def unban_ip_address(self, ip: str) -> str:
|
|
"""Remove an IP address ban."""
|
|
self.calls.append(RecordedCall("unban_ip_address", {"ip": ip}))
|
|
return ""
|
|
|
|
async def get_ip_address_bans(self) -> list[dict[str, Any]]:
|
|
"""Get a list of banned IP addresses."""
|
|
self.calls.append(RecordedCall("get_ip_address_bans", {}))
|
|
return []
|
|
|
|
async def set_user_moderator(self, user_id: str, *, is_mod: bool) -> str:
|
|
"""Grant or revoke moderator status for a user."""
|
|
self.calls.append(
|
|
RecordedCall("set_user_moderator", {"user_id": user_id, "is_mod": is_mod})
|
|
)
|
|
return ""
|
|
|
|
async def get_moderators(self) -> list[dict[str, Any]]:
|
|
"""Get a list of moderator users."""
|
|
self.calls.append(RecordedCall("get_moderators", {}))
|
|
return []
|
|
|
|
async def get_logs(self) -> list[dict[str, Any]]:
|
|
"""Get server logs."""
|
|
self.calls.append(RecordedCall("get_logs", {}))
|
|
return []
|
|
|
|
async def get_warnings(self) -> list[dict[str, Any]]:
|
|
"""Get server warning and error logs."""
|
|
self.calls.append(RecordedCall("get_warnings", {}))
|
|
return []
|
|
|
|
async def get_followers(self, offset: int = 0, limit: int = 25) -> dict[str, Any]:
|
|
"""Get a paginated list of followers."""
|
|
self.calls.append(
|
|
RecordedCall("get_followers", {"offset": offset, "limit": limit})
|
|
)
|
|
return {}
|
|
|
|
async def get_pending_follow_requests(self) -> list[dict[str, Any]]:
|
|
"""Get pending follow requests."""
|
|
self.calls.append(RecordedCall("get_pending_follow_requests", {}))
|
|
return []
|
|
|
|
async def get_blocked_followers(self) -> list[dict[str, Any]]:
|
|
"""Get blocked and rejected followers."""
|
|
self.calls.append(RecordedCall("get_blocked_followers", {}))
|
|
return []
|
|
|
|
async def approve_follower(self, actor_iri: str, *, approved: bool) -> str:
|
|
"""Approve or reject a follow request."""
|
|
self.calls.append(
|
|
RecordedCall(
|
|
"approve_follower", {"actor_iri": actor_iri, "approved": approved}
|
|
)
|
|
)
|
|
return ""
|
|
|
|
async def upload_emoji(self, name: str, base64_data_url: str) -> str:
|
|
"""Upload a custom emoji."""
|
|
self.calls.append(
|
|
RecordedCall(
|
|
"upload_emoji",
|
|
{"name": name, "base64_data_url": base64_data_url},
|
|
)
|
|
)
|
|
return ""
|
|
|
|
async def delete_emoji(self, name: str) -> str:
|
|
"""Delete a custom emoji."""
|
|
self.calls.append(RecordedCall("delete_emoji", {"name": name}))
|
|
return ""
|
|
|
|
async def set_admin_password(self, password: str) -> str:
|
|
"""Change the admin password."""
|
|
self.calls.append(RecordedCall("set_admin_password", {"password": password}))
|
|
return ""
|
|
|
|
async def set_stream_keys(self, keys: list[StreamKey]) -> str:
|
|
"""Set the stream keys."""
|
|
self.calls.append(RecordedCall("set_stream_keys", {"keys": keys}))
|
|
return ""
|
|
|
|
async def set_page_content(self, content: str) -> str:
|
|
"""Set the custom page content (HTML/markdown below the player)."""
|
|
self.calls.append(RecordedCall("set_page_content", {"content": content}))
|
|
return ""
|
|
|
|
async def set_stream_title(self, title: str) -> str:
|
|
"""Set the stream title."""
|
|
self.calls.append(RecordedCall("set_stream_title", {"title": title}))
|
|
return ""
|
|
|
|
async def set_server_name(self, name: str) -> str:
|
|
"""Set the server name."""
|
|
self.calls.append(RecordedCall("set_server_name", {"name": name}))
|
|
return ""
|
|
|
|
async def set_server_summary(self, summary: str) -> str:
|
|
"""Set the server summary."""
|
|
self.calls.append(RecordedCall("set_server_summary", {"summary": summary}))
|
|
return ""
|
|
|
|
async def set_offline_message(self, message: str) -> str:
|
|
"""Set the message shown when the stream is offline."""
|
|
self.calls.append(RecordedCall("set_offline_message", {"message": message}))
|
|
return ""
|
|
|
|
async def set_welcome_message(self, message: str) -> str:
|
|
"""Set the welcome message shown to new viewers."""
|
|
self.calls.append(RecordedCall("set_welcome_message", {"message": message}))
|
|
return ""
|
|
|
|
async def set_chat_disabled(self, *, disabled: bool) -> str:
|
|
"""Enable or disable the chat."""
|
|
self.calls.append(RecordedCall("set_chat_disabled", {"disabled": disabled}))
|
|
return ""
|
|
|
|
async def set_chat_join_messages_enabled(self, *, enabled: bool) -> str:
|
|
"""Enable or disable chat join messages."""
|
|
self.calls.append(
|
|
RecordedCall("set_chat_join_messages_enabled", {"enabled": enabled})
|
|
)
|
|
return ""
|
|
|
|
async def set_chat_established_mode(self, *, enabled: bool) -> str:
|
|
"""Enable or disable established user mode for chat."""
|
|
self.calls.append(
|
|
RecordedCall("set_chat_established_mode", {"enabled": enabled})
|
|
)
|
|
return ""
|
|
|
|
async def set_forbidden_usernames(self, names: list[str]) -> str:
|
|
"""Set the list of forbidden usernames."""
|
|
self.calls.append(RecordedCall("set_forbidden_usernames", {"names": names}))
|
|
return ""
|
|
|
|
async def set_suggested_usernames(self, names: list[str]) -> str:
|
|
"""Set the list of suggested usernames for new viewers."""
|
|
self.calls.append(RecordedCall("set_suggested_usernames", {"names": names}))
|
|
return ""
|
|
|
|
async def set_chat_spam_protection(self, *, enabled: bool) -> str:
|
|
"""Enable or disable chat spam protection."""
|
|
self.calls.append(
|
|
RecordedCall("set_chat_spam_protection", {"enabled": enabled})
|
|
)
|
|
return ""
|
|
|
|
async def set_chat_slur_filter(self, *, enabled: bool) -> str:
|
|
"""Enable or disable the chat slur filter."""
|
|
self.calls.append(RecordedCall("set_chat_slur_filter", {"enabled": enabled}))
|
|
return ""
|
|
|
|
async def set_chat_require_authentication(self, *, required: bool) -> str:
|
|
"""Require chat users to authenticate before sending messages."""
|
|
self.calls.append(
|
|
RecordedCall("set_chat_require_authentication", {"required": required})
|
|
)
|
|
return ""
|
|
|
|
async def set_video_codec(self, codec: str) -> str:
|
|
"""Set the video codec."""
|
|
self.calls.append(RecordedCall("set_video_codec", {"codec": codec}))
|
|
return ""
|
|
|
|
async def set_stream_latency(self, level: int) -> str:
|
|
"""Set the stream latency level."""
|
|
self.calls.append(RecordedCall("set_stream_latency", {"level": level}))
|
|
return ""
|
|
|
|
async def set_video_variants(self, variants: list[VideoVariant]) -> str:
|
|
"""Set the video output variants (quality levels)."""
|
|
self.calls.append(RecordedCall("set_video_variants", {"variants": variants}))
|
|
return ""
|
|
|
|
async def set_color_variables(self, variables: dict[str, Any]) -> str:
|
|
"""Set the custom color variables for the web interface."""
|
|
self.calls.append(RecordedCall("set_color_variables", {"variables": variables}))
|
|
return ""
|
|
|
|
async def set_logo(self, base64_data_url: str) -> str:
|
|
"""Set the server logo from a base64 data URL."""
|
|
self.calls.append(
|
|
RecordedCall("set_logo", {"base64_data_url": base64_data_url})
|
|
)
|
|
return ""
|
|
|
|
async def set_favicon(self, base64_data_url: str) -> str:
|
|
"""Set a custom favicon from a base64 data URL."""
|
|
self.calls.append(
|
|
RecordedCall("set_favicon", {"base64_data_url": base64_data_url})
|
|
)
|
|
return ""
|
|
|
|
async def reset_favicon(self) -> str:
|
|
"""Reset the favicon to the Owncast default."""
|
|
self.calls.append(RecordedCall("reset_favicon", {}))
|
|
return ""
|
|
|
|
async def set_tags(self, tags: list[str]) -> str:
|
|
"""Set the server tags."""
|
|
self.calls.append(RecordedCall("set_tags", {"tags": tags}))
|
|
return ""
|
|
|
|
async def set_ffmpeg_path(self, path: str) -> str:
|
|
"""Set the path to the ffmpeg binary."""
|
|
self.calls.append(RecordedCall("set_ffmpeg_path", {"path": path}))
|
|
return ""
|
|
|
|
async def set_web_server_port(self, port: int) -> str:
|
|
"""Set the web server port."""
|
|
self.calls.append(RecordedCall("set_web_server_port", {"port": port}))
|
|
return ""
|
|
|
|
async def set_web_server_ip(self, ip: str) -> str:
|
|
"""Set the web server bind IP address."""
|
|
self.calls.append(RecordedCall("set_web_server_ip", {"ip": ip}))
|
|
return ""
|
|
|
|
async def set_rtmp_port(self, port: int) -> str:
|
|
"""Set the RTMP server port."""
|
|
self.calls.append(RecordedCall("set_rtmp_port", {"port": port}))
|
|
return ""
|
|
|
|
async def set_socket_host_override(self, host: str) -> str:
|
|
"""Set the WebSocket host override."""
|
|
self.calls.append(RecordedCall("set_socket_host_override", {"host": host}))
|
|
return ""
|
|
|
|
async def set_video_serving_endpoint(self, endpoint: str) -> str:
|
|
"""Set the video serving endpoint (e.g., for CDN)."""
|
|
self.calls.append(
|
|
RecordedCall("set_video_serving_endpoint", {"endpoint": endpoint})
|
|
)
|
|
return ""
|
|
|
|
async def set_nsfw(self, *, nsfw: bool) -> str:
|
|
"""Set the NSFW flag for the server."""
|
|
self.calls.append(RecordedCall("set_nsfw", {"nsfw": nsfw}))
|
|
return ""
|
|
|
|
async def set_directory_enabled(self, *, enabled: bool) -> str:
|
|
"""Enable or disable listing in the Owncast directory."""
|
|
self.calls.append(RecordedCall("set_directory_enabled", {"enabled": enabled}))
|
|
return ""
|
|
|
|
async def set_social_handles(self, handles: list[SocialHandle]) -> str:
|
|
"""Set the social media handles displayed on the page."""
|
|
self.calls.append(RecordedCall("set_social_handles", {"handles": handles}))
|
|
return ""
|
|
|
|
async def set_s3_config(
|
|
self,
|
|
*,
|
|
enabled: bool,
|
|
endpoint: str,
|
|
access_key: str,
|
|
secret: str,
|
|
bucket: str,
|
|
region: str,
|
|
) -> str:
|
|
"""Set the S3 storage configuration."""
|
|
self.calls.append(
|
|
RecordedCall(
|
|
"set_s3_config",
|
|
{
|
|
"enabled": enabled,
|
|
"endpoint": endpoint,
|
|
"access_key": access_key,
|
|
"secret": secret,
|
|
"bucket": bucket,
|
|
"region": region,
|
|
},
|
|
)
|
|
)
|
|
return ""
|
|
|
|
async def set_server_url(self, url: str) -> str:
|
|
"""Set the public server URL."""
|
|
self.calls.append(RecordedCall("set_server_url", {"url": url}))
|
|
return ""
|
|
|
|
async def set_external_actions(self, actions: list[ExternalAction]) -> str:
|
|
"""Set the external actions (buttons/links in the player)."""
|
|
self.calls.append(RecordedCall("set_external_actions", {"actions": actions}))
|
|
return ""
|
|
|
|
async def set_custom_styles(self, css: str) -> str:
|
|
"""Set custom CSS styles for the web interface."""
|
|
self.calls.append(RecordedCall("set_custom_styles", {"css": css}))
|
|
return ""
|
|
|
|
async def set_custom_javascript(self, js: str) -> str:
|
|
"""Set custom JavaScript for the web interface."""
|
|
self.calls.append(RecordedCall("set_custom_javascript", {"js": js}))
|
|
return ""
|
|
|
|
async def set_hide_viewer_count(self, *, hide: bool) -> str:
|
|
"""Show or hide the viewer count."""
|
|
self.calls.append(RecordedCall("set_hide_viewer_count", {"hide": hide}))
|
|
return ""
|
|
|
|
async def set_disable_search_indexing(self, *, disabled: bool) -> str:
|
|
"""Enable or disable search engine indexing."""
|
|
self.calls.append(
|
|
RecordedCall("set_disable_search_indexing", {"disabled": disabled})
|
|
)
|
|
return ""
|
|
|
|
async def set_federation_enabled(self, *, enabled: bool) -> str:
|
|
"""Enable or disable federation (ActivityPub)."""
|
|
self.calls.append(RecordedCall("set_federation_enabled", {"enabled": enabled}))
|
|
return ""
|
|
|
|
async def set_federation_activity_private(self, *, private: bool) -> str:
|
|
"""Mark federated activity as private (followers-only)."""
|
|
self.calls.append(
|
|
RecordedCall("set_federation_activity_private", {"private": private})
|
|
)
|
|
return ""
|
|
|
|
async def set_federation_show_engagement(self, *, enabled: bool) -> str:
|
|
"""Show or hide federated engagement (likes, boosts) in chat."""
|
|
self.calls.append(
|
|
RecordedCall("set_federation_show_engagement", {"enabled": enabled})
|
|
)
|
|
return ""
|
|
|
|
async def set_federation_username(self, name: str) -> str:
|
|
"""Set the federation (ActivityPub) username."""
|
|
self.calls.append(RecordedCall("set_federation_username", {"name": name}))
|
|
return ""
|
|
|
|
async def set_federation_go_live_message(self, message: str) -> str:
|
|
"""Set the message sent to followers when going live."""
|
|
self.calls.append(
|
|
RecordedCall("set_federation_go_live_message", {"message": message})
|
|
)
|
|
return ""
|
|
|
|
async def set_federation_blocked_domains(self, domains: list[str]) -> str:
|
|
"""Set the list of blocked federation domains."""
|
|
self.calls.append(
|
|
RecordedCall("set_federation_blocked_domains", {"domains": domains})
|
|
)
|
|
return ""
|
|
|
|
async def set_discord_notifications(
|
|
self, *, enabled: bool, webhook: str, go_live_message: str
|
|
) -> str:
|
|
"""Set the Discord notification configuration."""
|
|
self.calls.append(
|
|
RecordedCall(
|
|
"set_discord_notifications",
|
|
{
|
|
"enabled": enabled,
|
|
"webhook": webhook,
|
|
"go_live_message": go_live_message,
|
|
},
|
|
)
|
|
)
|
|
return ""
|
|
|
|
async def set_browser_notifications(
|
|
self, *, enabled: bool, go_live_message: str
|
|
) -> str:
|
|
"""Set the browser notification configuration."""
|
|
self.calls.append(
|
|
RecordedCall(
|
|
"set_browser_notifications",
|
|
{"enabled": enabled, "go_live_message": go_live_message},
|
|
)
|
|
)
|
|
return ""
|
|
|
|
async def get_webhooks(self) -> list[dict[str, Any]]:
|
|
"""Get all registered webhooks."""
|
|
self.calls.append(RecordedCall("get_webhooks", {}))
|
|
return []
|
|
|
|
async def delete_webhook(self, webhook_id: int) -> str:
|
|
"""Delete a webhook."""
|
|
self.calls.append(RecordedCall("delete_webhook", {"webhook_id": webhook_id}))
|
|
return ""
|
|
|
|
async def create_webhook(self, url: str, events: list[str]) -> str:
|
|
"""Create a new webhook."""
|
|
self.calls.append(
|
|
RecordedCall("create_webhook", {"url": url, "events": events})
|
|
)
|
|
return ""
|
|
|
|
async def get_access_tokens(self) -> list[dict[str, Any]]:
|
|
"""Get all access tokens."""
|
|
self.calls.append(RecordedCall("get_access_tokens", {}))
|
|
return []
|
|
|
|
async def delete_access_token(self, token: str) -> str:
|
|
"""Delete an access token."""
|
|
self.calls.append(RecordedCall("delete_access_token", {"token": token}))
|
|
return ""
|
|
|
|
async def create_access_token(self, name: str, scopes: list[str]) -> str:
|
|
"""Create a new access token."""
|
|
self.calls.append(
|
|
RecordedCall("create_access_token", {"name": name, "scopes": scopes})
|
|
)
|
|
return ""
|
|
|
|
async def reset_yp_registration(self) -> dict[str, Any]:
|
|
"""Clear the YP (Owncast directory) registration key."""
|
|
self.calls.append(RecordedCall("reset_yp_registration", {}))
|
|
return {}
|
|
|
|
async def get_playback_metrics(self) -> dict[str, Any]:
|
|
"""Get playback quality metrics."""
|
|
self.calls.append(RecordedCall("get_playback_metrics", {}))
|
|
return {}
|
|
|
|
async def get_prometheus_metrics(self) -> str:
|
|
"""Fetch metrics from the Owncast-proxied Prometheus endpoint."""
|
|
self.calls.append(RecordedCall("get_prometheus_metrics", {}))
|
|
return ""
|
|
|
|
async def post_prometheus(self, data: dict[str, Any] | None = None) -> str:
|
|
"""POST to the Owncast-proxied Prometheus endpoint."""
|
|
self.calls.append(RecordedCall("post_prometheus", {"data": data}))
|
|
return ""
|
|
|
|
async def put_prometheus(self, data: dict[str, Any] | None = None) -> str:
|
|
"""PUT to the Owncast-proxied Prometheus endpoint."""
|
|
self.calls.append(RecordedCall("put_prometheus", {"data": data}))
|
|
return ""
|
|
|
|
async def delete_prometheus(self) -> str:
|
|
"""DELETE on the Owncast-proxied Prometheus endpoint."""
|
|
self.calls.append(RecordedCall("delete_prometheus", {}))
|
|
return ""
|
|
|
|
async def send_federated_message(self, message: str) -> str:
|
|
"""Send a message to all followers via federation."""
|
|
self.calls.append(RecordedCall("send_federated_message", {"message": message}))
|
|
return ""
|
|
|
|
async def get_federated_actions(
|
|
self, offset: int = 0, limit: int = 25
|
|
) -> dict[str, Any]:
|
|
"""Get a paginated list of accepted inbound federated activities."""
|
|
self.calls.append(
|
|
RecordedCall("get_federated_actions", {"offset": offset, "limit": limit})
|
|
)
|
|
return {}
|