dragon-item-parser: introduce item parser library
This commit is contained in:
49
.gitea/workflows/dragon-item-parser.yml
Normal file
49
.gitea/workflows/dragon-item-parser.yml
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Dragon Item Parser CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'dragon-item-parser/**'
|
||||
branches: [main]
|
||||
pull_request:
|
||||
paths:
|
||||
- 'dragon-item-parser/**'
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: dragon-item-parser
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Check formatting
|
||||
run: npm run format:check
|
||||
|
||||
- name: Run linting
|
||||
run: npm run lint
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Verify build output
|
||||
run: |
|
||||
test -f dist/index.js
|
||||
test -f dist/index.d.ts
|
||||
test -f dist/item.js
|
||||
test -f dist/item.d.ts
|
||||
3
dragon-item-parser/.gitignore
vendored
Normal file
3
dragon-item-parser/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
10
dragon-item-parser/.prettierrc
Normal file
10
dragon-item-parser/.prettierrc
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
19
dragon-item-parser/eslint.config.js
Normal file
19
dragon-item-parser/eslint.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { defineConfig } from 'eslint/config'
|
||||
import js from '@eslint/js'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import prettier from 'eslint-config-prettier'
|
||||
|
||||
export default defineConfig([
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
prettier,
|
||||
{
|
||||
ignores: ['dist/**', 'node_modules/**']
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
semi: 'off',
|
||||
'prefer-const': 'error'
|
||||
}
|
||||
}
|
||||
])
|
||||
2886
dragon-item-parser/package-lock.json
generated
Normal file
2886
dragon-item-parser/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
dragon-item-parser/package.json
Normal file
53
dragon-item-parser/package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "dragon-item-parser",
|
||||
"version": "1.0.0",
|
||||
"description": "Parse League of Legends item stats from CommunityDragon data",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"league-of-legends",
|
||||
"cdragon",
|
||||
"communitydragon",
|
||||
"item",
|
||||
"parser",
|
||||
"lol"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": ""
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
||||
"@typescript-eslint/parser": "^8.53.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"prettier": "^3.8.3",
|
||||
"typescript": "^5.0.0",
|
||||
"typescript-eslint": "^8.53.1",
|
||||
"vitest": "^3.0.0"
|
||||
}
|
||||
}
|
||||
2
dragon-item-parser/src/index.ts
Normal file
2
dragon-item-parser/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export type { ItemStats, CDragonItem, ItemWithStats } from './item.js'
|
||||
export { parseItemStats, parseItem, parseItems } from './item.js'
|
||||
202
dragon-item-parser/src/item.ts
Normal file
202
dragon-item-parser/src/item.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Item stats parsed from CommunityDragon item description HTML
|
||||
*/
|
||||
export interface ItemStats {
|
||||
// Offensive stats
|
||||
attackDamage?: number
|
||||
abilityPower?: number
|
||||
attackSpeed?: number
|
||||
criticalStrikeChance?: number
|
||||
lifeSteal?: number
|
||||
omnivamp?: number
|
||||
physicalVamp?: number
|
||||
spellVamp?: number
|
||||
|
||||
// Defensive stats
|
||||
health?: number
|
||||
armor?: number
|
||||
magicResist?: number
|
||||
|
||||
// Resource stats
|
||||
mana?: number
|
||||
baseManaRegen?: number
|
||||
baseHealthRegen?: number
|
||||
|
||||
// Movement stats
|
||||
moveSpeed?: number
|
||||
|
||||
// Ability stats
|
||||
abilityHaste?: number
|
||||
|
||||
// Penetration stats (usually percentages)
|
||||
armorPenetration?: number
|
||||
magicPenetration?: number
|
||||
lethality?: number
|
||||
magicPenetrationFlat?: number
|
||||
|
||||
// Other percentage stats
|
||||
healAndShieldPower?: number
|
||||
tenacity?: number
|
||||
slowResist?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Stat name mappings from CDragon description text to stat keys
|
||||
*/
|
||||
const STAT_MAPPINGS: Record<string, keyof ItemStats> = {
|
||||
'Attack Damage': 'attackDamage',
|
||||
'Ability Power': 'abilityPower',
|
||||
'Attack Speed': 'attackSpeed',
|
||||
'Critical Strike Chance': 'criticalStrikeChance',
|
||||
'Life Steal': 'lifeSteal',
|
||||
Omnivamp: 'omnivamp',
|
||||
'Physical Vamp': 'physicalVamp',
|
||||
'Spell Vamp': 'spellVamp',
|
||||
Health: 'health',
|
||||
Armor: 'armor',
|
||||
'Magic Resist': 'magicResist',
|
||||
Mana: 'mana',
|
||||
'Base Mana Regen': 'baseManaRegen',
|
||||
'Base Health Regen': 'baseHealthRegen',
|
||||
'Move Speed': 'moveSpeed',
|
||||
'Ability Haste': 'abilityHaste',
|
||||
'Armor Penetration': 'armorPenetration',
|
||||
'Magic Penetration': 'magicPenetration',
|
||||
Lethality: 'lethality',
|
||||
'Heal and Shield Power': 'healAndShieldPower',
|
||||
Tenacity: 'tenacity',
|
||||
'Slow Resist': 'slowResist'
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a stat value string to a number
|
||||
* Handles both flat values (e.g., "25") and percentages (e.g., "25%")
|
||||
*/
|
||||
function parseStatValue(valueStr: string): { value: number; isPercentage: boolean } {
|
||||
const trimmed = valueStr.trim()
|
||||
const isPercentage = trimmed.includes('%')
|
||||
const numStr = trimmed.replace('%', '').replace(',', '').trim()
|
||||
const value = parseFloat(numStr)
|
||||
return { value, isPercentage }
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract stats section from the description HTML
|
||||
*/
|
||||
function extractStatsSection(description: string): string | null {
|
||||
const statsMatch = description.match(/<stats>(.*?)<\/stats>/s)
|
||||
return statsMatch ? statsMatch[1] : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse individual stat lines from the stats section
|
||||
* Format: <attention> value </attention> statName
|
||||
*/
|
||||
function parseStatLines(
|
||||
statsSection: string
|
||||
): Array<{ value: number; isPercentage: boolean; statName: string }> {
|
||||
const results: Array<{ value: number; isPercentage: boolean; statName: string }> = []
|
||||
|
||||
// Match patterns like: <attention> 25</attention> Move Speed
|
||||
// or: <attention> 25%</attention> Attack Speed
|
||||
const statRegex = /<attention>\s*([^<]+)<\/attention>\s*([^<]+)/g
|
||||
let match
|
||||
|
||||
while ((match = statRegex.exec(statsSection)) !== null) {
|
||||
const valueStr = match[1]
|
||||
const statName = match[2].trim()
|
||||
|
||||
const { value, isPercentage } = parseStatValue(valueStr)
|
||||
|
||||
if (!isNaN(value) && statName) {
|
||||
results.push({ value, isPercentage, statName })
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse item stats from CDragon description HTML
|
||||
*
|
||||
* @param description - The HTML description string from CDragon items.json
|
||||
* @returns Parsed ItemStats object with all recognized stats
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const stats = parseItemStats(
|
||||
* '<mainText><stats><attention> 25</attention> Move Speed</stats><br><br></mainText>'
|
||||
* )
|
||||
* // Returns: { moveSpeed: 25 }
|
||||
* ```
|
||||
*/
|
||||
export function parseItemStats(description: string): ItemStats {
|
||||
const stats: ItemStats = {}
|
||||
|
||||
if (!description) {
|
||||
return stats
|
||||
}
|
||||
|
||||
const statsSection = extractStatsSection(description)
|
||||
if (!statsSection) {
|
||||
return stats
|
||||
}
|
||||
|
||||
const statLines = parseStatLines(statsSection)
|
||||
|
||||
for (const { value, statName } of statLines) {
|
||||
const statKey = STAT_MAPPINGS[statName]
|
||||
|
||||
if (statKey) {
|
||||
// For percentage stats that are stored as decimals (e.g., 25% -> 25)
|
||||
// We store the percentage value as-is for consistency with game data
|
||||
stats[statKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
/**
|
||||
* Item data structure from CDragon
|
||||
*/
|
||||
export interface CDragonItem {
|
||||
id: number
|
||||
name: string
|
||||
description: string
|
||||
active?: boolean
|
||||
inStore?: boolean
|
||||
from?: number[]
|
||||
to?: number[]
|
||||
categories?: string[]
|
||||
maxStacks?: number
|
||||
requiredChampion?: string
|
||||
requiredAlly?: string
|
||||
price?: number
|
||||
priceTotal?: number
|
||||
iconPath: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Item with parsed stats
|
||||
*/
|
||||
export interface ItemWithStats extends CDragonItem {
|
||||
stats: ItemStats
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a CDragon item and add parsed stats
|
||||
*/
|
||||
export function parseItem(item: CDragonItem): ItemWithStats {
|
||||
return {
|
||||
...item,
|
||||
stats: parseItemStats(item.description)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an array of CDragon items and add parsed stats
|
||||
*/
|
||||
export function parseItems(items: CDragonItem[]): ItemWithStats[] {
|
||||
return items.map(parseItem)
|
||||
}
|
||||
235
dragon-item-parser/test/item.test.ts
Normal file
235
dragon-item-parser/test/item.test.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { parseItemStats, parseItem, parseItems } from '../src/item.js'
|
||||
|
||||
describe('parseItemStats', () => {
|
||||
describe('basic stats', () => {
|
||||
it('should parse Move Speed', () => {
|
||||
// Boots (id: 1001)
|
||||
const description =
|
||||
'<mainText><stats><attention> 25</attention> Move Speed</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.moveSpeed).toBe(25)
|
||||
})
|
||||
|
||||
it('should parse Attack Damage', () => {
|
||||
// Long Sword (id: 1036)
|
||||
const description =
|
||||
'<mainText><stats><attention> 10</attention> Attack Damage</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.attackDamage).toBe(10)
|
||||
})
|
||||
|
||||
it('should parse Ability Power', () => {
|
||||
// Amplifying Tome (id: 1052)
|
||||
const description =
|
||||
'<mainText><stats><attention> 20</attention> Ability Power</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.abilityPower).toBe(20)
|
||||
})
|
||||
|
||||
it('should parse Health', () => {
|
||||
// Ruby Crystal (id: 1028)
|
||||
const description =
|
||||
'<mainText><stats><attention> 150</attention> Health</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.health).toBe(150)
|
||||
})
|
||||
|
||||
it('should parse Armor', () => {
|
||||
// Cloth Armor (id: 1029)
|
||||
const description =
|
||||
'<mainText><stats><attention> 15</attention> Armor</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.armor).toBe(15)
|
||||
})
|
||||
|
||||
it('should parse Magic Resist', () => {
|
||||
// Null-Magic Mantle (id: 1033)
|
||||
const description =
|
||||
'<mainText><stats><attention> 20</attention> Magic Resist</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.magicResist).toBe(20)
|
||||
})
|
||||
|
||||
it('should parse Mana', () => {
|
||||
// Sapphire Crystal (id: 1027)
|
||||
const description =
|
||||
'<mainText><stats><attention> 300</attention> Mana</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.mana).toBe(300)
|
||||
})
|
||||
})
|
||||
|
||||
describe('percentage stats', () => {
|
||||
it('should parse Attack Speed percentage', () => {
|
||||
// Dagger (id: 1042)
|
||||
const description =
|
||||
'<mainText><stats><attention> 10%</attention> Attack Speed</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.attackSpeed).toBe(10)
|
||||
})
|
||||
|
||||
it('should parse Critical Strike Chance', () => {
|
||||
// Cloak of Agility (id: 1018)
|
||||
const description =
|
||||
'<mainText><stats><attention> 15%</attention> Critical Strike Chance</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.criticalStrikeChance).toBe(15)
|
||||
})
|
||||
|
||||
it('should parse Life Steal', () => {
|
||||
// Vampiric Scepter (id: 1053)
|
||||
const description =
|
||||
'<mainText><stats><attention> 15</attention> Attack Damage<br><attention> 7%</attention> Life Steal</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.attackDamage).toBe(15)
|
||||
expect(stats.lifeSteal).toBe(7)
|
||||
})
|
||||
|
||||
it('should parse Base Mana Regen percentage', () => {
|
||||
// Faerie Charm (id: 1004)
|
||||
const description =
|
||||
'<mainText><stats><attention> 50%</attention> Base Mana Regen</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.baseManaRegen).toBe(50)
|
||||
})
|
||||
|
||||
it('should parse Base Health Regen percentage', () => {
|
||||
// Rejuvenation Bead (id: 1006)
|
||||
const description =
|
||||
'<mainText><stats><attention> 100%</attention> Base Health Regen</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.baseHealthRegen).toBe(100)
|
||||
})
|
||||
})
|
||||
|
||||
describe('multiple stats', () => {
|
||||
it("should parse multiple stats from Doran's Blade", () => {
|
||||
// Doran's Blade (id: 1055)
|
||||
const description =
|
||||
'<mainText><stats><attention> 10</attention> Attack Damage<br><attention> 80</attention> Health<br><attention> 2.5%</attention> Omnivamp</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.attackDamage).toBe(10)
|
||||
expect(stats.health).toBe(80)
|
||||
expect(stats.omnivamp).toBe(2.5)
|
||||
})
|
||||
|
||||
it("should parse multiple stats from Doran's Ring", () => {
|
||||
// Doran's Ring (id: 1056)
|
||||
const description =
|
||||
'<mainText><stats><attention> 18</attention> Ability Power<br><attention> 90</attention> Health</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.abilityPower).toBe(18)
|
||||
expect(stats.health).toBe(90)
|
||||
})
|
||||
|
||||
it("should parse multiple stats from Seeker's Armguard", () => {
|
||||
// Seeker's Armguard (id: 2420)
|
||||
const description =
|
||||
'<mainText><stats><attention> 40</attention> Ability Power<br><attention> 25</attention> Armor</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.abilityPower).toBe(40)
|
||||
expect(stats.armor).toBe(25)
|
||||
})
|
||||
|
||||
it('should parse complex item with many stats', () => {
|
||||
// Overlord's Bloodmail (id: 2501)
|
||||
const description =
|
||||
'<mainText><stats><attention> 30</attention> Attack Damage<br><attention> 550</attention> Health</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.attackDamage).toBe(30)
|
||||
expect(stats.health).toBe(550)
|
||||
})
|
||||
|
||||
it('should parse item with Ability Haste', () => {
|
||||
// Unending Despair (id: 2502)
|
||||
const description =
|
||||
'<mainText><stats><attention> 400</attention> Health<br><attention> 50</attention> Armor<br><attention> 15</attention> Ability Haste</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.health).toBe(400)
|
||||
expect(stats.armor).toBe(50)
|
||||
expect(stats.abilityHaste).toBe(15)
|
||||
})
|
||||
|
||||
it('should parse item with Mana and Ability Haste', () => {
|
||||
// Blackfire Torch (id: 2503)
|
||||
const description =
|
||||
'<mainText><stats><attention> 80</attention> Ability Power<br><attention> 600</attention> Mana<br><attention> 20</attention> Ability Haste</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.abilityPower).toBe(80)
|
||||
expect(stats.mana).toBe(600)
|
||||
expect(stats.abilityHaste).toBe(20)
|
||||
})
|
||||
})
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should return empty object for empty description', () => {
|
||||
const stats = parseItemStats('')
|
||||
expect(stats).toEqual({})
|
||||
})
|
||||
|
||||
it('should return empty object for description without stats', () => {
|
||||
const description = '<mainText><stats></stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats).toEqual({})
|
||||
})
|
||||
|
||||
it('should handle description with only passive text', () => {
|
||||
// Emberknife (id: 1035) - no stats, only passives
|
||||
const description =
|
||||
'<mainText><stats></stats><br><br> <passive>7%</passive> Omnivamp against jungle monsters<br><li><passive>Sear:</passive> Damaging jungle monsters burns them for <magicDamage> magic damage</magicDamage> over 5 seconds.</mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats).toEqual({})
|
||||
})
|
||||
|
||||
it('should handle decimal values', () => {
|
||||
const description =
|
||||
'<mainText><stats><attention> 2.5%</attention> Omnivamp</stats><br><br></mainText>'
|
||||
const stats = parseItemStats(description)
|
||||
expect(stats.omnivamp).toBe(2.5)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseItem', () => {
|
||||
it('should parse item and add stats', () => {
|
||||
const item = {
|
||||
id: 1001,
|
||||
name: 'Boots',
|
||||
description:
|
||||
'<mainText><stats><attention> 25</attention> Move Speed</stats><br><br></mainText>',
|
||||
iconPath: '/path/to/icon.png'
|
||||
}
|
||||
|
||||
const result = parseItem(item)
|
||||
expect(result.id).toBe(1001)
|
||||
expect(result.name).toBe('Boots')
|
||||
expect(result.stats.moveSpeed).toBe(25)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseItems', () => {
|
||||
it('should parse multiple items', () => {
|
||||
const items = [
|
||||
{
|
||||
id: 1001,
|
||||
name: 'Boots',
|
||||
description:
|
||||
'<mainText><stats><attention> 25</attention> Move Speed</stats><br><br></mainText>',
|
||||
iconPath: '/path/to/boots.png'
|
||||
},
|
||||
{
|
||||
id: 1036,
|
||||
name: 'Long Sword',
|
||||
description:
|
||||
'<mainText><stats><attention> 10</attention> Attack Damage</stats><br><br></mainText>',
|
||||
iconPath: '/path/to/sword.png'
|
||||
}
|
||||
]
|
||||
|
||||
const results = parseItems(items)
|
||||
expect(results).toHaveLength(2)
|
||||
expect(results[0].stats.moveSpeed).toBe(25)
|
||||
expect(results[1].stats.attackDamage).toBe(10)
|
||||
})
|
||||
})
|
||||
17
dragon-item-parser/tsconfig.json
Normal file
17
dragon-item-parser/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "test"]
|
||||
}
|
||||
7
dragon-item-parser/vitest.config.ts
Normal file
7
dragon-item-parser/vitest.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
include: ['test/**/*.test.ts']
|
||||
}
|
||||
})
|
||||
9078
frontend/package-lock.json
generated
9078
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@
|
||||
"format:check": "prettier --check ."
|
||||
},
|
||||
"dependencies": {
|
||||
"dragon-item-parser": "file:../dragon-item-parser",
|
||||
"@nuxt/eslint": "^1.12.1",
|
||||
"@nuxt/fonts": "^0.11.3",
|
||||
"@nuxt/image": "^1.10.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { readFile, existsSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
import { promisify } from 'util'
|
||||
import { parseItemStats, type ItemStats } from 'dragon-item-parser'
|
||||
|
||||
const readFileAsync = promisify(readFile)
|
||||
|
||||
@@ -78,14 +79,20 @@ async function fetchFromCache<T>(
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items data from cache
|
||||
* Get items data from cache with parsed stats
|
||||
*/
|
||||
async function getItems(patch?: string): Promise<CDragonItem[]> {
|
||||
return fetchFromCache<CDragonItem[]>(
|
||||
async function getItems(patch?: string): Promise<CDragonItemWithStats[]> {
|
||||
const items = await fetchFromCache<CDragonItem[]>(
|
||||
'items.json',
|
||||
'plugins/rcp-be-lol-game-data/global/default/v1/items.json',
|
||||
{ patch }
|
||||
)
|
||||
|
||||
// Parse stats from description for each item
|
||||
return items.map(item => ({
|
||||
...item,
|
||||
stats: parseItemStats(item.description || '')
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,6 +155,10 @@ interface CDragonItem {
|
||||
}
|
||||
}
|
||||
|
||||
interface CDragonItemWithStats extends CDragonItem {
|
||||
stats: ItemStats
|
||||
}
|
||||
|
||||
interface CDragonPerk {
|
||||
id: number
|
||||
name: string
|
||||
@@ -197,6 +208,7 @@ export {
|
||||
|
||||
export type {
|
||||
CDragonItem,
|
||||
CDragonItemWithStats,
|
||||
CDragonPerk,
|
||||
CDragonPerkStyle,
|
||||
CDragonPerkStyles,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { ItemStats } from 'dragon-item-parser'
|
||||
|
||||
declare global {
|
||||
type ChampionsResponse = {
|
||||
data: Ref<Array<Champion>>
|
||||
@@ -29,6 +31,7 @@ declare global {
|
||||
from?: number[]
|
||||
price?: number
|
||||
priceTotal?: number
|
||||
stats?: ItemStats
|
||||
}
|
||||
type SummonerSpell = {
|
||||
id: number
|
||||
|
||||
127
match_collector/package-lock.json
generated
127
match_collector/package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dragon-item-parser": "file:../dragon-item-parser",
|
||||
"mongodb": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -468,19 +469,25 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
|
||||
"integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
|
||||
"version": "0.21.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
|
||||
"integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint/object-schema": "^2.1.7",
|
||||
"debug": "^4.3.1",
|
||||
"minimatch": "^3.1.2"
|
||||
"minimatch": "^3.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@eslint/config-array/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
@@ -528,19 +535,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
|
||||
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
|
||||
"integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.4",
|
||||
"ajv": "^6.14.0",
|
||||
"debug": "^4.3.2",
|
||||
"espree": "^10.0.1",
|
||||
"globals": "^14.0.0",
|
||||
"ignore": "^5.2.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.1",
|
||||
"minimatch": "^3.1.2",
|
||||
"minimatch": "^3.1.5",
|
||||
"strip-json-comments": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -550,6 +557,12 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
@@ -582,9 +595,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.39.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
|
||||
"integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
|
||||
"version": "9.39.4",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
|
||||
"integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -616,27 +629,40 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||
"integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@humanfs/types": "^0.15.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/node": {
|
||||
"version": "0.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
|
||||
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
|
||||
"version": "0.16.8",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
|
||||
"integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@humanfs/core": "^0.19.1",
|
||||
"@humanfs/core": "^0.19.2",
|
||||
"@humanfs/types": "^0.15.0",
|
||||
"@humanwhocodes/retry": "^0.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/types": {
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
|
||||
"integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/module-importer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
||||
@@ -928,9 +954,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@@ -986,10 +1012,13 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.5",
|
||||
@@ -1003,15 +1032,6 @@
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion/node_modules/balanced-match": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/bson": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/bson/-/bson-7.2.0.tgz",
|
||||
@@ -1106,6 +1126,11 @@
|
||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/dragon-item-parser": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "file:../dragon-item-parser",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.27.7",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
|
||||
@@ -1160,24 +1185,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.39.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
|
||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||
"version": "9.39.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
|
||||
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
"@eslint/config-array": "^0.21.1",
|
||||
"@eslint/config-array": "^0.21.2",
|
||||
"@eslint/config-helpers": "^0.4.2",
|
||||
"@eslint/core": "^0.17.0",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "9.39.2",
|
||||
"@eslint/eslintrc": "^3.3.5",
|
||||
"@eslint/js": "9.39.4",
|
||||
"@eslint/plugin-kit": "^0.4.1",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@humanwhocodes/retry": "^0.4.2",
|
||||
"@types/estree": "^1.0.6",
|
||||
"ajv": "^6.12.4",
|
||||
"ajv": "^6.14.0",
|
||||
"chalk": "^4.0.0",
|
||||
"cross-spawn": "^7.0.6",
|
||||
"debug": "^4.3.2",
|
||||
@@ -1196,7 +1221,7 @@
|
||||
"is-glob": "^4.0.0",
|
||||
"json-stable-stringify-without-jsonify": "^1.0.1",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"minimatch": "^3.1.2",
|
||||
"minimatch": "^3.1.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"optionator": "^0.9.3"
|
||||
},
|
||||
@@ -1261,6 +1286,12 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/eslint/node_modules/brace-expansion": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
@@ -1463,7 +1494,6 @@
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
@@ -1473,11 +1503,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz",
|
||||
"integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==",
|
||||
"version": "4.14.0",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
|
||||
"integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
@@ -1858,7 +1887,6 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -1877,7 +1905,6 @@
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"dragon-item-parser": "file:../dragon-item-parser",
|
||||
"mongodb": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Reference in New Issue
Block a user