feat(api,userbot,frontend): search peers without chats and start tracking them

This commit is contained in:
hh committed 2026-08-05 23:46:54 +02:00
1 parent dcf95bd9d4
commit 92fd20137e
21 files changed
+936 -65

No files matched your search

@@ -0,0 +1,41 @@
import { getDiscoverItem } from "$lib/api/endpoints";
import type { DiscoverItem } from "$lib/api/types";
import { accounts } from "$lib/stores/accounts.svelte";
function createDiscover() {
let items = $state<Record<number, DiscoverItem>>({});
let account: number | null = null;
const pending = new Set<number>();
function syncAccount() {
if (accounts.selectedId !== account) {
account = accounts.selectedId;
items = {};
pending.clear();
}
}
return {
get(chatId: number): DiscoverItem | undefined {
return items[chatId];
},
set(item: DiscoverItem) {
items = { ...items, [item.chat_id]: item };
},
async ensure(chatId: number) {
syncAccount();
if (account === null || items[chatId] || pending.has(chatId)) {
return;
}
pending.add(chatId);
try {
const item = await getDiscoverItem(chatId);
items = { ...items, [chatId]: item };
} finally {
pending.delete(chatId);
}
},
};
}
export const discover = createDiscover();
+46 -14
View File
@@ -1,16 +1,18 @@
import { searchMessages } from "$lib/api/endpoints";
import type { SearchHit } from "$lib/api/types";
import { discoverPeers, searchMessages } from "$lib/api/endpoints";
import type { DiscoverItem, SearchHit } from "$lib/api/types";
import { chats } from "$lib/stores/chats.svelte";
const DEBOUNCE_MS = 250;
const REMOTE_DEBOUNCE_MS = 700;
const MIN_LENGTH = 1;
function createSearch() {
let active = $state(false);
let query = $state("");
let messageHits = $state<SearchHit[]>([]);
let peerResults = $state<DiscoverItem[]>([]);
let loading = $state(false);
let timer: ReturnType<typeof setTimeout> | null = null;
let timers: ReturnType<typeof setTimeout>[] = [];
let seq = 0;
const trimmed = $derived(query.trim());
@@ -23,9 +25,12 @@ function createSearch() {
(chat.title ?? "").toLowerCase().includes(needle)
);
});
const peerHits = $derived.by(() => {
const shown = new Set(chatHits.map((chat) => chat.chat_id));
return peerResults.filter((item) => !shown.has(item.chat_id));
});
async function run(value: string) {
const current = ++seq;
async function runMessages(value: string, current: number) {
try {
const hits = await searchMessages(value);
if (current === seq) {
@@ -42,21 +47,46 @@ function createSearch() {
}
}
function schedule() {
if (timer) {
async function runPeers(value: string, current: number, remote: boolean) {
try {
const items = await discoverPeers(value, remote);
if (current === seq) {
peerResults = items;
}
} catch {
if (current === seq && !remote) {
peerResults = [];
}
}
}
function clearTimers() {
for (const timer of timers) {
clearTimeout(timer);
}
timers = [];
}
function schedule() {
clearTimers();
const value = trimmed;
const current = ++seq;
if (value.length < MIN_LENGTH) {
seq++;
messageHits = [];
peerResults = [];
loading = false;
return;
}
loading = true;
timer = setTimeout(() => {
run(value).catch(() => undefined);
}, DEBOUNCE_MS);
timers.push(
setTimeout(() => {
runMessages(value, current).catch(() => undefined);
runPeers(value, current, false).catch(() => undefined);
}, DEBOUNCE_MS),
setTimeout(() => {
runPeers(value, current, true).catch(() => undefined);
}, REMOTE_DEBOUNCE_MS)
);
}
return {
@@ -78,6 +108,9 @@ function createSearch() {
get chatHits() {
return chatHits;
},
get peerHits() {
return peerHits;
},
open() {
active = true;
},
@@ -89,11 +122,10 @@ function createSearch() {
active = false;
query = "";
messageHits = [];
peerResults = [];
loading = false;
seq++;
if (timer) {
clearTimeout(timer);
}
clearTimers();
},
};
}