Compare commits

...

No commits in common. "python" and "master" have entirely different histories.

16 changed files with 2208 additions and 113 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
target/
*.json

36
.woodpecker/.linux.yml Normal file
View file

@ -0,0 +1,36 @@
steps:
build:
image: rust:alpine
pull: true
commands:
- apk add --no-cache musl-dev
- rustup component add clippy
- cargo test
- cargo clippy
- cargo build --release
- strip target/release/cggitem_sets
when:
# path:
# include: ['.woodpecker/*.yml', 'src/*', 'Cargo*']
- event: [ manual, tag ]
- event: push
branch: renovate/*
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
deploy:
image: alpine:3.21
pull: true
commands:
- apk add --no-cache curl
- curl -f -i -u $REGISTRY_USERNAME:$REGISTRY_PASSWORD -X POST 'https://nexus.nyyu.dev/service/rest/v1/components?repository=maven-releases' -F maven2.groupId=dev.nyyu -F maven2.artifactId=cggitem_sets -F maven2.version=$(awk -F ' = ' '$1 ~ /version/ { gsub(/["]/, "", $2); printf("%s",$2) }' Cargo.toml) -F maven2.asset1=@target/release/cggitem_sets -F maven2.asset1.extension=elf -F maven2.asset1.classifier=linux-x64
environment:
REGISTRY_USERNAME:
from_secret: registry_username
REGISTRY_PASSWORD:
from_secret: registry_password
when:
path:
include: [ '.woodpecker/*.yml', 'src/*', 'Cargo*' ]
event: [ push, manual, tag ]
branch:
- ${CI_REPO_DEFAULT_BRANCH}

38
.woodpecker/.mingw.yml Normal file
View file

@ -0,0 +1,38 @@
steps:
build:
image: rust:alpine
pull: true
commands:
- apk add --no-cache mingw-w64-gcc musl-dev
- mkdir ~/.cargo
- echo -e '[target.x86_64-pc-windows-gnu]\nlinker = "x86_64-w64-mingw32-gcc"\nar = "x86_64-w64-mingw32-gcc-ar"' > ~/.cargo/config
- rustup target add x86_64-pc-windows-gnu
- rustup component add clippy
- cargo test
- cargo clippy --target x86_64-pc-windows-gnu
- cargo build --release --target x86_64-pc-windows-gnu
- strip target/x86_64-pc-windows-gnu/release/cggitem_sets.exe
when:
# path:
# include: ['.woodpecker/*.yml', 'src/*', 'Cargo*']
- event: [ manual, tag ]
- event: push
branch: renovate/*
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
deploy:
image: alpine:3.21
pull: true
commands:
- apk add --no-cache curl
- curl -f -i -u $REGISTRY_USERNAME:$REGISTRY_PASSWORD -X POST 'https://nexus.nyyu.dev/service/rest/v1/components?repository=maven-releases' -F maven2.groupId=dev.nyyu -F maven2.artifactId=cggitem_sets -F maven2.version=$(awk -F ' = ' '$1 ~ /version/ { gsub(/["]/, "", $2); printf("%s",$2) }' Cargo.toml) -F maven2.asset1=@target/x86_64-pc-windows-gnu/release/cggitem_sets.exe -F maven2.asset1.extension=exe -F maven2.asset1.classifier=mingw-x64
environment:
REGISTRY_USERNAME:
from_secret: registry_username
REGISTRY_PASSWORD:
from_secret: registry_password
when:
path:
include: [ '.woodpecker/*.yml', 'src/*', 'Cargo*' ]
event: [ push, manual, tag ]
branch: ${CI_REPO_DEFAULT_BRANCH}

46
.woodpecker/.test.yml Normal file
View file

@ -0,0 +1,46 @@
clone:
- name: clone
image: woodpeckerci/plugin-git:2.6.0
settings:
partial: false
when:
- event: [ pull_request, manual, tag ]
- event: push
branch: renovate/*
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
steps:
test:
image: rust:alpine
pull: true
privileged: true
commands:
- apk add --no-cache musl-dev openssl-dev
- wget -q -O- https://github.com/xd009642/tarpaulin/releases/latest/download/cargo-tarpaulin-x86_64-unknown-linux-musl.tar.gz | tar xzf - -C /usr/local/cargo/bin/
- rustup component add clippy
- cargo clippy --message-format=json &> clippy-report.json
- cargo tarpaulin --out Lcov
when:
# path:
# include: ['.woodpecker/*.yml', 'src/*', 'Cargo*']
- event: [ pull_request, manual, tag ]
- event: push
branch: renovate/*
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
sonar:
image: sonarsource/sonar-scanner-cli:11
pull: true
commands:
- curl -s https://git.nyyu.dev/nyyu/ci-scripts/raw/branch/main/sonar.sh | bash
environment:
SONAR_HOST_URL:
from_secret: sonar_host_url
SONAR_TOKEN:
from_secret: sonar_token
when:
# path:
# include: ['.woodpecker/*.yml', 'src/*', 'Cargo*']
- event: [ pull_request, manual, tag ]
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}

View file

@ -1,111 +0,0 @@
import json
import logging
import random
import re
from pathlib import Path
from time import sleep
import requests
from bs4 import BeautifulSoup
LOL_CHAMPS_DIR = 'C:\\League of Legends\\Config\\Champions\\'
PATTERN_CHAMPIONGG = re.compile(r'^\s+matchupData\.championData = (.*)$', re.MULTILINE)
HEADERS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0'}
ITEMS_TYPE = {
'Most Frequent Starters': ['firstItems', 'mostGames'],
'Highest Win % Starters': ['firstItems', 'highestWinPercent'],
'Most Frequent Core Build': ['items', 'mostGames'],
'Highest Win % Core Build': ['items', 'highestWinPercent'],
}
CONSUMABLES = [2003, 2004, 2055, 2031, 2032, 2033, 2138, 2140, 2139]
TRINKETS = [3340, 3364, 3363]
def getChampData(champ, position, session):
page = session.get('https://champion.gg/champion/%s/%s?league=' % (champ, position)).text
return json.loads(PATTERN_CHAMPIONGG.search(page).group(1))
def getChampsWithPositionsAndPatch(session):
soup = BeautifulSoup(session.get('https://champion.gg').text, 'html.parser')
patch = soup.find('div', class_='analysis-holder').find('strong').text
champs = []
for div in soup.find_all('div', class_='champ-height'):
champ_name = div.find('span', class_='champion-name').text
pos = [link['href'].split('/')[-1] for link in div.find_all('a', href=True)[1:]]
champs.append({champ_name: pos})
return (champs, patch)
def makeItemSet(data, label):
return {
"items": [{"id": item['id'], "count": 1} for item in data['items']],
"type": "%s (%.2f%% - %d games)" % (label, data['winPercent'] * 100, data['games'])
}
def makeItemSetFromList(list, label, key, data):
return {
"items": [{"id": str(id), "count": 1} for id in list],
"type": label % '.'.join([data['skills']['skillInfo'][int(k)-1]['key'] for k in data['skills'][key]['order']])
}
def writeItemSet(id, pos, ver, dir, session):
logging.info('Retrieving data for %s at %s', id, pos)
data = getChampData(id, pos, session)
item_set = {
"title": "CGG %s %s - %.2f%%" % (pos, ver, data['stats']['winRate'] * 100),
"type": "custom",
"map": "any",
"mode": "any",
"priority": False,
"sortrank": 0,
"blocks": []
}
for label, path in ITEMS_TYPE.items():
if path[1] in data[path[0]]:
item_set['blocks'].append(makeItemSet(data[path[0]][path[1]], label))
item_set['blocks'].append(makeItemSetFromList(CONSUMABLES, "Consumables | Frequent: %s", 'mostGames', data))
item_set['blocks'].append(makeItemSetFromList(TRINKETS, "Trinkets | Wins: %s", 'highestWinPercent', data))
logging.info('Writing item set for %s at %s', id, pos)
with open(dir.joinpath('CGG_%s_%s.json' % (id, pos)), 'w', newline='\n') as out:
json.dump(item_set, out, indent=4)
def main():
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', level=logging.INFO)
logging.info('CGG Item Sets')
session = requests.Session()
session.headers.update(HEADERS)
ver = session.get('https://ddragon.leagueoflegends.com/realms/euw.json').json()['v']
data = session.get('http://ddragon.leagueoflegends.com/cdn/%s/data/en_US/champion.json' % ver).json()['data']
lol_champs = [data[i] for i in data]
logging.info('LoL version: %s', ver)
logging.info('LoL numbers of champs: %d', len(lol_champs))
(champs, patch) = getChampsWithPositionsAndPatch(session)
logging.info('CGG version: %s', patch)
logging.info('CGG numbers of champs: %d', len(champs))
for champ in champs:
for name, positions in champ.items():
c = next((x for x in lol_champs if x['name'] == name), None)
if c is not None:
path = Path(LOL_CHAMPS_DIR, c['id'], 'Recommended')
path.mkdir(parents=True, exist_ok=True)
for pos in positions:
writeItemSet(c['id'], pos, patch, path, session)
sleep(random.uniform(.3, .4))
else:
logging.error('%s not found in LoL champs', name)
if __name__ == "__main__":
main()

843
Cargo.lock generated Normal file
View file

@ -0,0 +1,843 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "cc"
version = "1.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0cf6e91fde44c773c6ee7ec6bba798504641a8bc2eb7e37a04ffbf4dfaa55a"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cggitem_sets"
version = "1.0.0"
dependencies = [
"indexmap",
"log",
"rayon",
"serde",
"serde_derive",
"serde_json",
"simple_logger",
"ureq",
"winreg",
]
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "flate2"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "form_urlencoded"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "idna"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "indexmap"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
"hashbrown",
"rayon",
"serde",
]
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "libc"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "litemap"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
dependencies = [
"adler2",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "ring"
version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"spin",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustls"
version = "0.23.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8"
dependencies = [
"log",
"once_cell",
"ring",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
[[package]]
name = "rustls-webpki"
version = "0.102.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
dependencies = [
"indexmap",
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simple_logger"
version = "5.0.0"
source = "git+https://github.com/phoval/rust-simple_logger.git#721ae9a83844bbe628d58a4469c72738c5925f13"
dependencies = [
"log",
"time",
"windows-sys 0.59.0",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "ureq"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
dependencies = [
"base64",
"flate2",
"log",
"once_cell",
"rustls",
"rustls-pki-types",
"serde",
"serde_json",
"url",
"webpki-roots",
]
[[package]]
name = "url"
version = "2.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "webpki-roots"
version = "0.26.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winreg"
version = "0.52.0"
source = "git+https://github.com/phoval/winreg-rs.git#654fc8f983c91346c729198a7b58f7b76fb7a8f5"
dependencies = [
"cfg-if",
"windows-sys 0.59.0",
]
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "yoke"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

26
Cargo.toml Normal file
View file

@ -0,0 +1,26 @@
[package]
authors = ["nyyu <mail@nyyu.dev>"]
edition = "2021"
resolver = "2"
name = "cggitem_sets"
version = "1.0.0"
include = ["src/**/*"]
[dependencies]
indexmap = {version = "2.2", features = ["serde", "rayon"]}
log = "0.4"
rayon = "1.10"
serde = "1.0"
serde_derive = "1.0"
serde_json = {version = "1.0", features = ["preserve_order"]}
simple_logger = {git = 'https://github.com/phoval/rust-simple_logger.git', default-features = false, features = ["timestamps"]}
ureq = {version = "2.9", features = ["json"]}
[target.'cfg(windows)'.dependencies]
winreg = { git = 'https://github.com/phoval/winreg-rs.git' }
[profile.release]
codegen-units = 1
lto = true
opt-level = 'z'
panic = 'abort'

