import { request } from './client'; /** * Приглашения на сервер (AGENT.md §7.9). * * Присоединиться можно только по приглашению, поэтому клиент умеет: * разобрать код из ссылки `https://<домен>/invite/<код>`, показать * предпросмотр (`GET /invites/{code}`) и принять его (`POST /invites/{code}`). */ export interface InviteGuildPreview { id: string; name: string; icon_file_id?: string; description?: string; member_count: number; is_member: boolean; } export interface InvitePreview { code: string; guild_id: string; guild: InviteGuildPreview; } export const inviteQueryKey = (code: string) => ['invites', code] as const; /** * Достаёт код приглашения из ссылки или «сырого» кода. * Возвращает `null`, если строка не похожа ни на то, ни на другое. */ export function parseInviteCode(input: string): string | null { const trimmed = input.trim(); if (trimmed === '') { return null; } const fromUrl = (value: string): string | null => { try { const url = new URL(value); const segments = url.pathname.split('/').filter((segment) => segment !== ''); const index = segments.indexOf('invite'); const code = index === -1 ? undefined : segments[index + 1]; return code === undefined || code === '' ? null : code; } catch { return null; } }; if (/^https?:\/\//iu.test(trimmed)) { return fromUrl(trimmed); } // Сырой код: буквы и цифры без разделителей (алфавит сервера — a-z0-9). return /^[0-9a-z]+$/iu.test(trimmed) ? trimmed : null; } function asRecord(value: unknown): Record | null { return typeof value === 'object' && value !== null ? (value as Record) : null; } function asString(value: unknown): string | undefined { return typeof value === 'string' && value !== '' ? value : undefined; } /** `GET /invites/{code}` — предпросмотр приглашения. */ export async function fetchInvite(code: string, signal?: AbortSignal): Promise { const payload = await request( `/invites/${encodeURIComponent(code)}`, signal === undefined ? {} : { signal }, ); const record = asRecord(payload); const invite = asRecord(record?.['invite']); const guild = asRecord(record?.['guild']); const guildId = asString(guild?.['id']); if (guild === null || guildId === undefined) { throw new Error('malformed invite payload'); } const preview: InvitePreview = { code: asString(invite?.['code']) ?? code, guild_id: guildId, guild: { id: guildId, name: asString(guild['name']) ?? guildId, member_count: typeof guild['member_count'] === 'number' ? guild['member_count'] : 0, is_member: guild['is_member'] === true, }, }; const icon = asString(guild['icon_file_id']); const description = asString(guild['description']); if (icon !== undefined) { preview.guild.icon_file_id = icon; } if (description !== undefined) { preview.guild.description = description; } return preview; } /** `POST /invites/{code}` — вступить на сервер; сервер отвечает его id. */ export async function acceptInvite(code: string): Promise { const payload = await request<{ guild_id?: unknown }>(`/invites/${encodeURIComponent(code)}`, { method: 'POST', }); const guildId = asString(payload.guild_id); if (guildId === undefined) { throw new Error('malformed invite accept payload'); } return guildId; }