mirror of
https://github.com/migatu/conjurer.git
synced 2026-07-14 21:38:38 +00:00
Apply conversation bot improvements (proto-improvements)
Diff vs working-copy baseline = exactly this session's bot edits: - constants.py: env-var config, safe JSON loading, optional-dependency guards, env->netrc token resolution, API_SHARED_KEY + service_headers() - communication_subroutine.py: queue get(timeout) + Empty handling, daemon threads, cooperative stop_event, inbound _authorize_request() - bot.py: single asyncio event loop with cooperative shutdown - music_functions / radio_commands / librarian_commands: send X-Conjurer-Api-Key on internal HTTP calls via service_headers() Musician runtime data overlay dropped; conjurer_musician/.gitignore now keeps generated playlists/mp3 out of the repo. The 1+2+3 musician code port already lives in the base conjurer_musician. Depends on #9: must merge after restructure/working-copy-root. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+61
-24
@@ -1,17 +1,20 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
from queue import Empty, Queue
|
||||
from typing import Optional
|
||||
from urllib import request as urequest
|
||||
|
||||
from flask import Flask, jsonify, request
|
||||
from flask import Flask, abort, jsonify, request
|
||||
from waitress import serve
|
||||
|
||||
HOST_ADDRESS = "192.168.1.31"
|
||||
PORT_ADDRESS = 5000
|
||||
ICECAST_ADDRESS = "http://192.168.1.15:8000"
|
||||
HOST_ADDRESS = os.getenv("CONJURER_DISCORD_HOST", "192.168.1.31")
|
||||
PORT_ADDRESS = int(os.getenv("CONJURER_DISCORD_PORT", "5000"))
|
||||
ICECAST_ADDRESS = os.getenv("CONJURER_ICECAST", "http://192.168.1.15:8000")
|
||||
API_KEY = os.getenv("CONJURER_API_KEY")
|
||||
OUT_COMM_Q = Queue()
|
||||
IN_COMM_Q = Queue()
|
||||
SRCHTITLE = re.compile(rb"StreamTitle=\\*(?P<title>[^;]*);").search
|
||||
@@ -19,6 +22,12 @@ SRCHTITLE = re.compile(rb"StreamTitle=\\*(?P<title>[^;]*);").search
|
||||
awaiting_q = []
|
||||
incoming_q = Queue()
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def _authorize_request() -> None:
|
||||
"""Reject inbound calls lacking the shared key (no-op if key is unset)."""
|
||||
if API_KEY and request.headers.get("X-Conjurer-Api-Key") != API_KEY:
|
||||
abort(401)
|
||||
PREPPED_TRACKS = {
|
||||
"requests": "",
|
||||
"hit": "",
|
||||
@@ -53,6 +62,7 @@ class QueryControl:
|
||||
|
||||
@app.route("/prepped_tracks", methods=["POST"])
|
||||
def log_radio_tracks():
|
||||
_authorize_request()
|
||||
app.logger = logging.getLogger("discord")
|
||||
|
||||
app.logger.info(request)
|
||||
@@ -85,6 +95,7 @@ def answer_external_command():
|
||||
:return: The function `answer_external_command()` is returning a JSON response with the message
|
||||
"SUCCESS".
|
||||
"""
|
||||
_authorize_request()
|
||||
logger = logging.getLogger("discord")
|
||||
logger.info(request)
|
||||
record = json.loads(request.data)
|
||||
@@ -129,34 +140,42 @@ def waitress_run():
|
||||
serve(app, host=HOST_ADDRESS, port=PORT_ADDRESS)
|
||||
|
||||
|
||||
def scan_queue():
|
||||
def scan_queue(stop_event: Optional[threading.Event] = None):
|
||||
"""
|
||||
The function `scan_queue` reads data from a queue, logs it, and appends it to another queue.
|
||||
|
||||
:param _logger: The `_logger` parameter is typically an instance of a logging object that is used to
|
||||
record and store log messages. It is commonly used to track the flow of the program, record errors,
|
||||
and provide information for debugging purposes. In this code snippet, the `_logger` object is used
|
||||
to log the
|
||||
A bounded ``get(timeout=1)`` is used instead of a blocking ``get()`` so the
|
||||
worker can observe ``stop_event`` and exit cleanly during shutdown.
|
||||
|
||||
:param stop_event: optional :class:`threading.Event`; when set the loop
|
||||
stops at the next iteration.
|
||||
"""
|
||||
logger = logging.getLogger("discord")
|
||||
while True:
|
||||
data = OUT_COMM_Q.get()
|
||||
if stop_event and stop_event.is_set():
|
||||
logger.info("scan_queue: stop requested")
|
||||
break
|
||||
try:
|
||||
data = OUT_COMM_Q.get(timeout=1)
|
||||
except Empty:
|
||||
continue
|
||||
logger.info(data)
|
||||
awaiting_q.append(data)
|
||||
|
||||
|
||||
def scan_incoming():
|
||||
def scan_incoming(stop_event: Optional[threading.Event] = None):
|
||||
"""
|
||||
The `scan_incoming` function continuously checks for incoming data, processes it, and logs when data
|
||||
is found.
|
||||
|
||||
:param _logger: The `_logger` parameter in the `scan_incoming` function is a logger object that is
|
||||
used to log messages or information during the execution of the function. It is typically used for
|
||||
debugging, monitoring, or tracking the flow of the program. In this case, the `_logger` is being
|
||||
used
|
||||
:param stop_event: optional :class:`threading.Event`; when set the loop
|
||||
stops at the next iteration.
|
||||
"""
|
||||
logger = logging.getLogger("discord")
|
||||
while True:
|
||||
if stop_event and stop_event.is_set():
|
||||
logger.info("scan_incoming: stop requested")
|
||||
break
|
||||
try:
|
||||
answer = incoming_q.get(block=False)
|
||||
logger.info("DATA FOUND")
|
||||
@@ -204,27 +223,45 @@ def id3(url: str) -> dict:
|
||||
return tagdata
|
||||
|
||||
|
||||
def comm_subroutine():
|
||||
def comm_subroutine(stop_event: Optional[threading.Event] = None):
|
||||
"""
|
||||
The `comm_subroutine` function starts multiple threads to run different tasks concurrently.
|
||||
|
||||
:param logger: The `logger` parameter in the `comm_subroutine` function is an instance of a logger
|
||||
object that is used for logging messages at various levels (e.g., debug, info, warning, error). In
|
||||
the provided code snippet, the logger is used to log messages at the "info" level
|
||||
Workers run as daemon threads and honour an optional ``stop_event`` so the
|
||||
bot can shut the communication layer down cleanly instead of blocking
|
||||
forever on ``join()``.
|
||||
|
||||
:param stop_event: optional :class:`threading.Event` shared with the caller
|
||||
to coordinate a cooperative shutdown.
|
||||
"""
|
||||
# logger.setLevel(logging.DEBUG)
|
||||
logger = logging.getLogger("discord")
|
||||
logger.info("Started comms")
|
||||
threads = []
|
||||
# threads.append(threading.Thread(target=flask_debug))
|
||||
threads.append(threading.Thread(target=waitress_run))
|
||||
threads.append(threading.Thread(target=scan_queue))
|
||||
threads.append(threading.Thread(target=scan_incoming))
|
||||
threads.append(threading.Thread(target=waitress_run, daemon=True))
|
||||
threads.append(
|
||||
threading.Thread(
|
||||
target=scan_queue, kwargs={"stop_event": stop_event}, daemon=True
|
||||
)
|
||||
)
|
||||
threads.append(
|
||||
threading.Thread(
|
||||
target=scan_incoming, kwargs={"stop_event": stop_event}, daemon=True
|
||||
)
|
||||
)
|
||||
|
||||
for worker in threads:
|
||||
worker.start()
|
||||
for worker in threads:
|
||||
worker.join()
|
||||
|
||||
try:
|
||||
while any(thread.is_alive() for thread in threads):
|
||||
if stop_event and stop_event.is_set():
|
||||
break
|
||||
time.sleep(0.5)
|
||||
finally:
|
||||
if stop_event:
|
||||
stop_event.set()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user