feat: 1-to-1 message render + web data-lake backend
This commit is contained in:
1 parent
f0afb7ec5b
commit
75425d1bee
110 files changed
+10199
-54
No files matched your search
@@ -0,0 +1,74 @@
|
||||
import { accounts } from "$lib/stores/accounts.svelte";
|
||||
import { auth } from "$lib/stores/auth.svelte";
|
||||
|
||||
const BASE = import.meta.env.VITE_API_BASE ?? "/api";
|
||||
const RETRY_DELAY = 2500;
|
||||
|
||||
export type AvatarKind = "peer" | "chat";
|
||||
|
||||
const ready = new Map<string, string>();
|
||||
const missing = new Set<string>();
|
||||
const inflight = new Map<string, Promise<string | null>>();
|
||||
|
||||
function cacheKey(account: number, kind: AvatarKind, id: number): string {
|
||||
return `${account}:${kind}:${id}`;
|
||||
}
|
||||
|
||||
function authHeaders(): Record<string, string> {
|
||||
return auth.token ? { Authorization: `Bearer ${auth.token}` } : {};
|
||||
}
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchAvatar(
|
||||
account: number,
|
||||
kind: AvatarKind,
|
||||
id: number,
|
||||
key: string,
|
||||
retry: boolean
|
||||
): Promise<string | null> {
|
||||
const url = `${BASE}/avatars/${kind}/${id}?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;
|
||||
}
|
||||
if (response.status === 409 && retry) {
|
||||
await delay(RETRY_DELAY);
|
||||
return fetchAvatar(account, kind, id, key, false);
|
||||
}
|
||||
missing.add(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
export function loadAvatar(
|
||||
kind: AvatarKind,
|
||||
id: number
|
||||
): Promise<string | null> {
|
||||
const account = accounts.selectedId;
|
||||
if (account === null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
const key = cacheKey(account, kind, id);
|
||||
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 = fetchAvatar(account, kind, id, key, true).finally(() => {
|
||||
inflight.delete(key);
|
||||
});
|
||||
inflight.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import { goto } from "$app/navigation";
|
||||
import { accounts } from "$lib/stores/accounts.svelte";
|
||||
import { auth } from "$lib/stores/auth.svelte";
|
||||
|
||||
const BASE = import.meta.env.VITE_API_BASE ?? "/api";
|
||||
const MAX_LIMIT = 500;
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number;
|
||||
detail: string;
|
||||
|
||||
constructor(status: number, detail: string) {
|
||||
super(detail);
|
||||
this.name = "ApiError";
|
||||
this.status = status;
|
||||
this.detail = detail;
|
||||
}
|
||||
}
|
||||
|
||||
type QueryValue = string | number | boolean | null | undefined;
|
||||
|
||||
interface RequestOptions {
|
||||
account?: boolean;
|
||||
body?: unknown;
|
||||
method?: string;
|
||||
query?: Record<string, QueryValue>;
|
||||
}
|
||||
|
||||
function buildQuery(
|
||||
query: Record<string, QueryValue> | undefined,
|
||||
withAccount: boolean
|
||||
): string {
|
||||
const params = new URLSearchParams();
|
||||
if (withAccount && accounts.selectedId !== null) {
|
||||
params.set("account_id", String(accounts.selectedId));
|
||||
}
|
||||
if (query) {
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (value === null || value === undefined) {
|
||||
continue;
|
||||
}
|
||||
const clamped =
|
||||
key === "limit" && typeof value === "number"
|
||||
? Math.min(value, MAX_LIMIT)
|
||||
: value;
|
||||
params.set(key, String(clamped));
|
||||
}
|
||||
}
|
||||
const text = params.toString();
|
||||
return text ? `?${text}` : "";
|
||||
}
|
||||
|
||||
function authHeaders(): Record<string, string> {
|
||||
return auth.token ? { Authorization: `Bearer ${auth.token}` } : {};
|
||||
}
|
||||
|
||||
async function handleError(response: Response): Promise<never> {
|
||||
if (response.status === 401) {
|
||||
auth.logout();
|
||||
await goto("/login");
|
||||
}
|
||||
let detail = response.statusText;
|
||||
try {
|
||||
const data = await response.json();
|
||||
if (data && typeof data.detail === "string") {
|
||||
detail = data.detail;
|
||||
}
|
||||
} catch {
|
||||
detail = response.statusText;
|
||||
}
|
||||
throw new ApiError(response.status, detail);
|
||||
}
|
||||
|
||||
export async function request<T>(
|
||||
path: string,
|
||||
options: RequestOptions = {}
|
||||
): Promise<T> {
|
||||
const { method = "GET", query, body, account = false } = options;
|
||||
const headers: Record<string, string> = authHeaders();
|
||||
if (body !== undefined) {
|
||||
headers["Content-Type"] = "application/json";
|
||||
}
|
||||
const response = await fetch(`${BASE}${path}${buildQuery(query, account)}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body === undefined ? undefined : JSON.stringify(body),
|
||||
});
|
||||
if (!response.ok) {
|
||||
return handleError(response);
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export type MediaResult =
|
||||
| { state: "ready"; url: string; mime: string | null }
|
||||
| { state: "not-downloaded" }
|
||||
| { state: "missing" };
|
||||
|
||||
export async function requestMedia(mediaId: number): Promise<MediaResult> {
|
||||
const response = await fetch(`${BASE}/media/${mediaId}`, {
|
||||
headers: authHeaders(),
|
||||
});
|
||||
if (response.status === 409) {
|
||||
return { state: "not-downloaded" };
|
||||
}
|
||||
if (response.status === 404) {
|
||||
return { state: "missing" };
|
||||
}
|
||||
if (!response.ok) {
|
||||
return handleError(response);
|
||||
}
|
||||
const blob = await response.blob();
|
||||
return {
|
||||
state: "ready",
|
||||
url: URL.createObjectURL(blob),
|
||||
mime: response.headers.get("Content-Type"),
|
||||
};
|
||||
}
|
||||
|
||||
export async function requestMediaVersion(
|
||||
versionId: number
|
||||
): Promise<MediaResult> {
|
||||
const response = await fetch(`${BASE}/media/version/${versionId}`, {
|
||||
headers: authHeaders(),
|
||||
});
|
||||
if (response.status === 404) {
|
||||
return { state: "missing" };
|
||||
}
|
||||
if (!response.ok) {
|
||||
return handleError(response);
|
||||
}
|
||||
const blob = await response.blob();
|
||||
return {
|
||||
state: "ready",
|
||||
url: URL.createObjectURL(blob),
|
||||
mime: response.headers.get("Content-Type"),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import { request } from "$lib/api/client";
|
||||
import type {
|
||||
Account,
|
||||
Chat,
|
||||
Folder,
|
||||
JobView,
|
||||
MediaVersion,
|
||||
MediaView,
|
||||
MessageVersion,
|
||||
MessageView,
|
||||
PeerView,
|
||||
} from "$lib/api/types";
|
||||
import { accounts } from "$lib/stores/accounts.svelte";
|
||||
|
||||
interface Page {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export function listAccounts(): Promise<Account[]> {
|
||||
return request<Account[]>("/accounts");
|
||||
}
|
||||
|
||||
export function listChats(page: Page = {}): Promise<Chat[]> {
|
||||
return request<Chat[]>("/chats", { account: true, query: { ...page } });
|
||||
}
|
||||
|
||||
export function listFolders(): Promise<Folder[]> {
|
||||
return request<Folder[]>("/folders", { account: true });
|
||||
}
|
||||
|
||||
export function listMessages(
|
||||
chatId: number,
|
||||
options: Page & { include_deleted?: boolean } = {}
|
||||
): Promise<MessageView[]> {
|
||||
return request<MessageView[]>(`/chats/${chatId}/messages`, {
|
||||
account: true,
|
||||
query: { ...options },
|
||||
});
|
||||
}
|
||||
|
||||
export function listMessageVersions(
|
||||
chatId: number,
|
||||
messageId: number
|
||||
): Promise<MessageVersion[]> {
|
||||
return request<MessageVersion[]>(
|
||||
`/chats/${chatId}/messages/${messageId}/versions`,
|
||||
{ account: true }
|
||||
);
|
||||
}
|
||||
|
||||
export function listDeleted(
|
||||
options: Page & { chat_id?: number } = {}
|
||||
): Promise<MessageView[]> {
|
||||
return request<MessageView[]>("/deleted", {
|
||||
account: true,
|
||||
query: { ...options },
|
||||
});
|
||||
}
|
||||
|
||||
export function getPeer(peerId: number): Promise<PeerView> {
|
||||
return request<PeerView>(`/peers/${peerId}`, { account: true });
|
||||
}
|
||||
|
||||
export function getPeers(ids: number[]): Promise<PeerView[]> {
|
||||
if (ids.length === 0) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
return request<PeerView[]>("/peers/batch", {
|
||||
account: true,
|
||||
query: { ids: ids.join(",") },
|
||||
});
|
||||
}
|
||||
|
||||
export function enrichChat(chatId: number): Promise<{ job_id: number }> {
|
||||
return request<{ job_id: number }>(`/chats/${chatId}/enrich`, {
|
||||
method: "POST",
|
||||
body: { account_id: accounts.selectedId },
|
||||
});
|
||||
}
|
||||
|
||||
export function getJob(jobId: number): Promise<JobView> {
|
||||
return request<JobView>(`/jobs/${jobId}`, { account: true });
|
||||
}
|
||||
|
||||
export function getMediaVersions(
|
||||
chatId: number,
|
||||
messageId: number
|
||||
): Promise<MediaVersion[]> {
|
||||
return request<MediaVersion[]>(`/media/versions/${chatId}/${messageId}`, {
|
||||
account: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function getMediaMeta(mediaId: number): Promise<MediaView> {
|
||||
return request<MediaView>(`/media/${mediaId}/meta`);
|
||||
}
|
||||
|
||||
export function getMessageMedia(
|
||||
chatId: number,
|
||||
messageId: number
|
||||
): Promise<MediaView> {
|
||||
return request<MediaView>(`/media/message/${chatId}/${messageId}`, {
|
||||
account: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchMedia(
|
||||
chatId: number,
|
||||
messageId: number
|
||||
): Promise<{ job_id: number }> {
|
||||
return request<{ job_id: number }>("/media/fetch", {
|
||||
method: "POST",
|
||||
body: {
|
||||
account_id: accounts.selectedId,
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import { requestMedia } from "$lib/api/client";
|
||||
import { getMessageMedia } from "$lib/api/endpoints";
|
||||
import type { MediaRef } from "$lib/api/types";
|
||||
import { accounts } from "$lib/stores/accounts.svelte";
|
||||
|
||||
export type InlineMedia =
|
||||
| {
|
||||
state: "ready";
|
||||
mediaId: number;
|
||||
kind: string;
|
||||
mime: string | null;
|
||||
url: string;
|
||||
transcript: string | null;
|
||||
}
|
||||
| { state: "not-downloaded"; mediaId: number; kind: string }
|
||||
| { state: "missing" };
|
||||
|
||||
export interface ViewerItem {
|
||||
downloaded: boolean;
|
||||
kind: string;
|
||||
mediaId: number | null;
|
||||
messageId: number;
|
||||
}
|
||||
|
||||
export function viewerItemsFrom(
|
||||
messageId: number,
|
||||
media: MediaRef[]
|
||||
): ViewerItem[] {
|
||||
if (media.length === 0) {
|
||||
return [{ messageId, mediaId: null, kind: "", downloaded: false }];
|
||||
}
|
||||
return media.map((item) => ({
|
||||
messageId: item.message_id,
|
||||
mediaId: item.id,
|
||||
kind: item.kind,
|
||||
downloaded: item.downloaded,
|
||||
}));
|
||||
}
|
||||
|
||||
export type VisualKind = "image" | "video" | "other";
|
||||
|
||||
const VIDEO_KINDS = new Set(["video", "video_note", "animation", "gif"]);
|
||||
|
||||
export function visualKind(kind: string): VisualKind {
|
||||
if (kind === "photo") {
|
||||
return "image";
|
||||
}
|
||||
if (VIDEO_KINDS.has(kind)) {
|
||||
return "video";
|
||||
}
|
||||
return "other";
|
||||
}
|
||||
|
||||
const ready = new Map<string, InlineMedia>();
|
||||
const inflight = new Map<string, Promise<InlineMedia>>();
|
||||
|
||||
function cacheKey(account: number, chatId: number, messageId: number): string {
|
||||
return `${account}:${chatId}:${messageId}`;
|
||||
}
|
||||
|
||||
async function resolve(
|
||||
chatId: number,
|
||||
messageId: number
|
||||
): Promise<InlineMedia> {
|
||||
let meta: Awaited<ReturnType<typeof getMessageMedia>>;
|
||||
try {
|
||||
meta = await getMessageMedia(chatId, messageId);
|
||||
} catch {
|
||||
return { state: "missing" };
|
||||
}
|
||||
if (!meta.downloaded) {
|
||||
return { state: "not-downloaded", mediaId: meta.id, kind: meta.kind };
|
||||
}
|
||||
const blob = await requestMedia(meta.id);
|
||||
if (blob.state === "ready") {
|
||||
return {
|
||||
state: "ready",
|
||||
mediaId: meta.id,
|
||||
kind: meta.kind,
|
||||
mime: blob.mime,
|
||||
url: blob.url,
|
||||
transcript: meta.extracted_text,
|
||||
};
|
||||
}
|
||||
if (blob.state === "not-downloaded") {
|
||||
return { state: "not-downloaded", mediaId: meta.id, kind: meta.kind };
|
||||
}
|
||||
return { state: "missing" };
|
||||
}
|
||||
|
||||
const byId = new Map<string, InlineMedia>();
|
||||
const byIdInflight = new Map<string, Promise<InlineMedia>>();
|
||||
|
||||
async function resolveById(media: MediaRef): Promise<InlineMedia> {
|
||||
if (media.id === null) {
|
||||
return { state: "missing" };
|
||||
}
|
||||
if (!media.downloaded) {
|
||||
return { state: "not-downloaded", mediaId: media.id, kind: media.kind };
|
||||
}
|
||||
const blob = await requestMedia(media.id);
|
||||
if (blob.state === "ready") {
|
||||
return {
|
||||
state: "ready",
|
||||
mediaId: media.id,
|
||||
kind: media.kind,
|
||||
mime: blob.mime,
|
||||
url: blob.url,
|
||||
transcript: null,
|
||||
};
|
||||
}
|
||||
if (blob.state === "not-downloaded") {
|
||||
return { state: "not-downloaded", mediaId: media.id, kind: media.kind };
|
||||
}
|
||||
return { state: "missing" };
|
||||
}
|
||||
|
||||
export function loadMediaItem(media: MediaRef): Promise<InlineMedia> {
|
||||
const account = accounts.selectedId;
|
||||
if (account === null || media.id === null) {
|
||||
return Promise.resolve<InlineMedia>({ state: "missing" });
|
||||
}
|
||||
const key = `${account}:${media.id}`;
|
||||
const cached = byId.get(key);
|
||||
if (cached) {
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
const existing = byIdInflight.get(key);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const promise = resolveById(media)
|
||||
.then((result) => {
|
||||
if (result.state === "ready") {
|
||||
byId.set(key, result);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.finally(() => {
|
||||
byIdInflight.delete(key);
|
||||
});
|
||||
byIdInflight.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
export function loadInlineMedia(
|
||||
chatId: number,
|
||||
messageId: number
|
||||
): Promise<InlineMedia> {
|
||||
const account = accounts.selectedId;
|
||||
if (account === null) {
|
||||
return Promise.resolve<InlineMedia>({ state: "missing" });
|
||||
}
|
||||
const key = cacheKey(account, chatId, messageId);
|
||||
const cached = ready.get(key);
|
||||
if (cached) {
|
||||
return Promise.resolve(cached);
|
||||
}
|
||||
const existing = inflight.get(key);
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
const promise = resolve(chatId, messageId)
|
||||
.then((result) => {
|
||||
if (result.state === "ready") {
|
||||
ready.set(key, result);
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.finally(() => {
|
||||
inflight.delete(key);
|
||||
});
|
||||
inflight.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
export type ChatKind = "private" | "group";
|
||||
export type PolicyScopeType =
|
||||
| "default_dm"
|
||||
| "default_group"
|
||||
| "default_channel"
|
||||
| "folder"
|
||||
| "chat";
|
||||
export type SearchSource = "text" | "stt";
|
||||
export type JobStatus =
|
||||
| "pending"
|
||||
| "running"
|
||||
| "done"
|
||||
| "failed"
|
||||
| "canceled"
|
||||
| "paused";
|
||||
|
||||
export interface Account {
|
||||
account_id: number;
|
||||
is_active: boolean;
|
||||
label: string | null;
|
||||
phone: string | null;
|
||||
tg_user_id: number | null;
|
||||
}
|
||||
|
||||
export interface Chat {
|
||||
chat_id: number;
|
||||
has_avatar: boolean;
|
||||
is_bot: boolean;
|
||||
is_broadcast: boolean;
|
||||
is_contact: boolean;
|
||||
kind: ChatKind;
|
||||
last_date: string | null;
|
||||
last_sender_id: number | null;
|
||||
last_text: string | null;
|
||||
message_count: number;
|
||||
title: string | null;
|
||||
}
|
||||
|
||||
export interface EntityView {
|
||||
custom_emoji_id: string | null;
|
||||
language: string | null;
|
||||
length: number;
|
||||
offset: number;
|
||||
type: string;
|
||||
url: string | null;
|
||||
}
|
||||
|
||||
export interface ReplyView {
|
||||
media_kind: string | null;
|
||||
message_id: number | null;
|
||||
sender_id: number | null;
|
||||
sender_name: string | null;
|
||||
text: string | null;
|
||||
}
|
||||
|
||||
export interface ForwardView {
|
||||
chat_id: number | null;
|
||||
chat_title: string | null;
|
||||
date: string | null;
|
||||
from_id: number | null;
|
||||
from_name: string | null;
|
||||
kind: "channel" | "hidden" | "user";
|
||||
message_id: number | null;
|
||||
signature: string | null;
|
||||
}
|
||||
|
||||
export interface MediaRef {
|
||||
downloaded: boolean;
|
||||
duration: number | null;
|
||||
file_size: number | null;
|
||||
height: number | null;
|
||||
id: number | null;
|
||||
kind: string;
|
||||
message_id: number;
|
||||
mime: string | null;
|
||||
ttl_seconds: number | null;
|
||||
width: number | null;
|
||||
}
|
||||
|
||||
export interface ReactionCount {
|
||||
chosen: boolean;
|
||||
count: number;
|
||||
custom_emoji_id: string | null;
|
||||
emoji: string | null;
|
||||
}
|
||||
|
||||
export interface InlineButton {
|
||||
data: string | null;
|
||||
kind: "callback" | "other" | "switch" | "url";
|
||||
text: string;
|
||||
url: string | null;
|
||||
}
|
||||
|
||||
export interface WebPageView {
|
||||
description: string | null;
|
||||
display_url: string | null;
|
||||
has_photo: boolean;
|
||||
site_name: string | null;
|
||||
title: string | null;
|
||||
type: string | null;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface PollOption {
|
||||
correct: boolean | null;
|
||||
text: string;
|
||||
vote_percentage: number;
|
||||
voter_count: number;
|
||||
}
|
||||
|
||||
export interface PollView {
|
||||
anonymous: boolean;
|
||||
closed: boolean;
|
||||
multiple: boolean;
|
||||
options: PollOption[];
|
||||
question: string;
|
||||
quiz: boolean;
|
||||
total_voter_count: number;
|
||||
}
|
||||
|
||||
export interface ContactView {
|
||||
first_name: string | null;
|
||||
last_name: string | null;
|
||||
phone_number: string | null;
|
||||
user_id: number | null;
|
||||
}
|
||||
|
||||
export interface LocationView {
|
||||
address: string | null;
|
||||
latitude: number | null;
|
||||
longitude: number | null;
|
||||
title: string | null;
|
||||
}
|
||||
|
||||
export interface ServiceView {
|
||||
duration: number | null;
|
||||
kind: string;
|
||||
member_ids: number[] | null;
|
||||
pinned_message_id: number | null;
|
||||
}
|
||||
|
||||
export interface StickerView {
|
||||
emoji: string | null;
|
||||
height: number | null;
|
||||
is_animated: boolean;
|
||||
is_video: boolean;
|
||||
mime: string | null;
|
||||
set_name: string | null;
|
||||
width: number | null;
|
||||
}
|
||||
|
||||
export interface MessageView {
|
||||
chat_id: number;
|
||||
contact: ContactView | null;
|
||||
date: string;
|
||||
deleted_at: string | null;
|
||||
edited_at: string | null;
|
||||
entities: EntityView[];
|
||||
forward: ForwardView | null;
|
||||
has_media: boolean;
|
||||
inline_buttons: InlineButton[][];
|
||||
is_animated_emoji: boolean;
|
||||
is_self_destruct: boolean;
|
||||
is_sticker: boolean;
|
||||
location: LocationView | null;
|
||||
media: MediaRef[];
|
||||
media_group_id: string | null;
|
||||
message_id: number;
|
||||
poll: PollView | null;
|
||||
quote: string | null;
|
||||
reactions: ReactionCount[];
|
||||
reply: ReplyView | null;
|
||||
sender_id: number | null;
|
||||
service: ServiceView | null;
|
||||
sticker: StickerView | null;
|
||||
text: string | null;
|
||||
via_bot_id: number | null;
|
||||
web_page: WebPageView | null;
|
||||
}
|
||||
|
||||
export interface MessageVersion {
|
||||
edit_date: string | null;
|
||||
observed_at: string;
|
||||
text: string | null;
|
||||
}
|
||||
|
||||
export interface MediaVersion {
|
||||
file_size: number | null;
|
||||
id: number;
|
||||
kind: string;
|
||||
mime: string | null;
|
||||
observed_at: string;
|
||||
storage_key: string;
|
||||
}
|
||||
|
||||
export interface SearchHit {
|
||||
chat_id: number;
|
||||
date: string;
|
||||
extracted_text: string | null;
|
||||
message_id: number;
|
||||
sender_id: number | null;
|
||||
source: SearchSource;
|
||||
text: string | null;
|
||||
}
|
||||
|
||||
export interface MediaView {
|
||||
account_id: number;
|
||||
chat_id: number;
|
||||
created_at: string;
|
||||
downloaded: boolean;
|
||||
extracted_text: string | null;
|
||||
file_size: number | null;
|
||||
id: number;
|
||||
kind: string;
|
||||
message_id: number;
|
||||
mime: string | null;
|
||||
storage_key: string | null;
|
||||
ttl_seconds: number | null;
|
||||
}
|
||||
|
||||
export interface Callback {
|
||||
data: string | null;
|
||||
label: string | null;
|
||||
position: number;
|
||||
}
|
||||
|
||||
export interface Reaction {
|
||||
added_at: string;
|
||||
peer_id: number;
|
||||
reaction: string;
|
||||
removed_at: string | null;
|
||||
}
|
||||
|
||||
export interface LinkPreview {
|
||||
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 PresenceSample {
|
||||
last_online_date: string | null;
|
||||
next_offline_date: string | null;
|
||||
peer_id: number;
|
||||
status: string;
|
||||
ts: string;
|
||||
}
|
||||
|
||||
export interface PresenceHourly {
|
||||
bucket: string;
|
||||
last_seen: string | null;
|
||||
online_samples: number;
|
||||
peer_id: number;
|
||||
samples: number;
|
||||
}
|
||||
|
||||
export interface PeerView {
|
||||
first_name: string | null;
|
||||
has_avatar: boolean;
|
||||
is_deleted_account: boolean;
|
||||
last_name: string | null;
|
||||
peer_id: number;
|
||||
phone: string | null;
|
||||
photo_unique_id: string | null;
|
||||
updated_at: string;
|
||||
username: string | null;
|
||||
}
|
||||
|
||||
export interface PeerHistoryView {
|
||||
first_name: string | null;
|
||||
is_deleted_account: boolean;
|
||||
last_name: string | null;
|
||||
observed_at: string;
|
||||
phone: string | null;
|
||||
photo_unique_id: string | null;
|
||||
username: string | null;
|
||||
}
|
||||
|
||||
export interface StoryView {
|
||||
caption: string | null;
|
||||
date: string | null;
|
||||
deleted: boolean;
|
||||
downloaded: boolean;
|
||||
expire_date: string | null;
|
||||
media_kind: string | null;
|
||||
peer_id: number;
|
||||
pinned: boolean;
|
||||
storage_key: string | null;
|
||||
story_id: number;
|
||||
views: number | null;
|
||||
}
|
||||
|
||||
export interface Annotation {
|
||||
account_id: number;
|
||||
chat_id: number;
|
||||
created_at: string;
|
||||
id: number;
|
||||
message_id: number;
|
||||
text: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface CaptureToggles {
|
||||
backfill: boolean;
|
||||
media: boolean;
|
||||
messages: boolean;
|
||||
presence: boolean;
|
||||
profile_history: boolean;
|
||||
reactions: boolean;
|
||||
self_destruct_media: boolean;
|
||||
stories: boolean;
|
||||
stt: boolean;
|
||||
track_edits_deletes: boolean;
|
||||
}
|
||||
|
||||
export interface PolicyRecord extends CaptureToggles {
|
||||
account_id: number | null;
|
||||
id: number;
|
||||
scope_id: number | null;
|
||||
scope_type: PolicyScopeType;
|
||||
}
|
||||
|
||||
export interface Folder {
|
||||
bots: boolean;
|
||||
broadcasts: boolean;
|
||||
contacts: boolean;
|
||||
exclude_ids: number[];
|
||||
folder_id: number;
|
||||
groups: boolean;
|
||||
include_ids: number[];
|
||||
is_chatlist: boolean;
|
||||
non_contacts: boolean;
|
||||
order_index: number;
|
||||
pinned_ids: number[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface Watch {
|
||||
account_id: number;
|
||||
created_at: string;
|
||||
enabled: boolean;
|
||||
id: number;
|
||||
kind: string;
|
||||
params: Record<string, unknown>;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface Alert {
|
||||
account_id: number;
|
||||
created_at: string;
|
||||
id: number;
|
||||
payload: Record<string, unknown>;
|
||||
seen: boolean;
|
||||
ts: string;
|
||||
watch_id: number;
|
||||
}
|
||||
|
||||
export interface JobView {
|
||||
account_id: number;
|
||||
attempts: number;
|
||||
created_at: string;
|
||||
cursor: Record<string, unknown> | null;
|
||||
error: string | null;
|
||||
finished_at: string | null;
|
||||
flood_waits: number;
|
||||
id: number;
|
||||
kind: string;
|
||||
params: Record<string, unknown>;
|
||||
progress: Record<string, unknown>;
|
||||
started_at: string | null;
|
||||
status: JobStatus;
|
||||
}
|
||||
Reference in new issue
Block a user