feat: add annotations, user profiles, watchers, stories, search and more
This commit is contained in:
1 parent
ed469ba8dd
commit
2465bcd184
47 files changed
+5009
-242
No files matched your search
@@ -72,3 +72,56 @@ export function loadAvatar(
|
||||
inflight.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
async function fetchVariant(
|
||||
account: number,
|
||||
kind: AvatarKind,
|
||||
id: number,
|
||||
uniqueId: string,
|
||||
key: string,
|
||||
retry: boolean
|
||||
): Promise<string | null> {
|
||||
const url = `${BASE}/avatars/${kind}/${id}?account_id=${account}&unique_id=${encodeURIComponent(uniqueId)}`;
|
||||
const response = await fetch(url, { headers: authHeaders() });
|
||||
if (response.ok) {
|
||||
const objectUrl = URL.createObjectURL(await response.blob());
|
||||
ready.set(key, objectUrl);
|
||||
return objectUrl;
|
||||
}
|
||||
if (response.status === 409 && retry) {
|
||||
await delay(RETRY_DELAY);
|
||||
return fetchVariant(account, kind, id, uniqueId, key, false);
|
||||
}
|
||||
missing.add(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function loadAvatarVariant(
|
||||
kind: AvatarKind,
|
||||
id: number,
|
||||
uniqueId: string
|
||||
): Promise<string | null> {
|
||||
const account = accounts.selectedId;
|
||||
if (account === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
const key = `${cacheKey(account, kind, id)}:${uniqueId}`;
|
||||
const cached = ready.get(key);
|
||||
if (cached) {
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
if (missing.has(key)) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
const existing = inflight.get(key);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const promise = fetchVariant(account, kind, id, uniqueId, key, true).finally(
|
||||
() => {
|
||||
inflight.delete(key);
|
||||
}
|
||||
);
|
||||
inflight.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
@@ -1,15 +1,24 @@
|
||||
import { request } from "$lib/api/client";
|
||||
import type {
|
||||
Account,
|
||||
Alert,
|
||||
Annotation,
|
||||
AvatarHistoryView,
|
||||
CallbackView,
|
||||
CaptureToggles,
|
||||
Chat,
|
||||
ChatLinkView,
|
||||
DayCount,
|
||||
Folder,
|
||||
JobStatus,
|
||||
JobView,
|
||||
LinkView,
|
||||
MediaVersion,
|
||||
MediaView,
|
||||
MessageAt,
|
||||
MessageVersion,
|
||||
MessageView,
|
||||
PeerHistoryView,
|
||||
PeerView,
|
||||
PinnedView,
|
||||
PolicyChatKind,
|
||||
@@ -17,9 +26,12 @@ import type {
|
||||
PolicyRecord,
|
||||
PresenceHourly,
|
||||
PresenceSample,
|
||||
ReactionView,
|
||||
ResponseStats,
|
||||
SearchHit,
|
||||
StoryView,
|
||||
VolumeBucket,
|
||||
Watch,
|
||||
} from "$lib/api/types";
|
||||
import { accounts } from "$lib/stores/accounts.svelte";
|
||||
|
||||
@@ -79,7 +91,11 @@ export function effectivePolicy(query: {
|
||||
|
||||
export function listMessages(
|
||||
chatId: number,
|
||||
options: Page & { include_deleted?: boolean } = {}
|
||||
options: Page & {
|
||||
include_deleted?: boolean;
|
||||
before_id?: number;
|
||||
after_id?: number;
|
||||
} = {}
|
||||
): Promise<MessageView[]> {
|
||||
return request<MessageView[]>(`/chats/${chatId}/messages`, {
|
||||
account: true,
|
||||
@@ -159,6 +175,90 @@ export function getPeer(peerId: number): Promise<PeerView> {
|
||||
return request<PeerView>(`/peers/${peerId}`, { account: true });
|
||||
}
|
||||
|
||||
export function getPeerHistory(peerId: number): Promise<PeerHistoryView[]> {
|
||||
return request<PeerHistoryView[]>(`/peers/${peerId}/history`, {
|
||||
account: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function getAvatarHistory(
|
||||
kind: "peer" | "chat",
|
||||
id: number
|
||||
): Promise<AvatarHistoryView[]> {
|
||||
return request<AvatarHistoryView[]>(`/avatars/${kind}/${id}/history`, {
|
||||
account: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function getChatMedia(
|
||||
chatId: number,
|
||||
kinds: string[],
|
||||
page: { limit?: number; offset?: number } = {}
|
||||
): Promise<MediaView[]> {
|
||||
return request<MediaView[]>(`/chats/${chatId}/media`, {
|
||||
account: true,
|
||||
query: { kinds: kinds.join(","), ...page },
|
||||
});
|
||||
}
|
||||
|
||||
export function getChatLinks(
|
||||
chatId: number,
|
||||
page: { limit?: number; offset?: number } = {}
|
||||
): Promise<ChatLinkView[]> {
|
||||
return request<ChatLinkView[]>(`/chats/${chatId}/links`, {
|
||||
account: true,
|
||||
query: { ...page },
|
||||
});
|
||||
}
|
||||
|
||||
export function getMessageReactions(
|
||||
chatId: number,
|
||||
messageId: number
|
||||
): Promise<ReactionView[]> {
|
||||
return request<ReactionView[]>(`/messages/${chatId}/${messageId}/reactions`, {
|
||||
account: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function getMessageCallbacks(
|
||||
chatId: number,
|
||||
messageId: number
|
||||
): Promise<CallbackView[]> {
|
||||
return request<CallbackView[]>(`/messages/${chatId}/${messageId}/callbacks`, {
|
||||
account: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function getMessageLinks(
|
||||
chatId: number,
|
||||
messageId: number
|
||||
): Promise<LinkView[]> {
|
||||
return request<LinkView[]>(`/messages/${chatId}/${messageId}/links`, {
|
||||
account: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function getChatCalendar(chatId: number): Promise<DayCount[]> {
|
||||
return request<DayCount[]>(`/chats/${chatId}/calendar`, { account: true });
|
||||
}
|
||||
|
||||
export function getMessageAt(chatId: number, date: string): Promise<MessageAt> {
|
||||
return request<MessageAt>(`/chats/${chatId}/message-at`, {
|
||||
account: true,
|
||||
query: { date },
|
||||
});
|
||||
}
|
||||
|
||||
export function getStories(
|
||||
peerId: number,
|
||||
page: Page = {}
|
||||
): Promise<StoryView[]> {
|
||||
return request<StoryView[]>("/stories", {
|
||||
account: true,
|
||||
query: { peer_id: peerId, ...page },
|
||||
});
|
||||
}
|
||||
|
||||
export function getPeers(ids: number[]): Promise<PeerView[]> {
|
||||
if (ids.length === 0) {
|
||||
return Promise.resolve([]);
|
||||
@@ -249,3 +349,82 @@ export function fetchMedia(
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function listWatches(): Promise<Watch[]> {
|
||||
return request<Watch[]>("/watches", { account: true });
|
||||
}
|
||||
|
||||
export function createWatch(
|
||||
kind: string,
|
||||
params: Record<string, unknown>,
|
||||
enabled: boolean
|
||||
): Promise<Watch> {
|
||||
return request<Watch>("/watches", {
|
||||
method: "POST",
|
||||
body: { account_id: accounts.selectedId, kind, params, enabled },
|
||||
});
|
||||
}
|
||||
|
||||
export function updateWatch(
|
||||
id: number,
|
||||
params: Record<string, unknown>,
|
||||
enabled: boolean
|
||||
): Promise<Watch> {
|
||||
return request<Watch>(`/watches/${id}`, {
|
||||
method: "PUT",
|
||||
body: { params, enabled },
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteWatch(id: number): Promise<void> {
|
||||
return request<void>(`/watches/${id}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export function listAlerts(
|
||||
options: Page & { seen?: boolean } = {}
|
||||
): Promise<Alert[]> {
|
||||
return request<Alert[]>("/alerts", { account: true, query: { ...options } });
|
||||
}
|
||||
|
||||
export function markAlertSeen(id: number): Promise<void> {
|
||||
return request<void>(`/alerts/${id}/seen`, { method: "POST" });
|
||||
}
|
||||
|
||||
export function listAnnotations(
|
||||
options: { chatId?: number; messageId?: number } = {}
|
||||
): Promise<Annotation[]> {
|
||||
return request<Annotation[]>("/annotations", {
|
||||
account: true,
|
||||
query: { chat_id: options.chatId, message_id: options.messageId },
|
||||
});
|
||||
}
|
||||
|
||||
export function createAnnotation(
|
||||
chatId: number,
|
||||
messageId: number,
|
||||
text: string
|
||||
): Promise<Annotation> {
|
||||
return request<Annotation>("/annotations", {
|
||||
method: "POST",
|
||||
body: {
|
||||
account_id: accounts.selectedId,
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
text,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function updateAnnotation(
|
||||
id: number,
|
||||
text: string
|
||||
): Promise<Annotation> {
|
||||
return request<Annotation>(`/annotations/${id}`, {
|
||||
method: "PUT",
|
||||
body: { text },
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteAnnotation(id: number): Promise<void> {
|
||||
return request<void>(`/annotations/${id}`, { method: "DELETE" });
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { accounts } from "$lib/stores/accounts.svelte";
|
||||
import { auth } from "$lib/stores/auth.svelte";
|
||||
|
||||
const BASE = import.meta.env.VITE_API_BASE ?? "/api";
|
||||
|
||||
const ready = new Map<string, string>();
|
||||
const missing = new Set<string>();
|
||||
const inflight = new Map<string, Promise<string | null>>();
|
||||
|
||||
function authHeaders(): Record<string, string> {
|
||||
return auth.token ? { Authorization: `Bearer ${auth.token}` } : {};
|
||||
}
|
||||
|
||||
async function fetchStoryMedia(
|
||||
account: number,
|
||||
peerId: number,
|
||||
storyId: number,
|
||||
key: string
|
||||
): Promise<string | null> {
|
||||
const url = `${BASE}/stories/${peerId}/${storyId}/media?account_id=${account}`;
|
||||
const response = await fetch(url, { headers: authHeaders() });
|
||||
if (response.ok) {
|
||||
const objectUrl = URL.createObjectURL(await response.blob());
|
||||
ready.set(key, objectUrl);
|
||||
return objectUrl;
|
||||
}
|
||||
missing.add(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function loadStoryMedia(
|
||||
peerId: number,
|
||||
storyId: number
|
||||
): Promise<string | null> {
|
||||
const account = accounts.selectedId;
|
||||
if (account === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
const key = `${account}:${peerId}:${storyId}`;
|
||||
const cached = ready.get(key);
|
||||
if (cached) {
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
if (missing.has(key)) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
const existing = inflight.get(key);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const promise = fetchStoryMedia(account, peerId, storyId, key).finally(() => {
|
||||
inflight.delete(key);
|
||||
});
|
||||
inflight.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
@@ -232,6 +232,56 @@ export interface Callback {
|
||||
position: number;
|
||||
}
|
||||
|
||||
export interface AvatarHistoryView {
|
||||
downloaded: boolean;
|
||||
first_seen_at: string;
|
||||
unique_id: string;
|
||||
}
|
||||
|
||||
export interface ChatLinkView {
|
||||
date: string | null;
|
||||
kind: string;
|
||||
message_id: number;
|
||||
url: string;
|
||||
web_site_name: string | null;
|
||||
web_title: string | null;
|
||||
web_url: string | null;
|
||||
}
|
||||
|
||||
export interface DayCount {
|
||||
count: number;
|
||||
day: string;
|
||||
outgoing: number;
|
||||
}
|
||||
|
||||
export interface ReactionView {
|
||||
added_at: string;
|
||||
peer_id: number;
|
||||
reaction: string;
|
||||
removed_at: string | null;
|
||||
}
|
||||
|
||||
export interface CallbackView {
|
||||
data: string | null;
|
||||
label: string | null;
|
||||
position: number;
|
||||
}
|
||||
|
||||
export interface LinkView {
|
||||
kind: string;
|
||||
position: number;
|
||||
url: string;
|
||||
web_description: string | null;
|
||||
web_site_name: string | null;
|
||||
web_title: string | null;
|
||||
web_url: string | null;
|
||||
}
|
||||
|
||||
export interface MessageAt {
|
||||
date: string;
|
||||
message_id: number;
|
||||
}
|
||||
|
||||
export interface Reaction {
|
||||
added_at: string;
|
||||
peer_id: number;
|
||||
|
||||
Reference in new issue
Block a user