diff --git a/package.json b/package.json index 3b7bd4d..0206473 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,9 @@ "flowbite": "^1.8.1", "flowbite-svelte": "^0.39.3", "mongodb": "^6.3.0", + "prismarine-nbt": "^2.5.0", "redis": "^4.6.12", - "tailwind-merge": "^1.14.0" + "tailwind-merge": "^1.14.0", + "util": "^0.12.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bfad930..83728bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,12 +26,18 @@ dependencies: mongodb: specifier: ^6.3.0 version: 6.3.0 + prismarine-nbt: + specifier: ^2.5.0 + version: 2.5.0 redis: specifier: ^4.6.12 version: 4.6.12 tailwind-merge: specifier: ^1.14.0 version: 1.14.0 + util: + specifier: ^0.12.5 + version: 0.12.5 devDependencies: '@sveltejs/adapter-auto': @@ -1186,7 +1192,6 @@ packages: /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} - dev: true /axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} @@ -1254,7 +1259,6 @@ packages: function-bind: 1.1.2 get-intrinsic: 1.2.2 set-function-length: 1.1.1 - dev: true /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -1414,7 +1418,6 @@ packages: get-intrinsic: 1.2.2 gopd: 1.0.1 has-property-descriptors: 1.0.1 - dev: true /define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} @@ -1963,7 +1966,6 @@ packages: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 - dev: true /foreground-child@3.1.1: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} @@ -1989,7 +1991,6 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - dev: true /function.prototype.name@1.1.6: resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} @@ -2021,7 +2022,6 @@ packages: has-proto: 1.0.1 has-symbols: 1.0.3 hasown: 2.0.0 - dev: true /get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} @@ -2117,7 +2117,6 @@ packages: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: get-intrinsic: 1.2.2 - dev: true /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -2138,31 +2137,26 @@ packages: resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} dependencies: get-intrinsic: 1.2.2 - dev: true /has-proto@1.0.1: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} - dev: true /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} - dev: true /has-tostringtag@1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 - dev: true /hasown@2.0.0: resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - dev: true /human-signals@5.0.0: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} @@ -2206,6 +2200,14 @@ packages: side-channel: 1.0.4 dev: true + /is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.5 + has-tostringtag: 1.0.0 + dev: false + /is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: @@ -2245,7 +2247,6 @@ packages: /is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - dev: true /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} @@ -2269,6 +2270,13 @@ packages: engines: {node: '>=8'} dev: true + /is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: false + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -2348,7 +2356,6 @@ packages: engines: {node: '>= 0.4'} dependencies: which-typed-array: 1.1.13 - dev: true /is-weakref@1.0.2: resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} @@ -2454,9 +2461,17 @@ packages: dependencies: p-locate: 5.0.0 + /lodash.get@4.4.2: + resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + dev: false + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + /lodash.reduce@4.6.0: + resolution: {integrity: sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==} + dev: false + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true @@ -2967,6 +2982,29 @@ packages: react-is: 18.2.0 dev: true + /prismarine-nbt@2.5.0: + resolution: {integrity: sha512-F0/8UAa9SDDnAGrBYqZc4nG8h2zj5cE2eAJU5xlDR/IsQQ3moVxkOjE3h3nMv6SbvZrvAcgX7waA/nd9LLHYdA==} + dependencies: + protodef: 1.15.0 + dev: false + + /protodef-validator@1.3.1: + resolution: {integrity: sha512-lZ5FWKZYR9xOjpMw1+EfZRfCjzNRQWPq+Dk+jki47Sikl2EeWEPnTfnJERwnU/EwFq6us+0zqHHzSsmLeYX+Lg==} + hasBin: true + dependencies: + ajv: 6.12.6 + dev: false + + /protodef@1.15.0: + resolution: {integrity: sha512-bZ2Omw8dT+DACjJHLrBWZlqN4MlT9g9oSpJDdkUAJOStUzgJp+Zn42FJfPUdwutUxjaxA0PftN0PDlNa2XbneA==} + engines: {node: '>=14'} + dependencies: + lodash.get: 4.4.2 + lodash.reduce: 4.6.0 + protodef-validator: 1.3.1 + readable-stream: 3.6.2 + dev: false + /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2984,6 +3022,15 @@ packages: pify: 2.3.0 dev: true + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: false + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -3088,6 +3135,10 @@ packages: isarray: 2.0.5 dev: true + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + /safe-regex-test@1.0.0: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} dependencies: @@ -3129,7 +3180,6 @@ packages: get-intrinsic: 1.2.2 gopd: 1.0.1 has-property-descriptors: 1.0.1 - dev: true /set-function-name@2.0.1: resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} @@ -3263,6 +3313,12 @@ packages: es-abstract: 1.22.3 dev: true + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -3728,7 +3784,16 @@ packages: /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: true + + /util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + dependencies: + inherits: 2.0.4 + is-arguments: 1.1.1 + is-generator-function: 1.0.10 + is-typed-array: 1.1.12 + which-typed-array: 1.1.13 + dev: false /vite-node@1.1.0(@types/node@20.10.5): resolution: {integrity: sha512-jV48DDUxGLEBdHCQvxL1mEh7+naVy+nhUUUaPAZLd3FJgXuxQiewHcfeZebbJ6onDqNGkP4r3MhQ342PRlG81Q==} @@ -3885,7 +3950,6 @@ packages: for-each: 0.3.3 gopd: 1.0.1 has-tostringtag: 1.0.0 - dev: true /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} diff --git a/src/api/profiles.ts b/src/api/profiles.ts index 81eb65d..3abcef9 100644 --- a/src/api/profiles.ts +++ b/src/api/profiles.ts @@ -81,7 +81,7 @@ export async function parseProfileMember( museum: true }, removed: false, - stats: getStats(profile, player?.data?.unparsed, memberId), + stats: await getStats(profile, player?.data?.unparsed, memberId), lastUpdated: Math.floor(Date.now() / 1000) }; } diff --git a/src/db/mongo/collections.d.ts b/src/db/mongo/collections.d.ts index bc00554..e7610a1 100644 --- a/src/db/mongo/collections.d.ts +++ b/src/db/mongo/collections.d.ts @@ -16,6 +16,7 @@ export interface StoredProfileMember { export interface StoredProfileMemberData { skills?: SkyblockPlayerSkillStats; collections?: Record; + items?: Record; unparsed?: Partial; } diff --git a/src/lib/helper.ts b/src/lib/helper.ts new file mode 100644 index 0000000..9aff8f4 --- /dev/null +++ b/src/lib/helper.ts @@ -0,0 +1,9 @@ +/** + * Get Minecraft lore without the color and formatting codes + * + * @param text - The text with color codes + * @returns The text without color codes. + */ +export function getRawLore(text: string): string { + return text.replaceAll(/ยง[0-9a-fk-or]/g, ''); +} diff --git a/src/lib/stats.ts b/src/lib/stats.ts index b40e58a..4650d30 100644 --- a/src/lib/stats.ts +++ b/src/lib/stats.ts @@ -2,12 +2,19 @@ import type { StoredProfileMemberData } from '$mongo/collections'; import * as stats from '$stats/index'; import type { HypixelPlayerData, SkyblockProfile } from '$types/hypixel'; -export function getStats(profile: SkyblockProfile, player: HypixelPlayerData, uuid: string): StoredProfileMemberData { +export async function getStats( + profile: SkyblockProfile, + player: HypixelPlayerData, + uuid: string +): Promise { const userProfile = profile.members[uuid]; + const items = await stats.getItems(userProfile); + const output = { skills: stats.getSkills(userProfile, player, profile.members), - slayers: stats.getSlayers(userProfile) + slayers: stats.getSlayers(userProfile), + items: items // unparsed: userProfile }; diff --git a/src/lib/stats/index.ts b/src/lib/stats/index.ts index f5bc6bc..14c6c33 100644 --- a/src/lib/stats/index.ts +++ b/src/lib/stats/index.ts @@ -1,2 +1,3 @@ export * from '$lib/stats/skills'; export * from '$lib/stats/slayer'; +export * from '$lib/stats/items'; diff --git a/src/lib/stats/items.ts b/src/lib/stats/items.ts new file mode 100644 index 0000000..39e1375 --- /dev/null +++ b/src/lib/stats/items.ts @@ -0,0 +1,47 @@ +import type { SkyblockProfileMember } from '$types/hypixel'; +import { processItems } from './items/items'; + +function getPath(object: any, path: string[]): unknown { + let output = object; + + for (const key of path) { + if (output === undefined) { + return undefined; + } + + output = output[key]; + } + + return output; +} + +export async function getItems(userProfile: Partial) { + const output = { + inventory: [] + }; + console.log(userProfile.inventory); + + const inventoryTypes = [ + { name: 'Inventory', path: ['inventory', 'inv_contents'] }, + { name: 'Ender Chest', path: ['inventory', 'ender_chest_contents'] }, + { name: 'Armor', path: ['inventory', 'inv_armor'] }, + { name: 'Equipment', path: ['inventory', 'equipment_contents'] }, + { name: 'Personal Vault', path: ['inventory', 'personal_vault_contents'] }, + + { name: 'Potion Bag', path: ['inventory', 'bag_contents', 'potion_bag'] }, + { name: 'Accessory Bag', path: ['inventory', 'bag_contents', 'talisman_bag'] }, + { name: 'Fishing Bag', path: ['inventory', 'bag_contents', 'fishing_bag'] }, + { name: 'Quiver', path: ['inventory', 'bag_contents', 'quiver'] }, + { name: 'Candy Bag', path: ['inventory', 'shared_inventory', 'candy_inventory_contents'] } + ]; + + for (const inventoryType of inventoryTypes) { + const key = inventoryType.name.replace(' ', '_').toLowerCase(); + + const data = getPath(userProfile, inventoryType.path); + + output[key] = await processItems(data); + } + + return output; +} diff --git a/src/lib/stats/items/items.ts b/src/lib/stats/items/items.ts new file mode 100644 index 0000000..61b2a37 --- /dev/null +++ b/src/lib/stats/items/items.ts @@ -0,0 +1,41 @@ +import util from 'util'; +import nbt from 'prismarine-nbt'; +const parseNbt = util.promisify(nbt.parse); +import * as helper from '../../helper'; + +export async function processItems(base64: { data?: string }) { + if (base64?.data === undefined) { + return []; + } + + const buf = Buffer.from(base64.data, 'base64'); + + let data = await parseNbt(buf); + data = nbt.simplify(data); + + const items = data.i; + for (const item of items) { + // ? Display Name + if (item.tag?.display?.Name != undefined) { + item.display_name = helper.getRawLore(item.tag.display.Name); + } + + // ? Skull Textre Path + if ( + Array.isArray(item.tag?.SkullOwner?.Properties?.textures) && + item.tag.SkullOwner.Properties.textures.length > 0 + ) { + try { + const json = JSON.parse(Buffer.from(item.tag.SkullOwner.Properties.textures[0].Value, 'base64').toString()); + const url = json.textures.SKIN.url; + const uuid = url.split('/').pop(); + + item.texture_path = `/head/${uuid}`; + } catch (e) { + console.error(e); + } + } + } + + return items; +}