Added polls module.
CI / Formatting (push) Successful in 6s
CI / Linting (push) Successful in 6s
CI / Tests (Python 3.12) (push) Successful in 12s
CI / Tests (Python 3.13) (push) Successful in 12s
CI / Tests (Python 3.14) (push) Successful in 10s
CI / Type Checking (push) Successful in 9s
CI / Spelling (push) Successful in 5s
CI / Formatting (push) Successful in 6s
CI / Linting (push) Successful in 6s
CI / Tests (Python 3.12) (push) Successful in 12s
CI / Tests (Python 3.13) (push) Successful in 12s
CI / Tests (Python 3.14) (push) Successful in 10s
CI / Type Checking (push) Successful in 9s
CI / Spelling (push) Successful in 5s
This commit is contained in:
@@ -0,0 +1,161 @@
|
||||
# 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.
|
||||
|
||||
"""Polls module for Owlbot.
|
||||
|
||||
Allows moderators to create interactive polls in chat with live results
|
||||
via Server-Sent Events. Supports single and multi-select polls, hidden
|
||||
results, and authentication requirements.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from markupsafe import escape
|
||||
|
||||
from owlbot.api import (
|
||||
EventContext,
|
||||
EventType,
|
||||
ModuleContext,
|
||||
StreamStartedEvent,
|
||||
StreamStoppedEvent,
|
||||
UserJoinedEvent,
|
||||
on_event,
|
||||
on_setup,
|
||||
on_teardown,
|
||||
)
|
||||
|
||||
from .commands import (
|
||||
cancel_poll_command,
|
||||
end_poll_command,
|
||||
handle_bare_vote,
|
||||
poll_command,
|
||||
vote_command,
|
||||
)
|
||||
from .manager import PollManager, get_manager
|
||||
from .routes import (
|
||||
create_page,
|
||||
create_submit,
|
||||
events_stream,
|
||||
mod_cancel,
|
||||
mod_end,
|
||||
results_page,
|
||||
static_create_js,
|
||||
static_polls_css,
|
||||
static_polls_js,
|
||||
vote_page,
|
||||
vote_submit,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"cancel_poll_command",
|
||||
"create_page",
|
||||
"create_submit",
|
||||
"end_poll_command",
|
||||
"events_stream",
|
||||
"handle_bare_vote",
|
||||
"mod_cancel",
|
||||
"mod_end",
|
||||
"on_stream_started",
|
||||
"on_stream_stopped",
|
||||
"on_user_joined",
|
||||
"poll_command",
|
||||
"results_page",
|
||||
"setup",
|
||||
"static_create_js",
|
||||
"static_polls_css",
|
||||
"static_polls_js",
|
||||
"teardown",
|
||||
"vote_command",
|
||||
"vote_page",
|
||||
"vote_submit",
|
||||
]
|
||||
|
||||
|
||||
@on_setup
|
||||
async def setup(ctx: ModuleContext) -> None:
|
||||
"""Initialize the polls module.
|
||||
|
||||
:param ctx: Module context with config, storage, and other services.
|
||||
"""
|
||||
ctx.state["manager"] = PollManager(ctx)
|
||||
|
||||
|
||||
@on_event(EventType.STREAM_STOPPED)
|
||||
async def on_stream_stopped(ctx: EventContext[StreamStoppedEvent]) -> None:
|
||||
"""Begin a grace period when the stream goes offline.
|
||||
|
||||
:param ctx: The event context.
|
||||
"""
|
||||
get_manager(ctx.module).begin_stream_grace()
|
||||
|
||||
|
||||
@on_event(EventType.STREAM_STARTED)
|
||||
async def on_stream_started(ctx: EventContext[StreamStartedEvent]) -> None:
|
||||
"""Cancel the stream grace period if the stream restarts.
|
||||
|
||||
:param ctx: The event context.
|
||||
"""
|
||||
get_manager(ctx.module).cancel_stream_grace()
|
||||
|
||||
|
||||
@on_event(EventType.USER_JOINED)
|
||||
async def on_user_joined(ctx: EventContext[UserJoinedEvent]) -> None:
|
||||
"""Notify a user joining chat that a poll is active.
|
||||
|
||||
Sends a private message with the poll question, options, and voting
|
||||
instructions. Also refreshes the user's token privileges in case their
|
||||
authentication or moderator status has changed.
|
||||
|
||||
:param ctx: The event context.
|
||||
"""
|
||||
manager = get_manager(ctx.module)
|
||||
poll = manager.active_poll
|
||||
if poll is None:
|
||||
return
|
||||
|
||||
event = ctx.event
|
||||
manager.refresh_user_status(
|
||||
event.user.id,
|
||||
is_moderator=event.user.is_moderator,
|
||||
is_authenticated=event.user.is_authenticated,
|
||||
)
|
||||
|
||||
escaped_question = escape(poll.question)
|
||||
option_lines = "".join(
|
||||
f"<strong>{i}.</strong> {escape(opt)}<br>"
|
||||
for i, opt in enumerate(poll.options, 1)
|
||||
)
|
||||
|
||||
if poll.allow_multiple:
|
||||
instructions = "Use <strong>!vote</strong> to get a voting link."
|
||||
else:
|
||||
instructions = "Type a number in chat or use <strong>!vote</strong> to vote."
|
||||
|
||||
await ctx.owncast_client.send_system_message_to_client(
|
||||
ctx.event.client_id,
|
||||
f"<h3>Poll in progress: {escaped_question}</h3>"
|
||||
f"{option_lines}"
|
||||
f"<br>{instructions}",
|
||||
unsanitized=True,
|
||||
)
|
||||
|
||||
|
||||
@on_teardown
|
||||
async def teardown(ctx: ModuleContext) -> None:
|
||||
"""Clean up the polls module.
|
||||
|
||||
:param ctx: Module context.
|
||||
"""
|
||||
manager = get_manager(ctx)
|
||||
await manager.teardown()
|
||||
Reference in New Issue
Block a user