4
renovate.json Normal file
View file

@ -0,0 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["local>nyyu/renovate-config"]
}

View file

@ -1,2 +0,0 @@
beautifulsoup4==4.6.0
Requests==2.18.4

5
sonar-project.properties Normal file
View file

@ -0,0 +1,5 @@
sonar.projectKey=dev.nyyu:cggitem_sets
sonar.projectName=CGGItemSets
sonar.sources=src
community.rust.clippy.reportPaths=clippy-report.json
community.rust.lcov.reportPaths=lcov.info

188
src/cgg_data_source.rs Normal file
View file

@ -0,0 +1,188 @@
use indexmap::IndexMap;
use log::error;
use serde_derive::Deserialize;
use serde_json::{json, Value};
use std::sync::Mutex;
use crate::data_source::{DataSource, Stat};
use crate::ChampInfo;
use crate::Champion;
#[derive(Deserialize)]
struct BuildResponse {
lol: Lol,
}
#[derive(Deserialize)]
struct Lol {
#[serde(rename = "championsReport")]
champions_report: Vec<ChampionReport>,
}
#[derive(Deserialize)]
struct ChampionReport {
champion_id: u32,
role: String,
patch: String,
stats: Stats,
}
#[derive(Deserialize)]
struct Stats {
starting_items: Build,
core_builds: Build,
big_item_builds: Build,
skills: Build,
most_common_starting_items: Build,
most_common_core_builds: Build,
most_common_big_item_builds: Build,
most_common_skills: Build,
games: u32,
kills: u32,
deaths: u32,
wins: u32,
assists: u32,
}
#[derive(Deserialize)]
struct Build {
build: Vec<u32>,
win_rate: f32,
games: u32,
}
pub struct CGGDataSource {
champions_report: Mutex<Vec<ChampionReport>>,
}
impl CGGDataSource {
pub fn new() -> CGGDataSource {
Self {
champions_report: Mutex::new(Vec::new()),
}
}
fn make_item_set(&self, build: &Build, label: &str) -> Value {
json!({
"items": build.build.iter().map(|x| json!({"id": x.to_string(), "count": 1})).collect::<Vec<Value>>(),
"type": format!("{} ({:.2}% - {} games)", label, build.win_rate * 100., build.games)
})
}
fn make_item_set_from_list(&self, build: &Build, label: &str, skills: &Build) -> Value {
let key_order = skills
.build
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>()
.join("");
self.make_item_set(build, [label, key_order.as_str()].join(" ").as_str())
}
}
fn extract_json(pattern: &str, page: &str) -> String {
let json = page[page.find(pattern).unwrap() + pattern.len()..].to_owned();
json[..json.find("};").unwrap() + 1].replace("undefined", "null")
}
impl DataSource for CGGDataSource {
fn get_alias(&self) -> &str {
"CGG"
}
fn get_timeout(&self) -> u64 {
0
}
fn get_champs_with_positions(
&self,
client: &ureq::Agent,
_champion: &Champion,
) -> IndexMap<u32, Vec<String>> {
let mut champions: IndexMap<u32, Vec<String>> = IndexMap::new();
match client.get("https://champion.gg").call() {
Ok(req) => {
let page = &req.into_string().unwrap();
let datas: BuildResponse =
serde_json::from_str(&extract_json("window.__PRELOADED_STATE__ = ", page))
.unwrap();
for champ in &datas.lol.champions_report {
let id = champ.champion_id;
if champions.contains_key(&id) {
let mut roles = champions.get(&id).unwrap().to_owned();
roles.push(champ.role.to_owned());
champions.insert(id, roles);
} else {
champions.insert(id, vec![champ.role.to_owned()]);
}
}
for report in datas.lol.champions_report {
self.champions_report.lock().unwrap().push(report);
}
}
Err(e) => error!("Error retrieving data: {}", e),
}
champions
}
fn get_champ_data_with_win_pourcentage(
&self,
champ: &ChampInfo,
positions: &[String],
_client: &ureq::Agent,
) -> Vec<(String, Vec<Value>, Stat)> {
let mut data = vec![];
for position in positions {
let mut some_champ: Option<&ChampionReport> = None;
let reports = self.champions_report.lock().unwrap();
for champion in reports.iter() {
if champion.champion_id.to_string() == champ.key && champion.role == *position {
some_champ = Some(champion);
break;
}
}
if let Some(champ) = some_champ {
let blocks = vec![
self.make_item_set_from_list(
&champ.stats.starting_items,
"Highest % Win Starting Items | Skills: ",
&champ.stats.skills,
),
self.make_item_set(&champ.stats.core_builds, "Highest % Win Core Build Path:"),
self.make_item_set(&champ.stats.big_item_builds, "Highest % Win Big Items:"),
self.make_item_set_from_list(
&champ.stats.most_common_starting_items,
"Most Frequent Starting Items | Skills: ",
&champ.stats.most_common_skills,
),
self.make_item_set(
&champ.stats.most_common_core_builds,
"Most Frequent Build Path",
),
self.make_item_set(
&champ.stats.most_common_big_item_builds,
"Most Frequent Big Items:",
),
];
data.push((
position.to_owned(),
blocks,
Stat {
win_rate: (champ.stats.wins as f32 / champ.stats.games as f32) * 100f32,
games: champ.stats.games,
kda: (champ.stats.kills + champ.stats.assists) as f32
/ champ.stats.deaths as f32,
patch: champ.patch.to_owned(),
},
));
}
}
data
}
}

