> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xneon.org/llms.txt
> Use this file to discover all available pages before exploring further.

# @xnlc/skins: домен скинов Minecraft

> Документация модуля @xnlc/skins: каталог Laby, избранные скины, локальные текстуры, активный скин на аккаунте и кэш.

Модуль `@xnlc/skins` реализует предметную область скинов Minecraft без зависимости от Electron, SQLite или файловой системы. Он работает с каталогом Laby.net, локальной библиотекой «Избранное», активным скином на аккаунте и кэшем текстур. Хранилище и доступ к аккаунтам описаны через порты, которые реализует приложение.

<CardGroup>
  <Card title="Версия 1.0.6" icon="brand-npm" />

  <Card title="Лицензия: GPL-3.0 (как и лаунчер)" icon="license" />

  <Card title="67 тестов" icon="test-pipe" />
</CardGroup>

## Установка

<CodeGroup>
  ```bash npm theme={null}
  npm install @xnlc/skins --registry https://git.xneon.org/api/packages/MAINER4IK/npm/
  ```

  ```bash pnpm theme={null}
  pnpm add @xnlc/skins --registry https://git.xneon.org/api/packages/MAINER4IK/npm/
  ```
</CodeGroup>

## Возможности

* Каталог Laby.net: страницы, теги, игроки, «похожие» скины
* Локальная библиотека «Избранное» с импортом из каталога
* Активный скин на аккаунте: надевание, сброс, плащ, профиль
* Кэш текстур в памяти (TTL) и на диске (последнее удачное значение)
* Валидация текстур, нормализация модели slim/classic
* Поиск по тегам с серверной и локальной пагинацией

## Архитектура

```text theme={null}
createSkinSystem({ library, accounts, disk?, locale?, client? })
  ├── catalog: LabyCatalog      ← каталог Laby
  ├── library: SkinLibrary      ← «Избранное»
  ├── minecraft: MinecraftSkins ← активный скин
  └── reset(): void             ← сброс кэша и пулов
```

Все внешние зависимости (Electron IPC, база данных, файловая система) описаны портами и передаются при создании системы.

## Создание системы

```ts theme={null}
import { createSkinSystem } from "@xnlc/skins";

const skinSystem = createSkinSystem({
  library: myLibraryStore,  // SkinLibraryStore
  accounts: myAccountPort,  // SkinAccountPort
  disk: myDiskCache,        // SkinDiskCache (опционально)
  locale: "ru",             // язык подписей тегов
});

const { catalog, library, minecraft } = skinSystem;
```

## LabyCatalog

Каталог скинов Laby.net. Поддерживает три режима выдачи:

1. **Общая лента**: чистая серверная пагинация без фильтров
2. **Один тег без текста**: серверная пагинация по `/api/v3/tag/{id}` (доступны все страницы)
3. **Несколько тегов и/или текст**: локальная фильтрация по пулу (до 5 страниц по 100 элементов)

### Страница каталога

```ts theme={null}
const page = await catalog.page({
  page: 0,              // номер страницы
  size: 25,             // элементов на странице (6..100)
  order: "most_used",   // сортировка
  tags: ["Girl"],       // фильтр по тегам
  query: null,          // текстовый поиск
});
```

<ResponseField name="LabyCatalogPage" type="object">
  <ResponseField name="items" type="LabySkin[]">
    Скины на странице
  </ResponseField>

  <ResponseField name="page" type="number">
    Номер страницы
  </ResponseField>

  <ResponseField name="hasMore" type="boolean">
    Есть ли следующая страница
  </ResponseField>

  <ResponseField name="endOfFeed" type="boolean">
    Конец выдачи
  </ResponseField>

  <ResponseField name="stale" type="boolean">
    Данные из дискового кэша
  </ResponseField>

  <ResponseField name="filteredLocally" type="boolean">
    Фильтрация выполнена локально
  </ResponseField>

  <ResponseField name="error" type="LabyApiError | null">
    Ошибка API
  </ResponseField>
</ResponseField>

### Теги

```ts theme={null}
const tags = await catalog.tags("ru");
// LabyTag[]: id, name, useCount, previewUrl
```

### Профиль игрока

```ts theme={null}
const player = await catalog.player("Notch");
// LabyPlayer | null: uuid, username, headUrl, skins[], capesCount
```

Ник должен быть точным: Laby не поддерживает поиск по частичному совпадению. Профиль кэшируется на 15 минут.

### Текстура по хэшу

```ts theme={null}
const texture = await catalog.texture("a1b2c3d4...");
// Uint8Array | null: PNG-текстура скина
```

Текстуры кэшируются бессрочно: хэш гарантирует неизменность.

### Похожие скины

```ts theme={null}
const similar = await catalog.similar({
  hash: "a1b2c3d4...",
  tags: ["Girl"],
  slim: true,
});
// LabySkin[]: подборка из загруженных пулов
```

Если пулы пусты, функция использует популярные скины из общей ленты.

## SkinLibrary

Локальная библиотека «Избранное».

```ts theme={null}
import { SkinLibrary } from "@xnlc/skins";

// Импорт из каталога
await library.importFromCatalog({ hash: "a1b2c3d4", name: "MySkin", variant: "slim" });

// Удалить
await library.remove(skinId);

// Получить список
const skins = await library.list();
```

Хранение реализуется приложением через порт `SkinLibraryStore`.

## MinecraftSkins

Управление активным скином на Minecraft-аккаунте.

```ts theme={null}
import { MinecraftSkins } from "@xnlc/skins";

const mcSkins = new MinecraftSkins(accountPort);

// Надеть скин
await mcSkins.equip(skin, accountUuid);

// Сбросить на стандартный
await mcSkins.reset(accountUuid);

// Установить плащ
await mcSkins.setCape(capeId, accountUuid);

// Получить профиль
const profile = await mcSkins.getProfile(accountUuid);
```

## Кэш

`SkinCache` работает в два уровня:

* **Память**: BoundedMap с TTL (до 256 записей)
* **Диск**: последнее удачное значение для офлайн-режима

```ts theme={null}
import { SkinCache, BoundedMap } from "@xnlc/skins";

const cache = new SkinCache(diskCache);
await cache.binary("key", () => fetchTexture());
await cache.json("key", { ttl: 300_000 }, () => fetchData());
```

## Валидация

```ts theme={null}
import {
  isValidTexture,      // проверить PNG-данные
  normalizeVariant,    // "slim" | "classic"
  sanitizeSkinName,    // очистить имя скина
  MAX_TEXTURE_BYTES,   // максимальный размер текстуры
} from "@xnlc/skins";
```

## URL-хелперы

Модуль экспортирует константы и функции для работы с адресами Laby API:

```ts theme={null}
import {
  LABY_API_BASE,           // https://laby.net/api/v3
  LABY_TEXTURE_BASE,       // texture.laby.net/{hash}.png
  labyUniqueIdUrl,         // /user/{nick}/uniqueId
  labyUserTexturesUrl,     // /user/{uuid}/textures
  labyTagSkinsUrl,         // /tag/{id}
  labyTagsUrl,             // /tags
  labyHeadUrl,             // аватар по UUID
  labyRenderUrl,           // рендер скина
  isLabyHash,              // валидация хэша
  isLabyUsername,          // валидация ника
  sanitizeLabyUsername,    // очистка ника
} from "@xnlc/skins";
```

## Маппинг

Функции для преобразования ответов API Laby в модель лаунчера:

```ts theme={null}
import {
  mapLabySkin,
  mapLabyTag,
  mapLabyTagSkin,
  mapLabyPlayerSkins,
  parseLabyTags,
} from "@xnlc/skins";
```

## Скоринг и фильтрация

```ts theme={null}
import {
  dedupeSkins,           // убрать дубликаты
  labyFilterSkins,       // фильтр по тегам и тексту
  labySimilarSkins,      // подборка похожих
  labySimilarityScore,   // числовая оценка схожести
  formatUseCount,        // форматирование метрики использования
} from "@xnlc/skins";
```

## Идентификаторы скинов

Laby-скины адресуются по `sourceId` вида `laby:<hash>`. Конвертация:

```ts theme={null}
import { labySourceId, labyHashFromSourceId, LABY_SOURCE_PREFIX } from "@xnlc/skins";

const sourceId = labySourceId("a1b2c3d4");        // "laby:a1b2c3d4"
const hash = labyHashFromSourceId("laby:a1b2..."); // "a1b2..."
```

## Порты

Порты определяют контракт между доменом скинов и приложением:

```ts theme={null}
import type {
  SkinLibraryStore,    // CRUD «Избранного»
  SkinAccountPort,     // доступ к аккаунтам и токенам
  SkinDiskCache,       // дисковый кэш
  ResolvedSkinAccount, // разрешенный аккаунт со скином
} from "@xnlc/skins";
```

В Xneon Launcher порты реализованы в `electron/main/skins/ports.ts`, а IPC-канал в `electron/main/skins/index.ts`.

## Сброс состояния

```ts theme={null}
skinSystem.reset(); // очистить кэш и пулы каталога
```

## Типы

Реэкспортируются из `@xnlc/types`:

* `LabyCatalogPage`, `LabySkin`, `LabyTag`, `LabyPlayer`, `LabyPlayerSkin`
* `LibrarySkin`, `MinecraftSkin`, `MinecraftCape`, `McProfile`
* `LabyApiError`, `LabyImportResult`, `LabyOrder`


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.