From f9fdffa48aba5f027f51ca4265bb9fc248f25bb7 Mon Sep 17 00:00:00 2001 From: chchatzop <35049131+chchatzop@users.noreply.github.com> Date: Thu, 24 Sep 2026 13:42:41 +0300 Subject: [PATCH] The List Browser's search can ask only the bots that are online (#926) AutoGet's Online Only. An Online only switch beside the cross-list filter keeps only the lists whose bot is in one of our channels right now; the rest are reported with the lists that had no match, so the sidebar dims them. Off, nothing changes. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01AP6LSxkr4n9dMFNSNMogmW --- docs/UPDATES-PUBLIC.md | 1 + docs/UPDATES.md | 10 +++ ...earch_can_ask_only_bots_that_are_online.py | 66 +++++++++++++++++++ web/app.js | 12 +++- web/index.html | 5 ++ web/lang/en.json | 1 + web/lang/es.json | 1 + web/lang/fr.json | 1 + web/style.css | 10 +++ webserver.py | 20 +++++- 10 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 tests/test_the_search_can_ask_only_bots_that_are_online.py diff --git a/docs/UPDATES-PUBLIC.md b/docs/UPDATES-PUBLIC.md index a85e3fe..f210a70 100644 --- a/docs/UPDATES-PUBLIC.md +++ b/docs/UPDATES-PUBLIC.md @@ -2,6 +2,7 @@ ## Unreleased +- **Added: "Online only" in the List Browser's search.** Tick it to search only the lists of bots that are in the channel right now - the ones you can actually download from today. - **Fixed: fetching from a busy bot now works.** When another bot answered "you're number 12 in my queue", DCCore ignored it, gave up after a minute - and then refused the file when it arrived later. It now understands what file servers answer (OmeNServE, SDFind, SpR, BWI and DCCore itself): a queued request shows its place in their queue on the Downloads page and waits for its turn (up to 12 hours, *Wait for a queued request*), and "I don't have that file" or "queue full" ends the request straight away with the server's own words instead of a minute of silence. - **Faster list rebuilds on a network drive.** The rebuild now scans several folders at once (*Folders scanned at once* under *List rebuild*, 16 by default) instead of one after another, so on a network drive - where most of the time is waiting - every rebuild is shorter, and searches, which wait while it runs, wait less. On a local disk it makes no noticeable difference; set it to 1 to scan one folder at a time as before. - **Added: your list can say how long each track is and at what quality.** Turn on *Length and quality in the list* on the Settings page (`LIST_SHOW_AUDIO_INFO`), and every MP3 and FLAC row gets its duration and bitrate/sample rate/channels after the size - `::INFO:: 10.3MB 4m31s 320/44.1/JS` - the way other servers' lists already show it. Every audio file has to be read once, several at a time; on a large library or a network drive that is spread over a few rebuilds, each spending at most 5 minutes on it (*Time limit for reading audio files* under *List rebuild*), so searches are never held up for long. After that only new or changed files are read. A file it cannot read keeps just its size, and search results drop the extra detail before they would cut a filename short. diff --git a/docs/UPDATES.md b/docs/UPDATES.md index fbd0476..a2b646d 100644 --- a/docs/UPDATES.md +++ b/docs/UPDATES.md @@ -4,6 +4,16 @@ All version changes, optimizations, and bug fixes made over time in the DCCore p ## 🟨 Unreleased +### 🟢 The List Browser's search can ask only the bots that are online (#926) + +Item 7 of #926, AutoGet's "Online Only". The cross-list filter searched every list held, including bots that left +days ago - so the best match was often a file nobody could send. An **Online only** switch beside the filter asks +`/api/filelists/search?online=1`, and `build_crosslist_search_payload(online_only=True)` keeps only the lists whose +bot is in one of our channels right now (`dcc.user_is_present_in_ram()`, the same presence every request is checked +against); the others are reported with the lists that had no match, so the sidebar dims them. Off, it is exactly as +before. `tests/test_the_search_can_ask_only_bots_that_are_online.py` (5): off, on, nobody online, and the route and +page passing the switch; removing the presence check fails two of them. + ### 📬 A bot's answer to our request is understood, and a queued file is taken when it comes (#926) Fetching from another bot understood one reply: "!rar is disabled". Everything else a server says about a request diff --git a/tests/test_the_search_can_ask_only_bots_that_are_online.py b/tests/test_the_search_can_ask_only_bots_that_are_online.py new file mode 100644 index 0000000..4230669 --- /dev/null +++ b/tests/test_the_search_can_ask_only_bots_that_are_online.py @@ -0,0 +1,66 @@ +"""#926 item 7: the cross-list search can be limited to bots that are online. + +AutoGet's "Online Only": of every list held, only the ones whose bot is in one +of our channels right now - the ones a request can reach today. The others are +reported with the lists that had no match, so the sidebar dims them. +""" + +import io +import os +import sys +import unittest + +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if REPO_ROOT not in sys.path: + sys.path.insert(0, REPO_ROOT) + +import defaults as config # noqa: E402 +import webserver # noqa: E402 + +from tests.test_crosslist_search import IndexCase # noqa: E402 + + +class OnlineOnly(IndexCase): + def setUp(self): + super().setUp() + self.index("ServerOne", "01 - Opening Song.flac") + self.index("ServerTwo", "02 - Opening Song.flac") + self.hold("ServerOne", "ServerTwo") + config.channel_users["#chan"] = {"serverone", "someuser"} + + def bots(self, payload): + return sorted({group["bot"] for group in payload["folders"]}) + + def test_off_every_list_is_searched(self): + payload = webserver.build_crosslist_search_payload("opening song") + self.assertEqual(self.bots(payload), ["ServerOne", "ServerTwo"]) + + def test_on_only_bots_in_a_channel_are(self): + payload = webserver.build_crosslist_search_payload("opening song", online_only=True) + self.assertEqual(self.bots(payload), ["ServerOne"]) + self.assertIn("servertwo", payload["empty"], "the sidebar dims it") + self.assertNotIn("servertwo", payload["matched"]) + + def test_with_nobody_online_nothing_matches_and_everything_is_dimmed(self): + config.channel_users["#chan"] = {"someuser"} + payload = webserver.build_crosslist_search_payload("opening song", online_only=True) + self.assertEqual(payload["folders"], []) + self.assertEqual(sorted(payload["empty"]), ["serverone", "servertwo"]) + + +class TheRouteAndThePage(unittest.TestCase): + def read(self, *parts): + with io.open(os.path.join(REPO_ROOT, *parts), encoding="utf-8") as handle: + return handle.read() + + def test_the_route_passes_the_switch(self): + self.assertIn('online_only=request.args.get("online", "") in ("1", "true")', + self.read("webserver.py")) + + def test_the_page_sends_it(self): + self.assertIn('(state.filelistsOnlineOnly ? "&online=1" : "")', self.read("web", "app.js")) + self.assertIn('id="filelists-online-only"', self.read("web", "index.html")) + + +if __name__ == "__main__": + unittest.main() diff --git a/web/app.js b/web/app.js index 4acf708..bf006ee 100644 --- a/web/app.js +++ b/web/app.js @@ -139,6 +139,7 @@ filelistsBody:document.getElementById("filelists-body"), filelistsFilterInput: document.getElementById("filelists-filter-input"), filelistsFilterClear: document.getElementById("filelists-filter-clear"), + filelistsOnlineOnly: document.getElementById("filelists-online-only"), filelistsFilterStatus: document.getElementById("filelists-filter-status"), filelistsFilterActions: document.getElementById("filelists-filter-actions"), filelistsFilterAll: document.getElementById("filelists-filter-all"), @@ -1519,6 +1520,14 @@ rerenderFromFilterPayload(); }); + // #926: search only the lists of bots that are in a channel right now. + if (el.filelistsOnlineOnly) { + el.filelistsOnlineOnly.addEventListener("change", function () { + state.filelistsOnlineOnly = el.filelistsOnlineOnly.checked; + runFilelistsFilter(); + }); + } + el.filelistsFilterClear.addEventListener("click", function () { el.filelistsFilterInput.value = ""; state.filelistsFilter = ""; @@ -2947,7 +2956,8 @@ // spans every list held, so "which bot am I looking at" stops being // the question while a term is set. The sidebar still shows which // bots have matches - see applyFilterHighlight(). - url = "/api/filelists/search?q=" + encodeURIComponent(filter); + url = "/api/filelists/search?q=" + encodeURIComponent(filter) + + (state.filelistsOnlineOnly ? "&online=1" : ""); } else { var base; if (isOwnSource(source)) { diff --git a/web/index.html b/web/index.html index a6c4291..3709962 100644 --- a/web/index.html +++ b/web/index.html @@ -257,6 +257,11 @@

Downloads (fetched from autocomplete="off" aria-describedby="filelists-filter-status"> + +