141
src/data_source.rs Normal file
View file

@ -0,0 +1,141 @@
use crate::ChampInfo;
use crate::Champion;
use indexmap::IndexMap;
use log::{error, info};
use serde_derive::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::fs;
use std::path::Path;
#[derive(Serialize, Deserialize)]
struct ItemSet {
title: String,
#[serde(rename = "type")]
type_: String,
map: String,
mode: String,
priority: bool,
sortrank: u32,
blocks: Vec<Value>,
}
#[derive(Serialize, Deserialize)]
pub struct Build {
#[serde(rename = "type")]
pub type_: String,
pub items: Vec<Item>,
}
#[derive(Serialize, Deserialize)]
pub struct Item {
pub id: String,
pub count: u8,
}
pub struct Stat {
pub win_rate: f32,
pub games: u32,
pub kda: f32,
pub patch: String,
}
pub trait DataSource {
fn get_alias(&self) -> &str;
fn get_timeout(&self) -> u64;
fn get_champs_with_positions(
&self,
client: &ureq::Agent,
champion: &Champion,
) -> IndexMap<u32, Vec<String>>;
fn make_item_set(&self, items: Vec<&str>, label: String) -> Value {
json!({
"items": items.iter().map(|x| json!({"id": x.to_string(), "count": 1})).collect::<Vec<Value>>(),
"type": label
})
}
fn get_champ_data_with_win_pourcentage(
&self,
champ: &ChampInfo,
positions: &[String],
client: &ureq::Agent,
) -> Vec<(String, Vec<Value>, Stat)>;
fn write_item_set(
&self,
champ: &ChampInfo,
positions: &[String],
path: &Path,
client: &ureq::Agent,
) {
info!(
"{}: Retrieving data for {} at {}",
self.get_alias(),
champ.name,
positions.join(", ")
);
let data = self.get_champ_data_with_win_pourcentage(champ, positions, client);
let mut missing_roles = vec![];
for pos in positions {
let mut ok = false;
for build in &data {
if build.0 == *pos {
ok = true;
break;
}
}
if !ok {
missing_roles.push(pos.to_owned());
}
}
if !missing_roles.is_empty() {
error!(
"{}: Can't get data for {} at {}",
self.get_alias(),
champ.id,
missing_roles.join(", ")
);
}
for build in data {
let item_set = ItemSet {
title: format!(
"{} {} {} - {:.2}% wins - {} games - {:.2} kda",
self.get_alias(),
build.0,
build.2.patch,
build.2.win_rate,
build.2.games,
build.2.kda
),
type_: "custom".to_string(),
map: "any".to_string(),
mode: "any".to_string(),
priority: false,
sortrank: 0,
blocks: build.1,
};
info!(
"{}: Writing item set for {} at {}",
self.get_alias(),
champ.name,
build.0
);
fs::write(
path.join(format!(
"{}_{}_{}.json",
self.get_alias(),
champ.id,
build.0
)),
serde_json::to_string_pretty(&item_set).unwrap(),
)
.unwrap();
}
}
}

