diff --git a/prototype_one/bot.py b/prototype_one/bot.py index 93b4a35..3614e39 100755 --- a/prototype_one/bot.py +++ b/prototype_one/bot.py @@ -5,6 +5,7 @@ """ Module of a python bot named Conjurer - used to work on BDSM discord servers. """ +import asyncio import logging # *=========================================== Standard Library Imports @@ -82,22 +83,52 @@ async def on_ready(): logger.info("All systems: operational") +# *=========================================== Runtime orchestration +# The legacy bootstrap used two bare threads (client.run + comm_subroutine) and +# joined them, which made a clean shutdown impossible. We now drive everything +# from a single asyncio loop: the Flask comm layer still runs in its own +# threads (via asyncio.to_thread) but is steered through a shared stop_event so +# the bot can stop both halves cooperatively. + + +async def _run_comm_subroutine(stop_event: threading.Event) -> None: + """Run the blocking comm subroutine in a worker thread.""" + await asyncio.to_thread(comm_subroutine, stop_event) + + +async def _run_bot(token: str, shutdown_event: asyncio.Event) -> None: + """Start the Discord client and flag shutdown when it returns.""" + try: + await client.start(token, log_handler=None) + finally: + shutdown_event.set() + + +async def main() -> None: + if not TOKEN: + logger.error("Discord token missing - set DISCORD_TOKEN or configure netrc") + return + + logger.info("Starting discord bot") + shutdown_event = asyncio.Event() + comm_stop_event = threading.Event() + + comm_task = asyncio.create_task(_run_comm_subroutine(comm_stop_event)) + bot_task = asyncio.create_task(_run_bot(TOKEN, shutdown_event)) + + try: + await shutdown_event.wait() + except (KeyboardInterrupt, asyncio.CancelledError): + logger.info("Shutdown signal received") + comm_stop_event.set() + await client.close() + finally: + comm_stop_event.set() + if not client.is_closed(): + await client.close() + await asyncio.gather(bot_task, comm_task, return_exceptions=True) + + # *================================== Run if __name__ == "__main__": - logger.info("Starting discord bot") - threads = [] - logger.info("Starting discord bot: Creating threads") - threads.append(threading.Thread(target=client.run, args=(TOKEN,),kwargs={"log_handler":None})) - threads.append(threading.Thread(target=comm_subroutine)) - logger.info("Starting discord bot: Starting threads") - WRK_CNT = 0 - for worker in threads: - WRK_CNT += 1 - logger.info("Starting discord bot: Starting thread %s", WRK_CNT) - worker.start() - logger.info("Starting discord bot: Joining threads") - WRK_CNT = 0 - for worker in threads: - WRK_CNT += 1 - logger.info("Starting discord bot: Joining thread %s", WRK_CNT) - worker.join() + asyncio.run(main()) diff --git a/prototype_one/communication_subroutine.py b/prototype_one/communication_subroutine.py index f4ee528..0edcc31 100644 --- a/prototype_one/communication_subroutine.py +++ b/prototype_one/communication_subroutine.py @@ -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[^;]*);").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__": diff --git a/prototype_one/constants.py b/prototype_one/constants.py index 2185187..aac36eb 100644 --- a/prototype_one/constants.py +++ b/prototype_one/constants.py @@ -1,13 +1,51 @@ +"""Centralised configuration and runtime constants for Conjurer. + +Historically this module performed heavy filesystem and credential reads at +import time which made the bot brittle on hosts that did not mirror the +original paths. This version keeps the original platform defaults (so the +behaviour on the Raspberry Pi / WSL / Windows deployments is unchanged when no +environment variables are set) but adds three robustness improvements ported +from the dockerised experiment: + +* every path/endpoint can be overridden via an environment variable, +* JSON state files are loaded defensively (a missing or corrupt file no longer + crashes the whole bot at import time), +* optional dependencies (openai, spotipy, netrc) and credentials are guarded so + the bot can still start when a secondary integration is offline, and +* a shared ``CONJURER_API_KEY`` plus ``service_headers()`` helper enables + authenticated internal HTTP calls. + +All path constants intentionally remain plain ``str`` (with their original +trailing separators) to stay byte-for-byte compatible with the existing string +concatenation in the command modules. +""" + import json -import netrc +import logging +import os from datetime import datetime from platform import uname from sys import platform from typing import List, Optional, TypedDict -import openai -import spotipy -from spotipy.oauth2 import SpotifyClientCredentials +try: + import netrc +except ImportError: # pragma: no cover - standard on CPython + netrc = None + +try: + import openai +except ImportError: # pragma: no cover - optional at runtime + openai = None + +try: + import spotipy + from spotipy.oauth2 import SpotifyClientCredentials +except ImportError: # pragma: no cover - optional component + spotipy = None + SpotifyClientCredentials = None + +logger = logging.getLogger("discord") Music_Config = TypedDict( "Music_Config", @@ -30,16 +68,12 @@ MUSIC_FOLDER = "" MEMORY_FIVE_SIARA = "" MEMORY_FIVE_MUZYKA = "" SETTINGS_FILE = "" -ENCODING = "" +ENCODING = "utf-8" GRAPHICS_PATH = "" MUZYKA_MOJEGO_LUDU_HISTORIA = 1500 MUZYKA_MOJEGO_LUDU_SLOWA_KLUCZOWE = 15 MUZYKA_MOJEGO_LUDU_PLAJLISTA = 30 -FILE_SERVICE_ADDRESS = "http://192.168.1.15:5000" -RADIO_HARBOR_ADDRESS = "http://192.168.1.15:54321" -SKIP_TRACK = "/skip" - GET_MP3 = "/mp3" SEND_MP3 = "/update_mp3" GET_PLAYLIST = "/get_music" @@ -48,14 +82,13 @@ CREATE_PRIO_PLAYLIST = "/create_priority_playlist" REQUEST_MUSIC = "/request_radio_file" CLEAR_PRIO = "/clear_pr_pls" -LIBRARIAN_SERVICE_ADDRESS = "http://192.168.1.192:5001" SEND_QUERY = "/query" TIME_BETWEEN_CALLS = 100000 LAST_SPONTANEOUS_CALL = datetime.now() -HOST_ADDRESS = "192.168.1.191" -PORT_ADDRESS = 5000 -# *=========================================== Platform Specific Predefines +# *=========================================== Platform Specific Defaults +# These blocks only establish *default* values. Every constant is overridable +# through the matching environment variable further below. if platform in ("linux", "linux2"): SEPARATOR_FILE_PATH = "/" @@ -101,47 +134,158 @@ elif platform == "win32": ENCODING = "utf-8" DIR_PATH_SADOX = "C:\\Users\\mtusz\\OneDrive\\Dokumenty\\Fansadox\\" SEPARATOR_FILE_PATH = "\\" -with open(SETTINGS_FILE, "r", encoding=ENCODING) as f_settings_file: - DATA = json.load(f_settings_file) -REMOTE_HOST_NAME = "openai" -netrc_mod = netrc.netrc(NETRC_FILE) -authTokens = netrc_mod.authenticators(REMOTE_HOST_NAME) -openai.api_key = authTokens[2] -OPENAICLIENT = openai.AsyncOpenAI(api_key=openai.api_key) -REMOTE_HOST_NAME = "discord" -authTokens = netrc_mod.authenticators(REMOTE_HOST_NAME) -TOKEN = authTokens[2] -REMOTE_HOST_NAME = "spotipy" -authTokens = netrc_mod.authenticators(REMOTE_HOST_NAME) -SPOTIFY_CTRL = spotipy.Spotify( - client_credentials_manager=SpotifyClientCredentials( - client_id=authTokens[0], - client_secret=authTokens[2], - ) +else: + # Fallback for development hosts (macOS, BSD, …) that match none of the + # production platforms. Everything is rooted next to this file so the + # module can at least be imported and unit-tested off-deployment. + _BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + SEPARATOR_FILE_PATH = os.sep + LOGFILE = os.path.join(_BASE_DIR, "discord.log") + MEMORY_FIVE_SIARA = os.path.join(_BASE_DIR, "pamiec.json") + SYSTEM_GPT_SETTINGS = os.path.join(_BASE_DIR, "system_gpt_settings.json") + MEMORY_FIVE_MUZYKA = os.path.join(_BASE_DIR, "pamiec_muzyki.json") + MUSIC_FOLDER = os.path.join(_BASE_DIR, "music") + os.sep + SETTINGS_FILE = os.path.join(_BASE_DIR, "settings.json") + NETRC_FILE = os.path.join(os.path.expanduser("~"), ".netrc") + LOGSTORE = os.path.join(_BASE_DIR, "logs") + os.sep + ACCIDENT_LOG = os.path.join(_BASE_DIR, "accident_log.json") + GRAPHICS_PATH = os.path.join(_BASE_DIR, "Conjurer_graphics") + os.sep + DIR_PATH_SADOX = os.path.join(_BASE_DIR, "Fansadox") + os.sep + + +# *=========================================== Environment overrides +# Values stay as plain strings so existing ``PATH + filename`` concatenation in +# the command modules keeps working unchanged. + +LOGFILE = os.getenv("CONJURER_LOG_FILE", LOGFILE) +NETRC_FILE = os.getenv("CONJURER_NETRC_FILE", NETRC_FILE) +SETTINGS_FILE = os.getenv("CONJURER_SETTINGS_FILE", SETTINGS_FILE) +MEMORY_FIVE_SIARA = os.getenv("CONJURER_MEMORY_FILE", MEMORY_FIVE_SIARA) +MEMORY_FIVE_MUZYKA = os.getenv("CONJURER_MUSIC_MEMORY_FILE", MEMORY_FIVE_MUZYKA) +SYSTEM_GPT_SETTINGS = os.getenv("CONJURER_SYSTEM_GPT_SETTINGS", SYSTEM_GPT_SETTINGS) +GRAPHICS_PATH = os.getenv("CONJURER_GRAPHICS_PATH", GRAPHICS_PATH) +MUSIC_FOLDER = os.getenv("CONJURER_MUSIC_FOLDER", MUSIC_FOLDER) +LOGSTORE = os.getenv("CONJURER_LOGSTORE", LOGSTORE) +ACCIDENT_LOG = os.getenv("CONJURER_ACCIDENT_LOG", ACCIDENT_LOG) +DIR_PATH_SADOX = os.getenv("CONJURER_SADOX_DIR", DIR_PATH_SADOX) +ENCODING = os.getenv("CONJURER_ENCODING", ENCODING) +SEPARATOR_FILE_PATH = os.getenv("CONJURER_PATH_SEPARATOR", SEPARATOR_FILE_PATH) + +FILE_SERVICE_ADDRESS = os.getenv("CONJURER_FILE_SERVICE", "http://192.168.1.15:5000") +RADIO_HARBOR_ADDRESS = os.getenv("CONJURER_RADIO_HARBOR", "http://192.168.1.15:54321") +SKIP_TRACK = os.getenv("CONJURER_SKIP_ENDPOINT", "/skip") +LIBRARIAN_SERVICE_ADDRESS = os.getenv( + "CONJURER_LIBRARIAN_SERVICE", "http://192.168.1.192:5001" ) -REMOTE_HOST_NAME = "youtube" -authTokens = netrc_mod.authenticators(REMOTE_HOST_NAME) -YOUTUBE_AUTH = [authTokens[0],authTokens[2]] +HOST_ADDRESS = os.getenv("CONJURER_DISCORD_HOST", "192.168.1.191") +PORT_ADDRESS = int(os.getenv("CONJURER_DISCORD_PORT", "5000")) -WORD_REACTIONS = DATA["word_reactions"] -CYCLIC_WORDS = DATA["cyclic_words"] +# Shared secret for authenticating internal service-to-service HTTP calls. +API_SHARED_KEY = os.getenv("CONJURER_API_KEY", "") + + +# *=========================================== Defensive state loading +def _load_json(path: str, fallback): + """Load JSON from *path*, falling back gracefully on missing/corrupt files.""" + try: + with open(path, "r", encoding=ENCODING) as handle: + return json.load(handle) + except FileNotFoundError: + logger.warning("Missing JSON file at %s - using fallback", path) + return fallback + except json.JSONDecodeError: + logger.warning("Corrupt JSON at %s - resetting to fallback", path) + return fallback + + +DATA = _load_json(SETTINGS_FILE, {}) +WORD_REACTIONS = DATA.get("word_reactions", {}) +CYCLIC_WORDS = DATA.get("cyclic_words", {}) for key in WORD_REACTIONS: - WORD_REACTIONS[key][2] = datetime.now() -with open(MEMORY_FIVE_SIARA, "r+", encoding=ENCODING) as temp_memory_file: - # First we load existing data into a dict. - MESSAGE_TABLE = json.load(temp_memory_file) + if isinstance(WORD_REACTIONS[key], list) and len(WORD_REACTIONS[key]) >= 3: + WORD_REACTIONS[key][2] = datetime.now() -with open(SYSTEM_GPT_SETTINGS, "r+", encoding=ENCODING) as temp_settings_file: - # First we load existing data into a dict. - GPT_SETTINGS = json.load(temp_settings_file) -with open(MEMORY_FIVE_MUZYKA, "r+", encoding=ENCODING) as temp_music_memory_file: - # First we load existing data into a dict. - MESSAGE_TABLE_MUZYKA = json.load(temp_music_memory_file) -SPECJALNE_ZIEMNIACZKI = GPT_SETTINGS[1] +MESSAGE_TABLE = _load_json(MEMORY_FIVE_SIARA, []) +GPT_SETTINGS = _load_json(SYSTEM_GPT_SETTINGS, {}) +MESSAGE_TABLE_MUZYKA = _load_json(MEMORY_FIVE_MUZYKA, []) + +SPECJALNE_ZIEMNIACZKI = GPT_SETTINGS[1] if isinstance(GPT_SETTINGS, list) else {} ASSISTANTS = {} +# *=========================================== Credentials +def _load_netrc_credentials(host: str): + """Return the netrc authenticators tuple for *host* or ``None``.""" + if netrc is None: + return None + try: + parsed = netrc.netrc(NETRC_FILE) + except FileNotFoundError: + logger.warning("netrc file %s not found", NETRC_FILE) + return None + except netrc.NetrcParseError: + logger.warning("netrc file %s is invalid", NETRC_FILE) + return None + return parsed.authenticators(host) + + +def _resolve_token(host: str, env_var: str) -> Optional[str]: + """Prefer an environment variable, then fall back to netrc.""" + env_value = os.getenv(env_var) + if env_value: + return env_value + creds = _load_netrc_credentials(host) + if creds: + return creds[2] + logger.warning("Token for %s not configured", host) + return None + + +OPENAI_API_KEY = _resolve_token("openai", "OPENAI_API_KEY") +if openai and OPENAI_API_KEY: + openai.api_key = OPENAI_API_KEY + OPENAICLIENT = openai.AsyncOpenAI(api_key=OPENAI_API_KEY) +else: + OPENAICLIENT = None + +TOKEN = _resolve_token("discord", "DISCORD_TOKEN") + +if spotipy: + _spotify_creds = _load_netrc_credentials("spotipy") + if _spotify_creds and SpotifyClientCredentials: + SPOTIFY_CTRL = spotipy.Spotify( + client_credentials_manager=SpotifyClientCredentials( + client_id=_spotify_creds[0], + client_secret=_spotify_creds[2], + ) + ) + else: + SPOTIFY_CTRL = None +else: + SPOTIFY_CTRL = None + +_youtube_creds = _load_netrc_credentials("youtube") +if _youtube_creds: + YOUTUBE_AUTH = [_youtube_creds[0], _youtube_creds[2]] +else: + YOUTUBE_AUTH = [ + os.getenv("YOUTUBE_USERNAME", ""), + os.getenv("YOUTUBE_PASSWORD", ""), + ] + + +def service_headers(): + """Shared header dict for internal service-to-service HTTP calls. + + Returns an empty dict when no key is configured, keeping calls backward + compatible with deployments that do not (yet) enforce authentication. + """ + if API_SHARED_KEY: + return {"X-Conjurer-Api-Key": API_SHARED_KEY} + return {} + + LATEX_TEX_ENGINE = "tectonic" LATEX_MAX_COMPILE_SECONDS = 45 LATEX_MAX_ATTACH_MB = 8 diff --git a/prototype_one/librarian_commands.py b/prototype_one/librarian_commands.py index aa12f6b..74d5b1e 100644 --- a/prototype_one/librarian_commands.py +++ b/prototype_one/librarian_commands.py @@ -15,7 +15,9 @@ from discord.ext import commands, tasks from ai_functions import handle_response from communication_subroutine import IN_COMM_Q, OUT_COMM_Q, QueryControl -from constants import DIR_PATH_SADOX, LIBRARIAN_SERVICE_ADDRESS, SEND_QUERY +from constants import DIR_PATH_SADOX, LIBRARIAN_SERVICE_ADDRESS, SEND_QUERY, service_headers + +SERVICE_HEADERS = service_headers() class DataModule(commands.Cog): @@ -165,6 +167,7 @@ class DataModule(commands.Cog): requests.post, f"{LIBRARIAN_SERVICE_ADDRESS}{SEND_QUERY}", json=json_query, + headers=SERVICE_HEADERS, timeout=360, ) await ctx.send( @@ -260,6 +263,7 @@ class DataModule(commands.Cog): requests.post, f"{LIBRARIAN_SERVICE_ADDRESS}{SEND_QUERY}", json=json_query, + headers=SERVICE_HEADERS, timeout=360, ) await ctx.send( diff --git a/prototype_one/music_functions.py b/prototype_one/music_functions.py index a6e2881..ef49c31 100644 --- a/prototype_one/music_functions.py +++ b/prototype_one/music_functions.py @@ -17,10 +17,13 @@ from constants import ( SEND_MP3, SPOTIFY_CTRL, YOUTUBE_AUTH, + service_headers, ) from spotify_dl import spotify from spotify_dl import youtube as youtube_download +SERVICE_HEADERS = service_headers() + class MusicFileList(object): @@ -43,7 +46,11 @@ class MusicFileList(object): """ try: self.logger.info("Attempt to connect to file service") - response = requests.get(f"{FILE_SERVICE_ADDRESS}{GET_MP3}", timeout=360) + response = requests.get( + f"{FILE_SERVICE_ADDRESS}{GET_MP3}", + headers=SERVICE_HEADERS, + timeout=360, + ) self.music_file_list = response.json()["music_file_list"] self.file_service_active = True except requests.exceptions.RequestException as e: @@ -98,7 +105,12 @@ class MusicFileList(object): """ self.music_file_list.append(item) post_data = {"item": str(item)} - requests.post(f"{FILE_SERVICE_ADDRESS}{SEND_MP3}", json=post_data, timeout=360) + requests.post( + f"{FILE_SERVICE_ADDRESS}{SEND_MP3}", + json=post_data, + headers=SERVICE_HEADERS, + timeout=360, + ) MUSIC_FILE_LIST = MusicFileList("discord") @@ -308,6 +320,7 @@ async def search_music(ctx, how_many=0, slowa_kluczowe=None): requests.post, f"{FILE_SERVICE_ADDRESS}{GET_PLAYLIST}", json=jrequest, + headers=SERVICE_HEADERS, timeout=360, ) return_data = await coroutine diff --git a/prototype_one/radio_commands.py b/prototype_one/radio_commands.py index 04d4d35..609ab7c 100644 --- a/prototype_one/radio_commands.py +++ b/prototype_one/radio_commands.py @@ -8,7 +8,9 @@ import uuid import asyncio from datetime import datetime -from constants import RADIO_HARBOR_ADDRESS, SKIP_TRACK, FILE_SERVICE_ADDRESS, ADD_TO_PRIO_PLAYLIST, REQUEST_MUSIC, CREATE_PRIO_PLAYLIST, CLEAR_PRIO +from constants import RADIO_HARBOR_ADDRESS, SKIP_TRACK, FILE_SERVICE_ADDRESS, ADD_TO_PRIO_PLAYLIST, REQUEST_MUSIC, CREATE_PRIO_PLAYLIST, CLEAR_PRIO, service_headers + +SERVICE_HEADERS = service_headers() class RadioModule(commands.Cog): def __init__(self, bot, logger_name): @@ -34,6 +36,7 @@ class RadioModule(commands.Cog): coroutine = asyncio.to_thread( requests.get, f"{RADIO_HARBOR_ADDRESS}{SKIP_TRACK}", + headers=SERVICE_HEADERS, timeout=360, ) result = await coroutine @@ -95,6 +98,7 @@ class RadioModule(commands.Cog): requests.post, f"{FILE_SERVICE_ADDRESS}{ADD_TO_PRIO_PLAYLIST}", json=jrequest, + headers=SERVICE_HEADERS, timeout=360, ) result = await coroutine @@ -130,6 +134,7 @@ class RadioModule(commands.Cog): requests.post, f"{FILE_SERVICE_ADDRESS}{REQUEST_MUSIC}", json=jrequest, + headers=SERVICE_HEADERS, timeout=360, ) result = await coroutine @@ -167,6 +172,7 @@ class RadioModule(commands.Cog): requests.post, f"{FILE_SERVICE_ADDRESS}{CREATE_PRIO_PLAYLIST}", json=jrequest, + headers=SERVICE_HEADERS, timeout=360, ) result = await coroutine @@ -194,6 +200,7 @@ class RadioModule(commands.Cog): coroutine = asyncio.to_thread( requests.get, f"{FILE_SERVICE_ADDRESS}{CLEAR_PRIO}", + headers=SERVICE_HEADERS, timeout=360, ) result = await coroutine