108 lines
3.7 KiB
TypeScript
108 lines
3.7 KiB
TypeScript
|
|
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<string, unknown> | null {
|
||
|
|
return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : 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<InvitePreview> {
|
||
|
|
const payload = await request<unknown>(
|
||
|
|
`/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<string> {
|
||
|
|
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;
|
||
|
|
}
|