399
src/kb_data_source.rs Normal file
View file

@ -0,0 +1,399 @@
use crate::data_source::{Build, DataSource, Item, Stat};
use crate::ChampInfo;
use crate::Champion as ChampionLoL;
use crate::USER_AGENT_VALUE;
use indexmap::IndexMap;
use serde_derive::Deserialize;
use serde_json::{json, Value};
use std::time::Duration;
pub struct KBDataSource {
token: Option<String>,
}
#[derive(Deserialize)]
struct ChampionResponse {
//patches: Vec<Patch>,
champions: Vec<Champion>,
}
#[derive(Deserialize)]
struct Patch {
//enabled: bool,
#[serde(rename = "patchVersion")]
patch_version: String,
//patchid: u32,
//start: String,
}
#[derive(Deserialize)]
struct Champion {
id: u32,
//name: String,
//#[serde(rename = "className")]
//classname: String,
builds: Option<Position>,
}
#[derive(Deserialize)]
struct Position {
bot: u16,
jungle: u16,
mid: u16,
support: u16,
top: u16,
}
#[derive(Deserialize)]
struct BuildResponse {
builds2: Vec<KBBuild>,
}
#[derive(Deserialize)]
struct KBBuild {
position: String,
item0: KBItem,
item1: KBItem,
item2: KBItem,
item3: KBItem,
item4: KBItem,
item5: KBItem,
item6: KBItem,
#[serde(rename = "startItem0")]
start_item0: KBItem,
#[serde(rename = "startItem1")]
start_item1: KBItem,
#[serde(rename = "startItem2")]
start_item2: KBItem,
#[serde(rename = "startItem3")]
start_item3: KBItem,
#[serde(rename = "startItem4")]
start_item4: KBItem,
#[serde(rename = "startItem5")]
start_item5: KBItem,
#[serde(rename = "skillOrder")]
skill_order: String,
wins: u32,
games: u32,
kda: f32,
summoner: Summoner,
patch: Patch,
}
#[derive(Deserialize)]
struct KBItem {
#[serde(rename = "itemId")]
item_id: u32,
//name: String,
}
#[derive(Deserialize)]
struct Summoner {
name: String,
}
// It will be better to use Result...
fn get_auth_token() -> Option<String> {
let client = ureq::AgentBuilder::new()
.user_agent(USER_AGENT_VALUE)
.timeout(Duration::from_secs(10))
.build();
let mut bundle = match client.get("https://koreanbuilds.net/bundle.js").call() {
Ok(resp) => match resp.into_string() {
Ok(val) => val,
Err(_) => return None,
},
Err(_) => return None,
};
let auth_position = match bundle.find("Authorization") {
Some(position) => position,
None => return None,
};
bundle = bundle[(auth_position + 13)..].to_string();
let q_position = match bundle.find('"') {
Some(position) => position,
None => return None,
};
bundle = bundle[(q_position + 1)..].to_string();
bundle
.find('"')
.map(|position| bundle[..position].to_string())
}
impl KBDataSource {
pub fn new() -> KBDataSource {
Self {
token: get_auth_token(),
}
}
fn get_champion_response(&self, client: &ureq::Agent) -> Option<ChampionResponse> {
if let Some(token) = &self.token {
return match client
.get("https://api.koreanbuilds.net/champions?patchid=-1")
.set("Accept", "application/json")
.set("Authorization", token.as_str())
.call()
{
Ok(resp) => resp.into_json().unwrap_or_default(),
Err(_) => None,
};
}
None
}
fn get_positions(position: Option<Position>) -> Vec<String> {
let mut positions = Vec::new();
if let Some(pos) = position {
if pos.top > 0 {
positions.push("TOP".to_owned());
}
if pos.jungle > 0 {
positions.push("JUNGLE".to_owned());
}
if pos.mid > 0 {
positions.push("MID".to_owned());
}
if pos.bot > 0 {
positions.push("BOT".to_owned());
}
if pos.support > 0 {
positions.push("SUPPORT".to_owned());
}
}
// TODO: find better solution, activate all positions for retrieve older builds
if positions.is_empty() {
positions.push("TOP".to_owned());
positions.push("JUNGLE".to_owned());
positions.push("MID".to_owned());
positions.push("BOT".to_owned());
positions.push("SUPPORT".to_owned());
}
positions
}
fn get_build(&self, build: &KBBuild) -> (String, Vec<Value>, Stat) {
let mut starting_items: Vec<Item> = vec![];
let mut blocks = vec![];
if build.start_item0.item_id != 0 {
starting_items.push(Item {
id: build.start_item0.item_id.to_string(),
count: 1,
})
}
if build.start_item1.item_id != 0 {
starting_items.push(Item {
id: build.start_item1.item_id.to_string(),
count: 1,
})
}
if build.start_item2.item_id != 0 {
starting_items.push(Item {
id: build.start_item2.item_id.to_string(),
count: 1,
})
}
if build.start_item3.item_id != 0 {
starting_items.push(Item {
id: build.start_item3.item_id.to_string(),
count: 1,
})
}
if build.start_item4.item_id != 0 {
starting_items.push(Item {
id: build.start_item4.item_id.to_string(),
count: 1,
})
}
if build.start_item5.item_id != 0 {
starting_items.push(Item {
id: build.start_item5.item_id.to_string(),
count: 1,
})
}
blocks.push(json!(Build {
type_: format!("Early game items | skillOrder : {}", build.skill_order),
items: starting_items
}));
let mut final_items: Vec<Item> = vec![];
if build.item0.item_id != 0 {
final_items.push(Item {
id: build.item0.item_id.to_string(),
count: 1,
})
}
if build.item1.item_id != 0 {
final_items.push(Item {
id: build.item1.item_id.to_string(),
count: 1,
})
}
if build.item2.item_id != 0 {
final_items.push(Item {
id: build.item2.item_id.to_string(),
count: 1,
})
}
if build.item3.item_id != 0 {
final_items.push(Item {
id: build.item3.item_id.to_string(),
count: 1,
})
}
if build.item4.item_id != 0 {
final_items.push(Item {
id: build.item4.item_id.to_string(),
count: 1,
})
}
if build.item5.item_id != 0 {
final_items.push(Item {
id: build.item5.item_id.to_string(),
count: 1,
})
}
if build.item6.item_id != 0 {
final_items.push(Item {
id: build.item6.item_id.to_string(),
count: 1,
})
}
blocks.push(json!(Build {
type_: format!(
"Item order by time finished | Summoner : {}",
build.summoner.name
),
items: final_items
}));
(
build.position.to_owned().to_uppercase(),
blocks,
Stat {
win_rate: (build.wins as f32 / build.games as f32) * 100f32,
games: build.games,
kda: build.kda,
patch: build.patch.patch_version.to_owned(),
},
)
}
}
impl DataSource for KBDataSource {
fn get_alias(&self) -> &str {
"KB"
}
fn get_timeout(&self) -> u64 {
300
}
fn get_champs_with_positions(
&self,
client: &ureq::Agent,
_champion: &ChampionLoL,
) -> IndexMap<u32, Vec<String>> {
let mut champions = IndexMap::new();
let data: ChampionResponse = match self.get_champion_response(client) {
Some(val) => val,
None => {
return champions;
}
};
for champ in data.champions {
champions.insert(champ.id, KBDataSource::get_positions(champ.builds));
}
champions
}
fn get_champ_data_with_win_pourcentage(
&self,
champ: &ChampInfo,
position: &[String],
client: &ureq::Agent,
) -> Vec<(String, Vec<Value>, Stat)> {
let mut champ_data = vec![];
if let Some(token) = &self.token {
let data: BuildResponse = match client
.get(&format!(
"https://api.koreanbuilds.net/builds?chmpname={}&patchid=-2&position=COMPOSITE",
champ.name
))
.set("Accept", "application/json")
.set("Authorization", token.as_str())
.call()
{
Ok(resp) => match resp.into_json() {
Ok(val) => val,
Err(_) => {
return vec![];
}
},
Err(_) => {
return vec![];
}
};
for pos in position {
for b in &data.builds2 {
if b.position.to_uppercase() == *pos {
champ_data.push(self.get_build(b));
break;
}
}
}
}
champ_data
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_auth_token() {
match get_auth_token() {
Some(token) => assert!(token.len() > 0),
None => assert!(false),
};
}
#[test]
fn test_get_champs_with_positions_and_patch() {
let client = ureq::AgentBuilder::new()
.user_agent(USER_AGENT_VALUE)
.timeout(Duration::from_secs(10))
.build();
let datasource = KBDataSource::new();
let champion = ChampionLoL {
data: IndexMap::new(),
};
let champs_with_positions = datasource.get_champs_with_positions(&client, &champion);
assert!(champs_with_positions.len() > 0);
}
#[test]
fn test_get_champ_data_with_win_pourcentage() {
let client = ureq::AgentBuilder::new()
.user_agent(USER_AGENT_VALUE)
.timeout(Duration::from_secs(10))
.build();
let datasource = KBDataSource::new();
let champ = ChampInfo {
id: String::from("Annie"),
name: String::from("Annie"),
key: String::from("1"),
};
let result = datasource.get_champ_data_with_win_pourcentage(
&champ,
&vec!["MID".to_string()],
&client,
);
assert!(!result.is_empty());
assert!(!result[0].1.is_empty());
assert!(result[0].2.win_rate > 0.);
}
}

256
src/main.rs Normal file
View file

@ -0,0 +1,256 @@
use indexmap::IndexMap;
#[cfg(target_os = "windows")]
use log::debug;
use log::{error, info, LevelFilter};
use rayon::prelude::*;
use serde_derive::Deserialize;
use simple_logger::SimpleLogger;
use std::env;
#[cfg(target_os = "windows")]
use std::io;
use std::io::Error;
#[cfg(target_os = "windows")]
use std::io::ErrorKind;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::time::Instant;
use std::{fs, thread, time};
use time::Duration;
#[cfg(target_os = "windows")]
use winreg::RegKey;
mod cgg_data_source;
mod data_source;
mod kb_data_source;
mod ms_data_source;
mod pb_data_source;
use cgg_data_source::CGGDataSource;
use data_source::DataSource;
use kb_data_source::KBDataSource;
use ms_data_source::MSDataSource;
use pb_data_source::PBDataSource;
#[derive(Deserialize)]
struct Realm {
v: String,
}
#[derive(Deserialize)]
pub struct Champion {
data: IndexMap<String, ChampInfo>,
}
#[derive(Deserialize)]
pub struct ChampInfo {
id: String,
name: String,
key: String,
}
const USER_AGENT_VALUE: &str =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0";
const DEFAULT_LOL_CHAMPS_DIR: &str = ".\\champs";
#[cfg(target_os = "windows")]
const REG_KEY_LOL_RADS: &str = r"SOFTWARE\WOW6432Node\Riot Games\RADS";
#[cfg(target_os = "windows")]
const REG_KEY_LOL_INC: &str = r"SOFTWARE\WOW6432Node\Riot Games, Inc\League of Legends";
#[cfg(target_os = "windows")]
const REG_KEY_WIN_64_UNINSTALL: &str =
r"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
#[cfg(target_os = "windows")]
const REG_KEY_WIN_UNINSTALL: &str = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\";
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
let mut level = LevelFilter::Info;
for s in &args {
if s.eq_ignore_ascii_case("-v") || s.eq_ignore_ascii_case("--verbose") {
level = LevelFilter::Debug;
break;
}
}
SimpleLogger::new()
.with_level(level)
.with_module_level("ureq", LevelFilter::Error)
.with_module_level("rustls", LevelFilter::Error)
.init()?;
info!("CGG Item Sets");
let lol_champs_dir: PathBuf = match lol_champ_dir() {
Ok(x) => x,
Err(_e) => PathBuf::from(DEFAULT_LOL_CHAMPS_DIR),
};
info!("LoL Champs Folder: {}", lol_champs_dir.to_str().unwrap());
let client: ureq::Agent = ureq::AgentBuilder::new()
.user_agent(USER_AGENT_VALUE)
.timeout(Duration::from_secs(10))
.build();
let realm: Realm = client
.get("https://ddragon.leagueoflegends.com/realms/euw.json")
.call()?
.into_json()?;
info!("LoL version: {}", realm.v);
let champion: Champion = client
.get(&format!(
"https://ddragon.leagueoflegends.com/cdn/{}/data/en_US/champion.json",
realm.v
))
.call()?
.into_json()?;
info!("LoL numbers of champs: {}", champion.data.len());
let data_sources: Vec<Box<dyn DataSource + Sync + Send>> = vec![
Box::new(PBDataSource),
Box::new(CGGDataSource::new()),
Box::new(KBDataSource::new()),
Box::new(MSDataSource),
];
data_sources.par_iter().for_each(|data_source| {
let init = Instant::now();
execute_data_source(data_source.deref(), &client, &champion, &lol_champs_dir);
info!(
"{}: done in {}s",
data_source.get_alias(),
init.elapsed().as_secs_f32()
);
});
Ok(())
}
fn get_champ_from_key(champs: &Champion, key: u32) -> Option<String> {
for champ in champs.data.values() {
if key.to_string() == champ.key {
return Some(champ.id.to_owned());
}
}
None
}
fn execute_data_source(
data_source: &(dyn DataSource + Sync + Send),
client: &ureq::Agent,
champion: &Champion,
lol_champs_dir: &Path,
) {
let champs = data_source.get_champs_with_positions(client, champion);
info!(
"{} numbers of champs: {}",
data_source.get_alias(),
champs.len()
);
if data_source.get_timeout() == 0 {
champs.par_iter().for_each(|(id, positions)| {
get_and_write_item_set(
data_source,
client,
champion,
lol_champs_dir,
*id,
positions,
);
});
} else {
champs.iter().for_each(|(id, positions)| {
get_and_write_item_set(
data_source,
client,
champion,
lol_champs_dir,
*id,
positions,
);
thread::sleep(Duration::from_millis(data_source.get_timeout()));
});
};
}
fn get_and_write_item_set(
data_source: &(dyn DataSource + Sync + Send),
client: &ureq::Agent,
champion: &Champion,
lol_champs_dir: &Path,
id: u32,
positions: &[String],
) {
if let Some(champ_id) = get_champ_from_key(champion, id) {
if let Some(champ) = champion.data.get(&champ_id) {
if positions.is_empty() {
error!("{}: {} empty positions", data_source.get_alias(), &champ_id);
} else {
let path = lol_champs_dir.join(&champ_id).join("Recommended");
fs::create_dir_all(&path).unwrap();
data_source.write_item_set(champ, positions, &path, client);
}
} else {
error!("{} not found in LoL champs", &champ_id);
}
}
}
#[cfg(target_os = "windows")]
fn lol_champ_dir() -> Result<PathBuf, Error> {
let hklm = RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE);
let path = if let Ok(node) = hklm.open_subkey(REG_KEY_LOL_RADS) {
debug!(
"Use registry key {} for relative champ directory",
REG_KEY_LOL_RADS
);
let val: String = node.get_value("LocalRootFolder")?;
// TODO: remplacer ce .unwrap()
PathBuf::from(val).parent().unwrap().to_path_buf()
} else if let Ok(node) = hklm.open_subkey(REG_KEY_LOL_INC) {
debug!(
"Use registry key {} for relative champ directory",
REG_KEY_LOL_INC
);
let val: String = node.get_value("Location")?;
PathBuf::from(val)
} else if let Ok(node) =
find_subnode_from_path(hklm, REG_KEY_WIN_64_UNINSTALL, "League of Legends")
{
debug!(
"Use registry key {} for relative champ directory",
REG_KEY_WIN_64_UNINSTALL
);
let val: String = node.get_value("InstallLocation")?;
PathBuf::from(val)
} else if let Ok(node) = find_subnode_from_path(
RegKey::predef(winreg::enums::HKEY_CURRENT_USER),
REG_KEY_WIN_UNINSTALL,
"Riot Game league_of_legends.live",
) {
debug!(
"Use registry key {} for relative champ directory",
REG_KEY_WIN_UNINSTALL
);
let val: String = node.get_value("InstallLocation")?;
PathBuf::from(val)
} else {
return Err(Error::from(ErrorKind::NotFound));
};
Ok(path.join("Config").join("Champions"))
}
#[cfg(not(target_os = "windows"))]
fn lol_champ_dir() -> Result<PathBuf, Error> {
Ok(PathBuf::from(DEFAULT_LOL_CHAMPS_DIR))
}
#[cfg(target_os = "windows")]
fn find_subnode_from_path(reg: RegKey, path: &str, key: &str) -> io::Result<RegKey> {
if let Ok(node) = reg.open_subkey(path) {
if let Some(k) = node
.enum_keys()
.map(|x| x.unwrap())
.find(|x| x.starts_with(key))
{
return node.open_subkey(k);
}
}
Err(Error::from(ErrorKind::NotFound))
}

