Changes to internal config handling, server side checking of waiting room policy, updatable config
This commit is contained in:
parent
a983c74de8
commit
31c45e3fe4
2 changed files with 212 additions and 69 deletions
|
@ -62,6 +62,14 @@ sources: dict[str, Source] = {}
|
||||||
currentLock: asyncio.Semaphore = asyncio.Semaphore(0)
|
currentLock: asyncio.Semaphore = asyncio.Semaphore(0)
|
||||||
|
|
||||||
|
|
||||||
|
def default_config():
|
||||||
|
return {
|
||||||
|
"preview_duration": 3,
|
||||||
|
"last_song": None,
|
||||||
|
"waiting_room_policy": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class State:
|
class State:
|
||||||
"""This captures the current state of the playback client.
|
"""This captures the current state of the playback client.
|
||||||
|
@ -87,12 +95,19 @@ class State:
|
||||||
:type secret: str
|
:type secret: str
|
||||||
:param key: An optional key, if registration on the server is limited.
|
:param key: An optional key, if registration on the server is limited.
|
||||||
:type key: Optional[str]
|
:type key: Optional[str]
|
||||||
:param preview_duration: Amount of seconds the preview before a song be
|
:param config: Various configuration options for the client:
|
||||||
displayed.
|
* `preview_duration` (`Optional[int]`): The duration in seconds the
|
||||||
:type preview_duration: int
|
playback client shows a preview for the next song. This is accounted for
|
||||||
:param last_song: At what time should the server not accept any more songs.
|
in the calculation of the ETA for songs later in the queue.
|
||||||
`None` if no such limit should exist.
|
* `last_song` (`Optional[datetime.datetime]`): A timestamp, defining the end of
|
||||||
:type last_song: Optional[datetime.datetime]
|
the queue.
|
||||||
|
* `waiting_room_policy` (Optional[str]): One of:
|
||||||
|
- `force`, if a performer is already in the queue, they are put in the
|
||||||
|
waiting room.
|
||||||
|
- `optional`, if a performer is already in the queue, they have the option
|
||||||
|
to be put in the waiting room.
|
||||||
|
- `None`, performers are always added to the queue.
|
||||||
|
:type config: dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=too-many-instance-attributes
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
@ -105,31 +120,17 @@ class State:
|
||||||
server: str = ""
|
server: str = ""
|
||||||
secret: str = ""
|
secret: str = ""
|
||||||
key: Optional[str] = None
|
key: Optional[str] = None
|
||||||
preview_duration: int = 3
|
config: dict[str, Any] = field(default_factory=default_config)
|
||||||
last_song: Optional[datetime.datetime] = None
|
|
||||||
|
|
||||||
def get_config(self) -> dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Return a subset of values to be send to the server.
|
|
||||||
|
|
||||||
Currently this is:
|
|
||||||
- :py:attr:`State.preview_duration`
|
|
||||||
- :py:attr:`State.last_song` (As a timestamp)
|
|
||||||
|
|
||||||
:return: A dict resulting from the above values
|
|
||||||
:rtype: dict[str, Any]
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"preview_duration": self.preview_duration,
|
|
||||||
"last_song": self.last_song.timestamp()
|
|
||||||
if self.last_song
|
|
||||||
else None,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
state: State = State()
|
state: State = State()
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on("update_config")
|
||||||
|
async def handle_update_config(data: dict[str, Any]) -> None:
|
||||||
|
state.config = default_config() | data
|
||||||
|
|
||||||
|
|
||||||
@sio.on("skip-current")
|
@sio.on("skip-current")
|
||||||
async def handle_skip_current(data: dict[str, Any]) -> None:
|
async def handle_skip_current(data: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -201,7 +202,7 @@ async def handle_connect() -> None:
|
||||||
"recent": state.recent,
|
"recent": state.recent,
|
||||||
"room": state.room,
|
"room": state.room,
|
||||||
"secret": state.secret,
|
"secret": state.secret,
|
||||||
"config": state.get_config(),
|
"config": state.config,
|
||||||
}
|
}
|
||||||
if state.key:
|
if state.key:
|
||||||
data["registration-key"] = state.key
|
data["registration-key"] = state.key
|
||||||
|
@ -252,7 +253,7 @@ async def preview(entry: Entry) -> None:
|
||||||
process = await asyncio.create_subprocess_exec(
|
process = await asyncio.create_subprocess_exec(
|
||||||
"mpv",
|
"mpv",
|
||||||
tmpfile.name,
|
tmpfile.name,
|
||||||
f"--image-display-duration={state.preview_duration}",
|
f"--image-display-duration={state.config['preview_duration']}",
|
||||||
"--sub-pos=50",
|
"--sub-pos=50",
|
||||||
"--sub-file=-",
|
"--sub-file=-",
|
||||||
"--fullscreen",
|
"--fullscreen",
|
||||||
|
@ -290,7 +291,7 @@ async def handle_play(data: dict[str, Any]) -> None:
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
state.current_source = sources[entry.source]
|
state.current_source = sources[entry.source]
|
||||||
if state.preview_duration > 0:
|
if state.config["preview_duration"] > 0:
|
||||||
await preview(entry)
|
await preview(entry)
|
||||||
await sources[entry.source].play(entry)
|
await sources[entry.source].play(entry)
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
|
@ -409,12 +410,12 @@ async def aiomain() -> None:
|
||||||
sources.update(configure_sources(config["sources"]))
|
sources.update(configure_sources(config["sources"]))
|
||||||
|
|
||||||
if "config" in config:
|
if "config" in config:
|
||||||
if "last_song" in config["config"]:
|
last_song = (
|
||||||
state.last_song = datetime.datetime.fromisoformat(
|
datetime.datetime.fromisoformat(config["config"]["last_song"])
|
||||||
config["config"]["last_song"]
|
if "last_song" in config["config"]
|
||||||
)
|
else None
|
||||||
if "preview_duration" in config["config"]:
|
)
|
||||||
state.preview_duration = config["config"]["preview_duration"]
|
state.config |= config["config"] | {"last_song": last_song}
|
||||||
|
|
||||||
state.key = args.key if args.key else None
|
state.key = args.key if args.key else None
|
||||||
|
|
||||||
|
|
210
syng/server.py
210
syng/server.py
|
@ -21,6 +21,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
from json.decoder import JSONDecodeError
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from dataclasses import field
|
from dataclasses import field
|
||||||
|
@ -43,6 +44,12 @@ sio = socketio.AsyncServer(
|
||||||
app = web.Application()
|
app = web.Application()
|
||||||
sio.attach(app)
|
sio.attach(app)
|
||||||
|
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
"preview_duration": 3,
|
||||||
|
"waiting_room_policy": None,
|
||||||
|
"last_song": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def root_handler(request: Any) -> Any:
|
async def root_handler(request: Any) -> Any:
|
||||||
"""
|
"""
|
||||||
|
@ -68,7 +75,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Config:
|
class Client:
|
||||||
"""This stores the configuration of a specific playback client.
|
"""This stores the configuration of a specific playback client.
|
||||||
|
|
||||||
In case a new playback client connects to a room, these values can be
|
In case a new playback client connects to a room, these values can be
|
||||||
|
@ -79,18 +86,23 @@ class Config:
|
||||||
:type sources: Source
|
:type sources: Source
|
||||||
:param sources_prio: A list defining the order of the search results.
|
:param sources_prio: A list defining the order of the search results.
|
||||||
:type sources_prio: list[str]
|
:type sources_prio: list[str]
|
||||||
:param preview_duration: The duration in seconds the playbackclients shows
|
:param config: Various configuration options for the client:
|
||||||
a preview for the next song. This is accounted for in the calculation
|
* `preview_duration` (`Optional[int]`): The duration in seconds the
|
||||||
of the ETA for songs later in the queue.
|
playback client shows a preview for the next song. This is accounted for
|
||||||
:type preview_duration: int
|
in the calculation of the ETA for songs later in the queue.
|
||||||
:param last_song: A timestamp, defining the end of the queue.
|
* `last_song` (`Optional[float]`): A timestamp, defining the end of the queue.
|
||||||
:type last_song: Optional[float]
|
* `waiting_room_policy` (Optional[str]): One of:
|
||||||
|
- `force`, if a performer is already in the queue, they are put in the
|
||||||
|
waiting room.
|
||||||
|
- `optional`, if a performer is already in the queue, they have the option
|
||||||
|
to be put in the waiting room.
|
||||||
|
- `None`, performers are always added to the queue.
|
||||||
|
:type config: dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sources: dict[str, Source]
|
sources: dict[str, Source]
|
||||||
sources_prio: list[str]
|
sources_prio: list[str]
|
||||||
preview_duration: int
|
config: dict[str, Any]
|
||||||
last_song: Optional[float]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -113,8 +125,8 @@ class State:
|
||||||
a new playback client connects to a room (with the correct secret),
|
a new playback client connects to a room (with the correct secret),
|
||||||
this will be swapped with the new sid.
|
this will be swapped with the new sid.
|
||||||
:type sid: str
|
:type sid: str
|
||||||
:param config: The config for the client
|
:param client: The config for the playback client
|
||||||
:type config: Config
|
:type client: Client
|
||||||
"""
|
"""
|
||||||
|
|
||||||
secret: str
|
secret: str
|
||||||
|
@ -122,7 +134,7 @@ class State:
|
||||||
waiting_room: list[Entry]
|
waiting_room: list[Entry]
|
||||||
recent: list[Entry]
|
recent: list[Entry]
|
||||||
sid: str
|
sid: str
|
||||||
config: Config
|
client: Client
|
||||||
last_seen: datetime.datetime = field(
|
last_seen: datetime.datetime = field(
|
||||||
init=False, default_factory=datetime.datetime.now
|
init=False, default_factory=datetime.datetime.now
|
||||||
)
|
)
|
||||||
|
@ -153,6 +165,7 @@ async def send_state(state: State, sid: str) -> None:
|
||||||
"queue": state.queue,
|
"queue": state.queue,
|
||||||
"recent": state.recent,
|
"recent": state.recent,
|
||||||
"waiting_room": state.waiting_room,
|
"waiting_room": state.waiting_room,
|
||||||
|
"config": state.client.config,
|
||||||
},
|
},
|
||||||
room=sid,
|
room=sid,
|
||||||
)
|
)
|
||||||
|
@ -188,7 +201,7 @@ async def handle_waiting_room_append(sid: str, data: dict[str, Any]) -> None:
|
||||||
room = session["room"]
|
room = session["room"]
|
||||||
state = clients[room]
|
state = clients[room]
|
||||||
|
|
||||||
source_obj = state.config.sources[data["source"]]
|
source_obj = state.client.sources[data["source"]]
|
||||||
|
|
||||||
entry = await source_obj.get_entry(data["performer"], data["ident"])
|
entry = await source_obj.get_entry(data["performer"], data["ident"])
|
||||||
|
|
||||||
|
@ -254,18 +267,23 @@ async def append_to_queue(
|
||||||
start_time = state.queue.fold(
|
start_time = state.queue.fold(
|
||||||
lambda item, time: time
|
lambda item, time: time
|
||||||
+ item.duration
|
+ item.duration
|
||||||
+ state.config.preview_duration
|
+ state.client.config["preview_duration"]
|
||||||
+ 1,
|
+ 1,
|
||||||
start_time,
|
start_time,
|
||||||
)
|
)
|
||||||
|
|
||||||
if state.config.last_song:
|
if state.client.config["last_song"]:
|
||||||
if state.config.last_song < start_time:
|
if state.client.config["last_song"] < start_time:
|
||||||
end_time = datetime.datetime.fromtimestamp(state.config.last_song)
|
# end_time = datetime.datetime.fromtimestamp(
|
||||||
|
# state.client.config["last_song"]
|
||||||
|
# )
|
||||||
if report_to is not None:
|
if report_to is not None:
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"err",
|
"err",
|
||||||
{"type": "QUEUE_FULL", "end_time": state.config.last_song},
|
{
|
||||||
|
"type": "QUEUE_FULL",
|
||||||
|
"end_time": state.client.config["last_song"],
|
||||||
|
},
|
||||||
room=report_to,
|
room=report_to,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
@ -280,6 +298,57 @@ async def append_to_queue(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on("show_config")
|
||||||
|
async def handle_show_config(sid: str) -> None:
|
||||||
|
"""
|
||||||
|
Sends public config to webclient.
|
||||||
|
|
||||||
|
This will only be send if the client is on an admin connection.
|
||||||
|
|
||||||
|
:param sid: The session id of the client sending this request
|
||||||
|
:type sid: str
|
||||||
|
:rtype: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
async with sio.session(sid) as session:
|
||||||
|
room = session["room"]
|
||||||
|
is_admin = session["admin"]
|
||||||
|
state = clients[room]
|
||||||
|
|
||||||
|
if is_admin:
|
||||||
|
await sio.emit(
|
||||||
|
"config",
|
||||||
|
state.client.config,
|
||||||
|
sid,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await sio.emit("err", {"type": "NO_ADMIN"}, sid)
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on("update_config")
|
||||||
|
async def handle_update_config(sid: str, data: dict[str, Any]) -> None:
|
||||||
|
async with sio.session(sid) as session:
|
||||||
|
room = session["room"]
|
||||||
|
is_admin = session["admin"]
|
||||||
|
state = clients[room]
|
||||||
|
|
||||||
|
if is_admin:
|
||||||
|
try:
|
||||||
|
config = json.loads(data["config"])
|
||||||
|
await sio.emit(
|
||||||
|
"update_config",
|
||||||
|
DEFAULT_CONFIG | config,
|
||||||
|
state.sid,
|
||||||
|
)
|
||||||
|
state.client.config = DEFAULT_CONFIG | config
|
||||||
|
await sio.emit("update_config", config, room)
|
||||||
|
except JSONDecodeError:
|
||||||
|
await sio.emit("err", {"type": "JSON_MALFORMED"})
|
||||||
|
|
||||||
|
else:
|
||||||
|
await sio.emit("err", {"type": "NO_ADMIN"}, sid)
|
||||||
|
|
||||||
|
|
||||||
@sio.on("append")
|
@sio.on("append")
|
||||||
async def handle_append(sid: str, data: dict[str, Any]) -> None:
|
async def handle_append(sid: str, data: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -297,6 +366,10 @@ async def handle_append(sid: str, data: dict[str, Any]) -> None:
|
||||||
request is denied and a "msg" message is send to the client, detailing
|
request is denied and a "msg" message is send to the client, detailing
|
||||||
this.
|
this.
|
||||||
|
|
||||||
|
If a waitingroom is forced or optional, it is checked, if one of the performers is
|
||||||
|
already in queue. In that case, a "ask_for_waitingroom" message is send to the
|
||||||
|
client.
|
||||||
|
|
||||||
Otherwise the song is added to the queue. And all connected clients (web
|
Otherwise the song is added to the queue. And all connected clients (web
|
||||||
and playback client) are informed of the new state with a "state" message.
|
and playback client) are informed of the new state with a "state" message.
|
||||||
|
|
||||||
|
@ -316,7 +389,30 @@ async def handle_append(sid: str, data: dict[str, Any]) -> None:
|
||||||
room = session["room"]
|
room = session["room"]
|
||||||
state = clients[room]
|
state = clients[room]
|
||||||
|
|
||||||
source_obj = state.config.sources[data["source"]]
|
if state.client.config["waiting_room_policy"] and (
|
||||||
|
state.client.config["waiting_room_policy"].lower() == "force"
|
||||||
|
or state.client.config["waiting_room_policy"].lower() == "optional"
|
||||||
|
):
|
||||||
|
old_entry = state.queue.find_by_name(data["performer"])
|
||||||
|
if old_entry is not None:
|
||||||
|
await sio.emit(
|
||||||
|
"ask_for_waitingroom",
|
||||||
|
{
|
||||||
|
"current_entry": {
|
||||||
|
"source": data["source"],
|
||||||
|
"performer": data["performer"],
|
||||||
|
"ident": data["ident"],
|
||||||
|
},
|
||||||
|
"old_entry": {
|
||||||
|
"artist": old_entry.artist,
|
||||||
|
"title": old_entry.title,
|
||||||
|
"performer": old_entry.performer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
source_obj = state.client.sources[data["source"]]
|
||||||
|
|
||||||
entry = await source_obj.get_entry(data["performer"], data["ident"])
|
entry = await source_obj.get_entry(data["performer"], data["ident"])
|
||||||
|
|
||||||
|
@ -333,6 +429,47 @@ async def handle_append(sid: str, data: dict[str, Any]) -> None:
|
||||||
await append_to_queue(room, entry, sid)
|
await append_to_queue(room, entry, sid)
|
||||||
|
|
||||||
|
|
||||||
|
@sio.on("append-anyway")
|
||||||
|
async def handle_append_anyway(sid: str, data: dict[str, Any]) -> None:
|
||||||
|
"""
|
||||||
|
Appends a song to the queue, even if the performer is already in queue.
|
||||||
|
|
||||||
|
Works the same as handle_append, but without the check if the performer is already
|
||||||
|
in queue.
|
||||||
|
|
||||||
|
Only if the waiting_room_policy is not configured as forced.
|
||||||
|
"""
|
||||||
|
async with sio.session(sid) as session:
|
||||||
|
room = session["room"]
|
||||||
|
state = clients[room]
|
||||||
|
|
||||||
|
if state.client.config["waiting_room_policy"].lower() == "force":
|
||||||
|
await sio.emit(
|
||||||
|
"err",
|
||||||
|
{"type": "WAITING_ROOM_FORCED"},
|
||||||
|
room=sid,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
source_obj = state.client.sources[data["source"]]
|
||||||
|
|
||||||
|
entry = await source_obj.get_entry(data["performer"], data["ident"])
|
||||||
|
|
||||||
|
if entry is None:
|
||||||
|
await sio.emit(
|
||||||
|
"msg",
|
||||||
|
{"msg": f"Unable to append {data['ident']}. Maybe try again?"},
|
||||||
|
room=sid,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
entry.uid = data["uid"] if "uid" in data else None
|
||||||
|
|
||||||
|
print(entry)
|
||||||
|
|
||||||
|
await append_to_queue(room, entry, sid)
|
||||||
|
|
||||||
|
|
||||||
@sio.on("meta-info")
|
@sio.on("meta-info")
|
||||||
async def handle_meta_info(sid: str, data: dict[str, Any]) -> None:
|
async def handle_meta_info(sid: str, data: dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -586,10 +723,10 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None:
|
||||||
if data["secret"] == old_state.secret:
|
if data["secret"] == old_state.secret:
|
||||||
logger.info("Got new client connection for %s", room)
|
logger.info("Got new client connection for %s", room)
|
||||||
old_state.sid = sid
|
old_state.sid = sid
|
||||||
old_state.config = Config(
|
old_state.client = Client(
|
||||||
sources=old_state.config.sources,
|
sources=old_state.client.sources,
|
||||||
sources_prio=old_state.config.sources_prio,
|
sources_prio=old_state.client.sources_prio,
|
||||||
**data["config"],
|
config=DEFAULT_CONFIG | data["config"],
|
||||||
)
|
)
|
||||||
await sio.enter_room(sid, room)
|
await sio.enter_room(sid, room)
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
|
@ -615,8 +752,13 @@ async def handle_register_client(sid: str, data: dict[str, Any]) -> None:
|
||||||
waiting_room=initial_waiting_room,
|
waiting_room=initial_waiting_room,
|
||||||
recent=initial_recent,
|
recent=initial_recent,
|
||||||
sid=sid,
|
sid=sid,
|
||||||
config=Config(sources={}, sources_prio=[], **data["config"]),
|
client=Client(
|
||||||
|
sources={},
|
||||||
|
sources_prio=[],
|
||||||
|
config=DEFAULT_CONFIG | data["config"],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
await sio.enter_room(sid, room)
|
await sio.enter_room(sid, room)
|
||||||
await sio.emit(
|
await sio.emit(
|
||||||
"client-registered", {"success": True, "room": room}, room=sid
|
"client-registered", {"success": True, "room": room}, room=sid
|
||||||
|
@ -653,13 +795,13 @@ async def handle_sources(sid: str, data: dict[str, Any]) -> None:
|
||||||
if sid != state.sid:
|
if sid != state.sid:
|
||||||
return
|
return
|
||||||
|
|
||||||
unused_sources = state.config.sources.keys() - data["sources"]
|
unused_sources = state.client.sources.keys() - data["sources"]
|
||||||
new_sources = data["sources"] - state.config.sources.keys()
|
new_sources = data["sources"] - state.client.sources.keys()
|
||||||
|
|
||||||
for source in unused_sources:
|
for source in unused_sources:
|
||||||
del state.config.sources[source]
|
del state.client.sources[source]
|
||||||
|
|
||||||
state.config.sources_prio = data["sources"]
|
state.client.sources_prio = data["sources"]
|
||||||
|
|
||||||
for name in new_sources:
|
for name in new_sources:
|
||||||
await sio.emit("request-config", {"source": name}, room=sid)
|
await sio.emit("request-config", {"source": name}, room=sid)
|
||||||
|
@ -690,12 +832,12 @@ async def handle_config_chunk(sid: str, data: dict[str, Any]) -> None:
|
||||||
if sid != state.sid:
|
if sid != state.sid:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not data["source"] in state.config.sources:
|
if data["source"] not in state.client.sources:
|
||||||
state.config.sources[data["source"]] = available_sources[
|
state.client.sources[data["source"]] = available_sources[
|
||||||
data["source"]
|
data["source"]
|
||||||
](data["config"])
|
](data["config"])
|
||||||
else:
|
else:
|
||||||
state.config.sources[data["source"]].add_to_config(data["config"])
|
state.client.sources[data["source"]].add_to_config(data["config"])
|
||||||
|
|
||||||
|
|
||||||
@sio.on("config")
|
@sio.on("config")
|
||||||
|
@ -722,7 +864,7 @@ async def handle_config(sid: str, data: dict[str, Any]) -> None:
|
||||||
if sid != state.sid:
|
if sid != state.sid:
|
||||||
return
|
return
|
||||||
|
|
||||||
state.config.sources[data["source"]] = available_sources[data["source"]](
|
state.client.sources[data["source"]] = available_sources[data["source"]](
|
||||||
data["config"]
|
data["config"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -910,8 +1052,8 @@ async def handle_search(sid: str, data: dict[str, Any]) -> None:
|
||||||
query = data["query"]
|
query = data["query"]
|
||||||
results_list = await asyncio.gather(
|
results_list = await asyncio.gather(
|
||||||
*[
|
*[
|
||||||
state.config.sources[source].search(query)
|
state.client.sources[source].search(query)
|
||||||
for source in state.config.sources_prio
|
for source in state.client.sources_prio
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue