@magicwenli/league-fan-assets
Version:
Typed League of Legends asset loaders (icons, emotes, wards, loot, champions, skins) for league-fan
715 lines (697 loc) • 20.9 kB
JavaScript
;
// src/sources/cdragon.ts
var CDRAGON = "https://raw.communitydragon.org";
var TENCENT_PROFILE_ICON_CDN = "https://dlied1.qq.com/lolapp/lol/summoner/profileicon/";
var CATEGORY_CDRAGON_FILE = {
"summoner-icons": "summoner-icons.json",
"summoner-icon-sets": "summoner-icon-sets.json",
"summoner-emotes": "summoner-emotes.json",
"ward-skins": "ward-skins.json",
"ward-skin-sets": "ward-skin-sets.json",
loot: "loot.json",
champions: "champion-summary.json",
skinlines: "skinlines.json",
universes: "universes.json",
skins: "skins.json"
};
function dataRoot(opts = {}) {
const patch = opts.patch ?? "latest";
const lang = opts.lang ?? "default";
return `${CDRAGON}/${patch}/plugins/rcp-be-lol-game-data/global/${lang}`;
}
function cdragonDataUrl(opts) {
const file = CATEGORY_CDRAGON_FILE[opts.category];
if (!file) {
throw new Error(
`Category "${opts.category}" is not loaded from CDragon v1 JSON`
);
}
return `${dataRoot({ patch: opts.patch, lang: opts.lang })}/v1/${file}`;
}
function defaultPatchForCategory(category) {
switch (category) {
case "champions":
case "skinlines":
case "universes":
case "skins":
case "added":
return "pbe";
default:
return "latest";
}
}
// src/sources/release.ts
var DEFAULT_RELEASE_BASE = "https://github.com/league-fan/league-fan-assets/releases/latest/download";
function releaseBaseUrl(opts) {
if (opts?.baseUrl) {
return opts.baseUrl.replace(/\/$/, "");
}
if (opts?.version) {
const ver = opts.version.startsWith("data-v") ? opts.version : `data-v${opts.version}`;
return `https://github.com/league-fan/league-fan-assets/releases/download/${ver}`;
}
return DEFAULT_RELEASE_BASE;
}
function releaseAssetName(relPath) {
return relPath.replace(/\//g, "__");
}
function releaseDataUrl(opts) {
const base = releaseBaseUrl({
baseUrl: opts.baseUrl,
version: opts.version
});
if (opts.category === "version") {
return `${base}/${releaseAssetName("version.json")}`;
}
if (opts.category === "added") {
return `${base}/${releaseAssetName("default/added.json")}`;
}
const lang = opts.lang ?? "default";
return `${base}/${releaseAssetName(`${lang}/${opts.category}.json`)}`;
}
function releaseManifestUrl(opts) {
return `${releaseBaseUrl(opts)}/${releaseAssetName("manifest.json")}`;
}
// src/loaders/http.ts
var FetchError = class extends Error {
status;
url;
constructor(url, status, statusText) {
super(`HTTP ${status} ${statusText} for ${url}`);
this.name = "FetchError";
this.status = status;
this.url = url;
}
};
async function fetchJson(url, opts) {
const fetchImpl = opts?.fetch ?? globalThis.fetch;
if (!fetchImpl) {
throw new Error("No fetch implementation available");
}
const res = await fetchImpl(url);
if (!res.ok) {
throw new FetchError(url, res.status, res.statusText);
}
return await res.json();
}
// src/sources/ddragon.ts
var DDRAGON_REALM_TENCENT = "https://ddragon.leagueoflegends.com/realms/tencent.json";
var DDRAGON_REALM_NA = "https://ddragon.leagueoflegends.com/realms/na.json";
async function getGameVersion(opts) {
const info = await getVersionInfo(opts);
return info.v;
}
async function getVersionInfo(opts) {
const url = opts?.realmUrl ?? DDRAGON_REALM_TENCENT;
return fetchJson(url, { fetch: opts?.fetch });
}
// src/transforms/paths.ts
var GAME_DATA_PREFIX = "/lol-game-data/assets";
function assetUrl(path, opts = {}) {
if (path == null || path === "") return "";
if (/^https?:\/\//i.test(path)) return path;
const patch = opts.patch ?? "latest";
if (opts.category === "summoner-icons" && (opts.lang === "zh_cn" || opts.lang === "zh_CN")) {
const m = path.match(
/\/lol-game-data\/assets\/v1\/profile-icons\/([0-9]+\.(?:jpg|png))/i
);
if (m?.[1]) {
return `${TENCENT_PROFILE_ICON_CDN}${m[1].toLowerCase()}`;
}
}
if (path.startsWith(GAME_DATA_PREFIX)) {
const rest = path.slice(GAME_DATA_PREFIX.length).replace(/^\/+/, "");
return `${dataRoot({ patch, lang: "default" })}/${rest.toLowerCase()}`;
}
if (path.startsWith("/")) {
return `${dataRoot({ patch, lang: "default" })}${path.toLowerCase()}`;
}
return path;
}
function rewritePathsDeep(value, opts = {}) {
if (value == null) return value;
if (typeof value === "string") {
if (value.includes(GAME_DATA_PREFIX) || value.startsWith("/lol-game-data")) {
return assetUrl(value, opts);
}
return value;
}
if (Array.isArray(value)) {
return value.map((v) => rewritePathsDeep(v, opts));
}
if (typeof value === "object") {
const out = {};
for (const [k, v] of Object.entries(value)) {
out[k] = rewritePathsDeep(v, opts);
}
return out;
}
return value;
}
// src/transforms/loot.ts
var CHEST_ID = /CHEST_[0-9]{1,4}$/;
function filterLootChests(raw, opts = {}) {
const patch = opts.patch ?? "latest";
let items = [];
if (Array.isArray(raw)) {
items = raw;
} else if (raw && typeof raw === "object" && "LootItems" in raw) {
items = raw.LootItems ?? [];
}
const filtered = items.filter((el) => {
const id = String(el.id ?? "");
return CHEST_ID.test(id);
});
const mapped = filtered.map((el) => {
const image = assetUrl(String(el.image ?? el.imagePath ?? ""), {
patch,
lang: opts.lang,
category: "loot"
});
return {
...el,
id: String(el.id),
name: String(el.name ?? ""),
description: String(el.description ?? ""),
image
};
});
return mapped.sort(
(a, b) => (a.mappedStoreId ?? 0) - (b.mappedStoreId ?? 0)
);
}
// src/transforms/cosmetics.ts
function flattenDescription(item) {
if (item.descriptions?.[0]?.description) {
return item.descriptions[0].description;
}
return item.description ?? "";
}
function transformSummonerIcons(raw, opts = {}) {
const patch = opts.patch ?? "latest";
const lang = opts.lang ?? "default";
const list = raw.map((item) => {
const imagePath = assetUrl(item.imagePath ?? "", {
patch,
lang,
category: "summoner-icons"
});
return {
...item,
id: Number(item.id),
title: item.title ?? "",
imagePath,
description: flattenDescription(item)
};
});
return list.filter((i) => i.imagePath).sort((a, b) => b.id - a.id);
}
function transformSummonerIconSets(raw, opts = {}) {
const data = Array.isArray(raw) ? raw : [];
return rewritePathsDeep(data, {
patch: opts.patch ?? "latest",
lang: opts.lang,
category: "summoner-icon-sets"
});
}
function transformSummonerEmotes(raw, opts = {}) {
const patch = opts.patch ?? "latest";
return raw.map((item) => ({
...item,
id: Number(item.id),
name: item.name ?? "",
description: item.description ?? "",
inventoryIcon: assetUrl(item.inventoryIcon ?? "", {
patch,
lang: opts.lang,
category: "summoner-emotes"
})
}));
}
function transformWardSkins(raw, opts = {}) {
const patch = opts.patch ?? "latest";
return raw.map((item) => ({
...item,
id: Number(item.id),
name: item.name ?? "",
description: item.description ?? "",
wardImagePath: assetUrl(item.wardImagePath ?? "", {
patch,
lang: opts.lang,
category: "ward-skins"
}),
wardShadowImagePath: assetUrl(item.wardShadowImagePath ?? "", {
patch,
lang: opts.lang,
category: "ward-skins"
})
}));
}
function transformWardSkinSets(raw, opts = {}) {
const data = Array.isArray(raw) ? raw : [];
return rewritePathsDeep(data, {
patch: opts.patch ?? "latest",
lang: opts.lang,
category: "ward-skin-sets"
});
}
function transformLoot(raw, opts = {}) {
return filterLootChests(raw, opts);
}
// src/helpers/substitute.ts
var ALIAS_SUBSTITUTIONS = {
monkeyking: "wukong"
};
function substitute(value, sets = ALIAS_SUBSTITUTIONS) {
return sets[value] ?? value;
}
// src/transforms/champions.ts
function transformChampions(raw, opts = {}) {
const patch = opts.patch ?? "pbe";
return raw.filter((d) => d.id !== -1 && d.id != null).map((a) => {
const alias = a.alias ?? "";
return {
id: Number(a.id),
name: a.name ?? "",
alias,
squarePortraitPath: assetUrl(a.squarePortraitPath ?? "", {
patch,
lang: opts.lang,
category: "champions"
}),
roles: a.roles ?? [],
key: substitute(alias.toLowerCase())
};
}).sort((a, b) => a.name > b.name ? 1 : -1);
}
// src/transforms/skins.ts
var PATH_KEYS = [
"splashPath",
"uncenteredSplashPath",
"tilePath",
"loadScreenPath",
"loadScreenVintagePath",
"chromaPath",
"splashVideoPath",
"collectionSplashVideoPath",
"collectionCardHoverVideoPath",
"rarityGemPath"
];
function absolutizeSkinPaths(skin, opts) {
const next = { ...skin };
for (const key of PATH_KEYS) {
const val = next[key];
if (typeof val === "string" && val) {
next[key] = assetUrl(val, {
patch: opts.patch,
lang: opts.lang,
category: "skins"
});
}
}
if (next.chromas?.length) {
next.chromas = next.chromas.map((c) => ({
...c,
chromaPath: assetUrl(c.chromaPath, {
patch: opts.patch,
lang: opts.lang,
category: "skins"
})
}));
}
if (next.emblems?.length) {
next.emblems = next.emblems.map((e) => ({
...e,
emblemPath: {
large: assetUrl(e.emblemPath?.large, {
patch: opts.patch,
lang: opts.lang,
category: "skins"
}),
small: assetUrl(e.emblemPath?.small, {
patch: opts.patch,
lang: opts.lang,
category: "skins"
})
}
}));
}
if (next.skinAugments) {
next.skinAugments = rewritePathsDeep(next.skinAugments, {
patch: opts.patch,
lang: opts.lang,
category: "skins"
});
}
return next;
}
function transformSkins(raw, opts = {}) {
const patch = opts.patch ?? "pbe";
const originalPrefix = opts.originalPrefix ?? true;
const data = { ...raw };
for (const id of Object.keys(data)) {
const skin = data[id];
if (!skin) continue;
if (skin.isBase && originalPrefix && !skin.name.startsWith("Original ")) {
skin.name = "Original " + skin.name;
}
if (skin.questSkinInfo?.tiers?.length) {
const base = { ...skin };
delete base.questSkinInfo;
for (const tier of skin.questSkinInfo.tiers) {
const merged = { ...base, ...tier };
data[String(merged.id)] = absolutizeSkinPaths(merged, {
patch,
lang: opts.lang
});
}
}
data[id] = absolutizeSkinPaths(skin, { patch, lang: opts.lang });
}
return data;
}
function transformSkinlines(raw, _opts = {}) {
return raw.filter((d) => d.id !== 0).map((d) => ({
id: d.id,
name: d.name ?? "",
description: d.description ?? ""
})).sort((a, b) => a.name > b.name ? 1 : -1);
}
function transformUniverses(raw, opts = {}) {
const patch = opts.patch ?? "pbe";
return raw.filter((d) => d.id !== 0 && d.id != null).map((d) => ({
id: Number(d.id),
name: d.name ?? "",
description: d.description ?? "",
imagePath: assetUrl(d.imagePath ?? "", {
patch,
lang: opts.lang,
category: "universes"
}),
skinSets: d.skinSets ?? []
})).sort((a, b) => a.name > b.name ? 1 : -1);
}
// src/transforms/added.ts
function computeAdded(opts) {
const oldSkinIds = new Set(Object.keys(opts.previous.skins));
const oldChampionIds = new Set(opts.previous.champions.map((c) => c.id));
const oldSkinlineIds = new Set(opts.previous.skinlines.map((l) => l.id));
const oldUniverseIds = new Set(opts.previous.universes.map((u) => u.id));
return {
skins: Object.keys(opts.current.skins).filter((i) => !oldSkinIds.has(i)),
champions: opts.current.champions.map((c) => c.id).filter((i) => !oldChampionIds.has(i)),
skinlines: opts.current.skinlines.map((l) => l.id).filter((i) => !oldSkinlineIds.has(i)),
universes: opts.current.universes.map((u) => u.id).filter((i) => !oldUniverseIds.has(i))
};
}
// src/loaders/category.ts
function resolvePatch(category, source, explicit) {
if (explicit) return explicit;
if (source.kind === "cdragon") {
return source.patch ?? defaultPatchForCategory(category);
}
return defaultPatchForCategory(category);
}
async function loadCategory(category, opts = {}) {
const lang = opts.lang ?? "default";
const source = opts.source ?? { kind: "cdragon" };
const patch = resolvePatch(category, source, opts.patch);
const fetchOpt = { fetch: opts.fetch };
if (category === "version") {
if (source.kind === "release") {
const url2 = releaseDataUrl({
category: "version",
baseUrl: source.baseUrl,
version: source.version
});
return await fetchJson(url2, fetchOpt);
}
return await getVersionInfo(fetchOpt);
}
if (category === "added") {
if (source.kind === "release") {
const url2 = releaseDataUrl({
category: "added",
baseUrl: source.baseUrl,
version: source.version
});
return await fetchJson(url2, fetchOpt);
}
throw new Error(
'Category "added" requires source: { kind: "release" } or precomputed data from build-data'
);
}
let raw;
if (source.kind === "release") {
const url2 = releaseDataUrl({
category,
lang,
baseUrl: source.baseUrl,
version: source.version
});
raw = await fetchJson(url2, fetchOpt);
return raw;
}
const url = cdragonDataUrl({ category, lang, patch });
raw = await fetchJson(url, fetchOpt);
const tOpts = { lang, patch };
switch (category) {
case "summoner-icons":
return transformSummonerIcons(raw, tOpts);
case "summoner-icon-sets":
return transformSummonerIconSets(raw, tOpts);
case "summoner-emotes":
return transformSummonerEmotes(raw, tOpts);
case "ward-skins":
return transformWardSkins(raw, tOpts);
case "ward-skin-sets":
return transformWardSkinSets(raw, tOpts);
case "loot":
return transformLoot(raw, tOpts);
case "champions":
return transformChampions(raw, tOpts);
case "skinlines":
return transformSkinlines(raw, tOpts);
case "universes":
return transformUniverses(raw, tOpts);
case "skins":
return transformSkins(raw, tOpts);
default:
throw new Error(`Unsupported category: ${category}`);
}
}
// src/client.ts
function createClient(options = {}) {
const lang = options.lang ?? "default";
const source = options.source ?? { kind: "cdragon" };
const base = {
lang,
source,
fetch: options.fetch,
patch: options.patch
};
const load = (category) => loadCategory(category, base);
return {
lang,
source,
load,
summonerIcons: () => load("summoner-icons"),
summonerIconSets: () => load("summoner-icon-sets"),
summonerEmotes: () => load("summoner-emotes"),
wardSkins: () => load("ward-skins"),
wardSkinSets: () => load("ward-skin-sets"),
loot: () => load("loot"),
champions: () => load("champions"),
skinlines: () => load("skinlines"),
universes: () => load("universes"),
skins: () => load("skins"),
added: () => load("added"),
version: () => load("version")
};
}
// src/catalog.ts
var ASSET_CATEGORIES = [
"summoner-icons",
"summoner-icon-sets",
"summoner-emotes",
"ward-skins",
"ward-skin-sets",
"loot",
"champions",
"skinlines",
"universes",
"skins",
"added",
"version"
];
var PUBLISHED_DATA_CATEGORIES = [
"summoner-icons",
"summoner-icon-sets",
"summoner-emotes",
"ward-skins",
"ward-skin-sets",
"loot",
"champions",
"skinlines",
"universes",
"skins",
"added"
];
var DEFAULT_DATA_LANGUAGES = ["default", "zh_cn"];
var CATEGORY_META = {
"summoner-icons": {
id: "summoner-icons",
domain: "cosmetics",
description: "Summoner profile icons"
},
"summoner-icon-sets": {
id: "summoner-icon-sets",
domain: "cosmetics",
description: "Summoner icon sets"
},
"summoner-emotes": {
id: "summoner-emotes",
domain: "cosmetics",
description: "Summoner emotes"
},
"ward-skins": {
id: "ward-skins",
domain: "cosmetics",
description: "Ward skins"
},
"ward-skin-sets": {
id: "ward-skin-sets",
domain: "cosmetics",
description: "Ward skin sets"
},
loot: {
id: "loot",
domain: "cosmetics",
description: "Loot chests (filtered)"
},
champions: {
id: "champions",
domain: "champions",
description: "Champion summary list"
},
skinlines: {
id: "skinlines",
domain: "skins",
description: "Skin lines / thematics"
},
universes: {
id: "universes",
domain: "skins",
description: "Universes / IP sets"
},
skins: {
id: "skins",
domain: "skins",
description: "Full skins map with chromas / quest tiers"
},
added: {
id: "added",
domain: "meta",
description: "IDs added vs previous patch snapshot"
},
version: {
id: "version",
domain: "meta",
description: "Data dragon realm / game version"
}
};
// src/helpers/split-skin-id.ts
function splitSkinId(id) {
return {
champId: Math.floor(id / 1e3),
skinIndex: id % 1e3
};
}
function splitId(id) {
const { champId, skinIndex } = splitSkinId(id);
return [champId, skinIndex];
}
// src/types/skins.ts
var RarityEnum = /* @__PURE__ */ ((RarityEnum2) => {
RarityEnum2["KEpic"] = "kEpic";
RarityEnum2["KLegendary"] = "kLegendary";
RarityEnum2["KMythic"] = "kMythic";
RarityEnum2["KNoRarity"] = "kNoRarity";
RarityEnum2["KRare"] = "kRare";
RarityEnum2["KTranscendent"] = "kTranscendent";
RarityEnum2["KUltimate"] = "kUltimate";
return RarityEnum2;
})(RarityEnum || {});
var SkinType = /* @__PURE__ */ ((SkinType2) => {
SkinType2["Empty"] = "";
SkinType2["Ultimate"] = "Ultimate";
return SkinType2;
})(SkinType || {});
// src/helpers/rarity.ts
var raritiesMap = {
["kUltimate" /* KUltimate */]: ["ultimate.png", "Ultimate"],
["kMythic" /* KMythic */]: ["mythic.png", "Mythic"],
["kLegendary" /* KLegendary */]: ["legendary.png", "Legendary"],
["kEpic" /* KEpic */]: ["epic.png", "Epic"],
["kTranscendent" /* KTranscendent */]: ["transcendent.png", "Transcendent"],
["kRare" /* KRare */]: ["rare.png", "Rare"]
};
function getRarityUrl(rarity, opts) {
const key = rarity;
const info = raritiesMap[key];
if (!info) return null;
const [imgName, name] = info;
const imgUrl = `${dataRoot({ patch: opts?.patch ?? "pbe", lang: "default" })}/v1/rarity-gem-icons/${imgName}`;
return { imgUrl, name };
}
function modelviewerUrl(skinId) {
return `https://www.modelviewer.lol/en-US/model-viewer?id=${skinId}`;
}
exports.ALIAS_SUBSTITUTIONS = ALIAS_SUBSTITUTIONS;
exports.ASSET_CATEGORIES = ASSET_CATEGORIES;
exports.CATEGORY_CDRAGON_FILE = CATEGORY_CDRAGON_FILE;
exports.CATEGORY_META = CATEGORY_META;
exports.CDRAGON = CDRAGON;
exports.DDRAGON_REALM_NA = DDRAGON_REALM_NA;
exports.DDRAGON_REALM_TENCENT = DDRAGON_REALM_TENCENT;
exports.DEFAULT_DATA_LANGUAGES = DEFAULT_DATA_LANGUAGES;
exports.DEFAULT_RELEASE_BASE = DEFAULT_RELEASE_BASE;
exports.FetchError = FetchError;
exports.PUBLISHED_DATA_CATEGORIES = PUBLISHED_DATA_CATEGORIES;
exports.RarityEnum = RarityEnum;
exports.SkinType = SkinType;
exports.TENCENT_PROFILE_ICON_CDN = TENCENT_PROFILE_ICON_CDN;
exports.assetUrl = assetUrl;
exports.cdragonDataUrl = cdragonDataUrl;
exports.computeAdded = computeAdded;
exports.createClient = createClient;
exports.dataRoot = dataRoot;
exports.defaultPatchForCategory = defaultPatchForCategory;
exports.fetchJson = fetchJson;
exports.filterLootChests = filterLootChests;
exports.getGameVersion = getGameVersion;
exports.getRarityUrl = getRarityUrl;
exports.getVersionInfo = getVersionInfo;
exports.loadCategory = loadCategory;
exports.modelviewerUrl = modelviewerUrl;
exports.raritiesMap = raritiesMap;
exports.releaseAssetName = releaseAssetName;
exports.releaseBaseUrl = releaseBaseUrl;
exports.releaseDataUrl = releaseDataUrl;
exports.releaseManifestUrl = releaseManifestUrl;
exports.rewritePathsDeep = rewritePathsDeep;
exports.splitId = splitId;
exports.splitSkinId = splitSkinId;
exports.substitute = substitute;
exports.transformChampions = transformChampions;
exports.transformLoot = transformLoot;
exports.transformSkinlines = transformSkinlines;
exports.transformSkins = transformSkins;
exports.transformSummonerEmotes = transformSummonerEmotes;
exports.transformSummonerIconSets = transformSummonerIconSets;
exports.transformSummonerIcons = transformSummonerIcons;
exports.transformUniverses = transformUniverses;
exports.transformWardSkinSets = transformWardSkinSets;
exports.transformWardSkins = transformWardSkins;
//# sourceMappingURL=index.cjs.map
//# sourceMappingURL=index.cjs.map