121
src/ms_data_source.rs Normal file
View file

@ -0,0 +1,121 @@
use indexmap::IndexMap;
use serde_json::Value;
use crate::data_source::{DataSource, Stat};
use crate::ChampInfo;
use crate::Champion;
pub struct MSDataSource;
const CHAMP_PATTERN: &str = " href=https://www.metasrc.com/5v5/champion/";
impl DataSource for MSDataSource {
fn get_alias(&self) -> &str {
"MS"
}
fn get_timeout(&self) -> u64 {
300
}
fn get_champs_with_positions(
&self,
client: &ureq::Agent,
champion: &Champion,
) -> IndexMap<u32, Vec<String>> {
let mut champs = IndexMap::new();
let page = client
.get("https://www.metasrc.com/5v5")
.call()
.unwrap()
.into_string()
.unwrap();
let mut pos: Option<usize> = page.find(CHAMP_PATTERN);
while let Some(mut p) = pos {
p += CHAMP_PATTERN.len();
let role =
&page[p + page[p..].find('/').unwrap() + 1..p + page[p..].find('>').unwrap()];
let k = p + page[p..].find("data-search-terms-like=").unwrap() + 23;
let pipe = k + page[k..].find('|').unwrap() + 1;
let key = &page[pipe..pipe + page[pipe..].find(' ').unwrap()].replace('\"', "");
let id = champion.data.get(key).unwrap().key.parse::<u32>().unwrap();
champs.insert(id, vec![role.to_uppercase()]);
let next = page[p..].find(CHAMP_PATTERN);
if let Some(n) = next {
pos = Some(p + n);
} else {
pos = None;
}
}
champs
}
fn get_champ_data_with_win_pourcentage(
&self,
champ: &ChampInfo,
positions: &[String],
client: &ureq::Agent,
) -> Vec<(String, Vec<Value>, Stat)> {
let mut builds = vec![];
let rep = client
.get(
format!(
"https://www.metasrc.com/5v5/champion/{}/{}",
champ.id.to_lowercase(),
positions[0].to_lowercase()
)
.as_str(),
)
.call();
if let Ok(p) = rep {
let page = p.into_string().unwrap();
let mut pos = page.find("Patch ").unwrap();
let patch = &page[pos + 6..pos + 11];
pos = page.find("Win Rate:").unwrap();
let win_rate: f32 = page[pos + 26..pos + 31].parse().unwrap();
pos = page.find("KDA:").unwrap();
let kda: f32 = page[pos + 21..pos + 25].parse().unwrap();
let mut items = vec![];
let mut pos: Option<usize> = page.find("/item/");
while let Some(mut p) = pos {
p += 6;
let i = &page[p..p + page[p..].find('.').unwrap()];
items.push(i);
let next = page[p..].find("/item/");
if let Some(n) = next {
pos = Some(p + n);
} else {
pos = None;
}
}
builds.push((
positions[0].to_owned(),
vec![self.make_item_set(items, "Set".to_owned())],
Stat {
win_rate,
games: 1,
kda,
patch: patch.to_owned(),
},
));
}
builds
}
}

