Admin console additions (Layer 228): - Give Gifts: dedicated tab with sorted Lottie/TGS gift picker + inline form; grant any catalog gift to a user/channel from 777000 (no charge) - Upgraded/collectible delivery: mint a unique gift with admin-selected model/pattern/backdrop and custom number, or random/auto (DB FK + UNIQUE(gift_id,num) enforce invariants) - SCAM/FAKE flags for users/channels (migration 0136) with configurable profile warning (TELESRV_SCAM_WARNING/TELESRV_FAKE_WARNING) - Support toggle, force channel settings incl. gigagroup (migration 0137), username management, cosmetic color/emoji-status - Emoji admin tab (custom emoji list + document IDs + Lottie/TGS preview) - Bot management; soft UI / dark theme Wired through Router -> admin.Service -> adminapi -> BFF -> React panel (en/zh/ru).
89 lines
4.4 KiB
TypeScript
89 lines
4.4 KiB
TypeScript
import type {
|
|
AccountDetail,
|
|
AccountListResponse,
|
|
BotDetail,
|
|
BotListResponse,
|
|
ChannelDetail,
|
|
EmojiListResponse,
|
|
ChannelListResponse,
|
|
CommandResult,
|
|
GroupMessageDetail,
|
|
GroupMessageListResponse,
|
|
MessageDetail,
|
|
MessageListResponse,
|
|
OfficialStarGiftListResponse,
|
|
StarGiftCollectiblePreview,
|
|
StarGiftListResponse
|
|
} from "./types";
|
|
|
|
export class APIError extends Error {
|
|
status: number;
|
|
|
|
constructor(status: number, message: string) {
|
|
super(message);
|
|
this.status = status;
|
|
}
|
|
}
|
|
|
|
async function request<T>(url: string, init: RequestInit = {}): Promise<T> {
|
|
const isForm = typeof FormData !== "undefined" && init.body instanceof FormData;
|
|
const response = await fetch(url, {
|
|
credentials: "same-origin",
|
|
headers: isForm ? init.headers : { "Content-Type": "application/json", ...(init.headers ?? {}) },
|
|
...init
|
|
});
|
|
const text = await response.text();
|
|
const data = text ? JSON.parse(text) : null;
|
|
if (!response.ok) {
|
|
const message = data?.error || data?.Error || data?.message || response.statusText;
|
|
throw new APIError(response.status, message);
|
|
}
|
|
return data as T;
|
|
}
|
|
|
|
export function errorMessage(error: unknown): string {
|
|
if (error instanceof Error) {
|
|
return error.message;
|
|
}
|
|
return String(error);
|
|
}
|
|
|
|
export const api = {
|
|
session: () => request<{ actor: string }>("/api/session"),
|
|
login: (secret: string) => request<{ actor: string }>("/api/login", {
|
|
method: "POST",
|
|
body: JSON.stringify({ secret })
|
|
}),
|
|
logout: () => request<{ ok: boolean }>("/api/logout", { method: "POST", body: "{}" }),
|
|
accounts: (params: URLSearchParams) => request<AccountListResponse>(`/api/accounts?${params.toString()}`),
|
|
account: (id: number) => request<AccountDetail>(`/api/accounts/${id}`),
|
|
channels: (params: URLSearchParams) => request<ChannelListResponse>(`/api/channels?${params.toString()}`),
|
|
channel: (id: number) => request<ChannelDetail>(`/api/channels/${id}`),
|
|
bots: (params: URLSearchParams) => request<BotListResponse>(`/api/bots?${params.toString()}`),
|
|
bot: (id: number) => request<BotDetail>(`/api/bots/${id}`),
|
|
emoji: (params: URLSearchParams) => request<EmojiListResponse>(`/api/emoji?${params.toString()}`),
|
|
emojiAnimation: (documentID: string) => request<Record<string, unknown>>(`/api/emoji/${encodeURIComponent(documentID)}/animation`),
|
|
messages: (params: URLSearchParams) => request<MessageListResponse>(`/api/messages?${params.toString()}`),
|
|
message: (ownerUserID: number, msgID: number) => {
|
|
const params = new URLSearchParams({ owner_user_id: String(ownerUserID), msg_id: String(msgID) });
|
|
return request<MessageDetail>(`/api/messages/detail?${params.toString()}`);
|
|
},
|
|
groupMessages: (params: URLSearchParams) => request<GroupMessageListResponse>(`/api/messages/groups?${params.toString()}`),
|
|
groupMessage: (channelID: number, msgID: number) => {
|
|
const params = new URLSearchParams({ channel_id: String(channelID), msg_id: String(msgID) });
|
|
return request<GroupMessageDetail>(`/api/messages/groups/detail?${params.toString()}`);
|
|
},
|
|
gifts: () => request<StarGiftListResponse>("/api/gifts"),
|
|
officialGifts: () => request<OfficialStarGiftListResponse>("/api/official-gifts"),
|
|
officialGiftAnimation: (id: string) => request<Record<string, unknown>>(`/api/official-gifts/${encodeURIComponent(id)}/animation`),
|
|
giftAnimation: (id: string) => request<Record<string, unknown>>(`/api/gifts/${encodeURIComponent(id)}/animation`),
|
|
giftCollectibles: (id: string) => request<StarGiftCollectiblePreview>(`/api/gifts/${encodeURIComponent(id)}/collectibles`),
|
|
giftCollectibleAnimation: (giftID: string, kind: "model" | "pattern", attributeID: string) => request<Record<string, unknown>>(`/api/gifts/${encodeURIComponent(giftID)}/collectibles/${kind}/${encodeURIComponent(attributeID)}/animation`),
|
|
importGift: (form: FormData) => request<CommandResult>("/api/actions/import-gift", { method: "POST", body: form }),
|
|
importOfficialGift: (payload: Record<string, unknown>) => request<CommandResult>("/api/actions/import-official-gift", { method: "POST", body: JSON.stringify(payload) }),
|
|
publishGiftCollectibles: (giftID: string, form: FormData) => request<CommandResult>(`/api/actions/publish-gift-collectibles?gift_id=${encodeURIComponent(giftID)}`, { method: "POST", body: form }),
|
|
action: (path: string, payload: Record<string, unknown>) => request<CommandResult>(path, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload)
|
|
})
|
|
};
|