103
src/pb_data_source.rs Normal file
View file

@ -0,0 +1,103 @@
use indexmap::IndexMap;
use serde_derive::Deserialize;
use serde_json::Value;
use crate::data_source::{DataSource, Stat};
use crate::ChampInfo;
use crate::Champion as ChampionLoL;
pub struct PBDataSource;
#[derive(Deserialize)]
struct ChampionResponse {
champions: Vec<Champion>,
}
#[derive(Deserialize)]
struct Champion {
key: Option<String>,
}
impl DataSource for PBDataSource {
fn get_alias(&self) -> &str {
"PB"
}
fn get_timeout(&self) -> u64 {
300
}
fn get_champs_with_positions(
&self,
client: &ureq::Agent,
_champion: &ChampionLoL,
) -> IndexMap<u32, Vec<String>> {
let mut champs = IndexMap::new();
let page = client
.get("https://www.probuilds.net/ajax/championListNew")
.set("Accept", "application/json")
.call()
.unwrap()
.into_string()
.unwrap();
let data: ChampionResponse = serde_json::from_str(&page).unwrap();
for champ in data.champions {
if let Some(k) = champ.key {
champs.insert(k.parse::<u32>().unwrap(), vec!["ANY".to_owned()]);
}
}
champs
}
fn get_champ_data_with_win_pourcentage(
&self,
champ: &ChampInfo,
positions: &[String],
client: &ureq::Agent,
) -> Vec<(String, Vec<Value>, Stat)> {
let mut builds = vec![];
let rep = client
.get(format!("https://www.probuilds.net/ajax/gamesLive?limit=5&sort=gameDate-desc&championId={}&olderThan=0&lane=&region=", champ.key).as_str())
.set("Accept", "application/json")
.call();
if let Ok(page) = rep {
let rep: Vec<String> = serde_json::from_str(&page.into_string().unwrap()).unwrap();
let mut blocks = vec![];
for build in rep {
let pos = build.find("/players").unwrap();
let tmp = &build[pos..];
let name = &tmp[tmp.find('>').unwrap() + 1..tmp.find('<').unwrap()];
let mut items = vec![];
let mut build = &build[build.find("'items'").unwrap()..];
while build.contains("items") {
let pos = build.find("data-id=").unwrap();
build = &build[pos + 9..];
let end = build.find('"').unwrap();
items.push(&build[..end]);
build = &build[end + 18..];
}
blocks.push(self.make_item_set(items, name.to_string()));
}
builds.push((
positions[0].to_owned(),
blocks,
Stat {
win_rate: 0.0,
games: 1,
kda: 0.0,
patch: "".to_string(),
},
));
}
builds
}
}