mirror of
https://github.com/Steffo99/patched-porobot.git
synced 2024-12-23 01:54:22 +00:00
Merge pull request #4 from Steffo99/reqeast
Dynamic fetch of data from Riot Games' live servers
This commit is contained in:
commit
28f690b6f2
43 changed files with 310 additions and 88753 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
/target/
|
/target/
|
||||||
|
/data/
|
||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
|
37
Cargo.lock
generated
37
Cargo.lock
generated
|
@ -347,6 +347,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer",
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -652,6 +653,21 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hmac"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "htmlescape"
|
name = "htmlescape"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -1088,8 +1104,11 @@ name = "patched_porobot"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"base64 0.21.0",
|
||||||
"data-encoding",
|
"data-encoding",
|
||||||
"glob",
|
"glob",
|
||||||
|
"hex",
|
||||||
|
"hmac",
|
||||||
"itertools 0.10.5",
|
"itertools 0.10.5",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
|
@ -1101,6 +1120,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serenity",
|
"serenity",
|
||||||
|
"sha2",
|
||||||
"tantivy",
|
"tantivy",
|
||||||
"teloxide",
|
"teloxide",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -1563,6 +1583,17 @@ dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
@ -1624,6 +1655,12 @@ version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.107"
|
version = "1.0.107"
|
||||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -25,6 +25,12 @@ lazy_static = { version = "1.4.0" }
|
||||||
data-encoding = { version = "2.3.2" }
|
data-encoding = { version = "2.3.2" }
|
||||||
varint-rs = { version = "2.2.0" }
|
varint-rs = { version = "2.2.0" }
|
||||||
glob = { version = "0.3.0" }
|
glob = { version = "0.3.0" }
|
||||||
|
reqwest = { version = "0.11.11", features = ["rustls-tls", "json"], default-features = false }
|
||||||
|
# jpg
|
||||||
|
hex = { version = "0.4.3", optional = true }
|
||||||
|
base64 = { version = "0.21.0", optional = true }
|
||||||
|
hmac = { version = "0.12.1", optional = true }
|
||||||
|
sha2 = { version = "0.10.6", optional = true }
|
||||||
# exec
|
# exec
|
||||||
pretty_env_logger = { version = "0.4.0", optional = true }
|
pretty_env_logger = { version = "0.4.0", optional = true }
|
||||||
# data
|
# data
|
||||||
|
@ -34,7 +40,6 @@ serde_json = { version = "1.0.82" }
|
||||||
tantivy = { version = "0.19.1", optional = true }
|
tantivy = { version = "0.19.1", optional = true }
|
||||||
# telegram
|
# telegram
|
||||||
teloxide = { version = "0.12.0", features = ["rustls", "ctrlc_handler", "auto-send"], default-features = false, optional = true }
|
teloxide = { version = "0.12.0", features = ["rustls", "ctrlc_handler", "auto-send"], default-features = false, optional = true }
|
||||||
reqwest = { version = "0.11.11", features = ["rustls-tls"], default-features = false, optional = true }
|
|
||||||
tokio = { version = "1.20.3", features = ["rt-multi-thread", "macros"], optional = true }
|
tokio = { version = "1.20.3", features = ["rt-multi-thread", "macros"], optional = true }
|
||||||
md5 = { version = "0.7.0", optional = true }
|
md5 = { version = "0.7.0", optional = true }
|
||||||
rand = { version = "0.8.5", optional = true }
|
rand = { version = "0.8.5", optional = true }
|
||||||
|
@ -45,9 +50,11 @@ anyhow = { version = "^1.0.68", optional = true }
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
jpg = ["hmac", "sha2", "base64", "hex"]
|
||||||
|
test = ["tokio"]
|
||||||
exec = ["pretty_env_logger"]
|
exec = ["pretty_env_logger"]
|
||||||
search = ["tantivy"]
|
search = ["tantivy"]
|
||||||
telegram = ["exec", "search", "teloxide", "reqwest", "tokio", "md5", "rand"]
|
telegram = ["exec", "search", "jpg", "teloxide", "tokio", "md5", "rand"]
|
||||||
discord = ["exec", "search", "serenity", "tokio", "anyhow"]
|
discord = ["exec", "search", "serenity", "tokio", "anyhow"]
|
||||||
matrix = ["exec", "search"]
|
matrix = ["exec", "search"]
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Copyright Riot Games, Inc. 2019
|
|
|
@ -1,40 +0,0 @@
|
||||||
metadata.json
|
|
||||||
{
|
|
||||||
"locales": ["{string}", ...],
|
|
||||||
"clientHash": "{string}"
|
|
||||||
"gameplayDataHash": "{string}",
|
|
||||||
"timestamp": "{YYYYMMDDhhmm}",
|
|
||||||
"patchlineRef": "{string}"
|
|
||||||
}
|
|
||||||
|
|
||||||
cards.json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "{cardCode}",
|
|
||||||
"idComponents":
|
|
||||||
{
|
|
||||||
"set": "setNumber",
|
|
||||||
"region":
|
|
||||||
{
|
|
||||||
"id": "{shortRegionCode}",
|
|
||||||
"name": "{regionName}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"name": "{name}",
|
|
||||||
"type": "{type}",
|
|
||||||
"subType": "{subType}",
|
|
||||||
"superType": "{superType}",
|
|
||||||
"description": "{description}",
|
|
||||||
"keywords": [],
|
|
||||||
"associatedCards": [{cardCode}, ...]
|
|
||||||
"health": "{health}",
|
|
||||||
"attack": "{attack}",
|
|
||||||
"cost": "{cost}",
|
|
||||||
"assets":
|
|
||||||
{
|
|
||||||
"gameAbsolutePath": "http://{cdn}/{bundleName}/set1/en_us/img/card/game/{cardCode}.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
{...}
|
|
||||||
]
|
|
|
@ -1,743 +0,0 @@
|
||||||
{
|
|
||||||
"vocabTerms": [
|
|
||||||
{
|
|
||||||
"description": "When you summon this, it gets its allegiance bonus if the top card of your deck matches its region.",
|
|
||||||
"name": "Allegiance",
|
|
||||||
"nameRef": "Allegiance"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Automatically equips this item from hand or play when summoned, creating it first if needed.",
|
|
||||||
"name": "Auto-Equip",
|
|
||||||
"nameRef": "AutoEquip"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Pick the next Moon Weapon for Aphelios.",
|
|
||||||
"name": "Phase",
|
|
||||||
"nameRef": "Phase"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Create a random Blade Fragment still needed to restore the blade. Once you’ve played all 3, create the Blade of the Exile.",
|
|
||||||
"name": "Reforge",
|
|
||||||
"nameRef": "Reforge"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Immediately draw 1 of each Ascended ally. For the rest of the game, level 2 Ascended allies are level 3.",
|
|
||||||
"name": "Restore the Sun Disc",
|
|
||||||
"nameRef": "SunDiscRestore"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Effect when unit strikes with an attack",
|
|
||||||
"name": "Attack Strike",
|
|
||||||
"nameRef": "AttackStrike"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "For each Spawn:\nSummon a 1|1 Tentacle, or if you already have one, grant your strongest Tentacle +1|+1.",
|
|
||||||
"name": "Spawn",
|
|
||||||
"nameRef": "Spawn"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Highest Power, with ties broken by highest Health then highest Cost.",
|
|
||||||
"name": "Strongest",
|
|
||||||
"nameRef": "Strongest"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Obliterate X non-champion cards from the bottom of your deck.",
|
|
||||||
"name": "Toss",
|
|
||||||
"nameRef": "Toss"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Start a free attack with that many summoned Blades.",
|
|
||||||
"name": "Blade Dance",
|
|
||||||
"nameRef": "BladeDance"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "You behold something if you have it in play or hand.",
|
|
||||||
"name": "Behold",
|
|
||||||
"nameRef": "Behold"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "This is how much Mana you need to spend to play this card.",
|
|
||||||
"name": "Cost",
|
|
||||||
"nameRef": "Cost"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Get this effect when the round ends.",
|
|
||||||
"name": "Round End",
|
|
||||||
"nameRef": "RoundEnd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "This is how much damage the unit can withstand. If it reaches zero, the unit dies.",
|
|
||||||
"name": "Health",
|
|
||||||
"nameRef": "Health"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Choose one of two random options from a depleting pool of equipment and equip it to this ally. If the ally wasn't played from hand, it equips a random equipment instead.",
|
|
||||||
"name": "Improvise",
|
|
||||||
"nameRef": "Improvise"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "This is how much damage the unit deals when it strikes.",
|
|
||||||
"name": "Power",
|
|
||||||
"nameRef": "Power"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A card activates its Flow on Round Start if you played 2+ spells or skills last round.",
|
|
||||||
"name": "Flow",
|
|
||||||
"nameRef": "Flow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Get this effect when this unit attacks.",
|
|
||||||
"name": "Attack",
|
|
||||||
"nameRef": "Attack"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A unit is buffed when its Power or Health is increased or it gains a new keyword.",
|
|
||||||
"name": "Buffed",
|
|
||||||
"nameRef": "Buffed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "This champion counts as one of your deck's regions. During deckbuilding, you may add the specified cards to your deck regardless of region. Origins may also have an effect that begins at Start of Game.",
|
|
||||||
"name": "Origin",
|
|
||||||
"nameRef": "Origin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Get this effect when a unit attempts to deal damage using its Power, either at the end of battle or with spells. Units with 0 Power can't strike.",
|
|
||||||
"name": "Strike",
|
|
||||||
"nameRef": "Strike"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Effect when unit Strikes the enemy Nexus.",
|
|
||||||
"name": "Nexus Strike",
|
|
||||||
"nameRef": "NexusStrike"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Activates if allies have struck for 5+ damage at least 4 times this game.",
|
|
||||||
"name": "Reputation",
|
|
||||||
"nameRef": "Reputation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Equipping an Item to a unit grants it the listed bonuses. If the unit leaves play, the Item will return to your hand. You may play each item at most once per round.",
|
|
||||||
"name": "Equip",
|
|
||||||
"nameRef": "Equip"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Get this effect when the round starts.",
|
|
||||||
"name": "Round Start",
|
|
||||||
"nameRef": "RoundStart"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "The opponent in The Path of Champions.",
|
|
||||||
"name": "Foe",
|
|
||||||
"nameRef": "Foe"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Grant an ally +1|+1. If the ally is equipped, grant it to their item instead.",
|
|
||||||
"name": "Forge",
|
|
||||||
"nameRef": "Forge"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Get this effect when you play this unit from hand.",
|
|
||||||
"name": "Play",
|
|
||||||
"nameRef": "Play"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "In play, in hand, in deck, in discard, and even if created/summoned later.",
|
|
||||||
"name": "Everywhere",
|
|
||||||
"nameRef": "Everywhere"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Makes a Countdown landmark count down that many times",
|
|
||||||
"name": "Advance",
|
|
||||||
"nameRef": "Advance"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Round Start: I count down 1. At 0, activate the Countdown effect, then destroy me.",
|
|
||||||
"name": "Countdown",
|
|
||||||
"nameRef": "Countdown"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "If you don't have one, gain the attack token. You can attack this round.",
|
|
||||||
"name": "Rally",
|
|
||||||
"nameRef": "Rally"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "When you kill a unit via damage, kill effect, or striking it with an ally. (Self-killing, like from Ephemeral, doesn't count.)",
|
|
||||||
"name": "Slay",
|
|
||||||
"nameRef": "Slay"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Remove all keywords, abilities, and ongoing effects. Doesn't affect damage or subtype.",
|
|
||||||
"name": "Silence",
|
|
||||||
"nameRef": "Silence"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Create in hand 1 of 3 randomly selected cards.",
|
|
||||||
"name": "Manifest",
|
|
||||||
"nameRef": "Manifest"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Pick a card from among 3 in your deck. Shuffle the deck and put that card on top.",
|
|
||||||
"name": "Predict",
|
|
||||||
"nameRef": "Predict"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"keywords": [
|
|
||||||
{
|
|
||||||
"description": "Inflicts damage beyond what would kill the target(s) to the enemy Nexus.",
|
|
||||||
"name": "Overwhelm",
|
|
||||||
"nameRef": "SpellOverwhelm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Can be played whenever you may act. Happens instantly and allows you to continue to play other cards.",
|
|
||||||
"name": "Burst",
|
|
||||||
"nameRef": "Burst"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Round Start: I count down 1. At 0, activate the Countdown effect, then destroy me.",
|
|
||||||
"name": "Countdown",
|
|
||||||
"nameRef": "Countdown"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Get this effect when you play this unit from hand.",
|
|
||||||
"name": "Play",
|
|
||||||
"nameRef": "PlaySkillMark"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Landmarks take up a space on the board. They can't attack, block, or take damage.",
|
|
||||||
"name": "Landmark",
|
|
||||||
"nameRef": "LandmarkVisualOnly"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A Captured card is removed from the game. It returns when the Capturing unit leaves play.",
|
|
||||||
"name": "Capture",
|
|
||||||
"nameRef": "Capture"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Get this effect when this unit attacks.",
|
|
||||||
"name": "Attack",
|
|
||||||
"nameRef": "AttackSkillMark"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": " ",
|
|
||||||
"name": "Shurima",
|
|
||||||
"nameRef": "Shurima"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Attach me to an ally to give it my stats and keywords while I'm attached. When that ally leaves play, Recall me.",
|
|
||||||
"name": "Attach",
|
|
||||||
"nameRef": "Attach"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": " ",
|
|
||||||
"name": "Noxus",
|
|
||||||
"nameRef": "Noxus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Fleeting cards discard from hand when the round ends.",
|
|
||||||
"name": "Fleeting",
|
|
||||||
"nameRef": "Fleeting"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Missing Translation",
|
|
||||||
"name": "Missing Translation",
|
|
||||||
"nameRef": "ClobberNoEmptySlotRequirement"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Draw a non-champion card from the bottom of the enemy deck.",
|
|
||||||
"name": "Nab",
|
|
||||||
"nameRef": "Nab"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Can be played outside combat or when no other spells or skills are pending. Happens instantly and allows you to continue to play other cards.",
|
|
||||||
"name": "Focus",
|
|
||||||
"nameRef": "Focus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "You're Enlightened when you have 10 max mana.",
|
|
||||||
"name": "Enlightened",
|
|
||||||
"nameRef": "Enlightened"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Pick a Celestial card from among 3 to create in hand.",
|
|
||||||
"name": "Invoke",
|
|
||||||
"nameRef": "Invoke"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A card activates its Flow on Round Start if you played 2+ spells or skills last round.",
|
|
||||||
"name": "Flow",
|
|
||||||
"nameRef": "Flow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Attaches to another card in a deck. When that card is drawn, activate the effect.",
|
|
||||||
"name": "Boon",
|
|
||||||
"nameRef": "Boon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Attaches to another card in a deck. When that card is drawn, activate the effect.",
|
|
||||||
"name": "Trap",
|
|
||||||
"nameRef": "Autoplay"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Heal your Nexus for the amount of damage dealt",
|
|
||||||
"name": "Drain",
|
|
||||||
"nameRef": "Drain"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "These abilities take effect when the unit dies.",
|
|
||||||
"name": "Last Breath",
|
|
||||||
"nameRef": "LastBreath"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": " ",
|
|
||||||
"name": "Demacia",
|
|
||||||
"nameRef": "Demacia"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A champion levels up once this condition is met, even in hand or deck.",
|
|
||||||
"name": "Level Up",
|
|
||||||
"nameRef": "LevelUp"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "",
|
|
||||||
"name": "Bandle City",
|
|
||||||
"nameRef": "BandleCity"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Can be played whenever you may act. Happens after your opponent has a chance to react.",
|
|
||||||
"name": "Fast",
|
|
||||||
"nameRef": "Fast"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": " ",
|
|
||||||
"name": "Bilgewater",
|
|
||||||
"nameRef": "Bilgewater"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "",
|
|
||||||
"name": "Runeterra",
|
|
||||||
"nameRef": "Runeterra"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Return a unit to hand and remove all effects applied to it.",
|
|
||||||
"name": "Recall",
|
|
||||||
"nameRef": "Recall"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Lowest Power, with ties broken by lowest Health then lowest Cost",
|
|
||||||
"name": "Weakest",
|
|
||||||
"nameRef": "Weakest"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Attacking with a support unit will buff the unit to its right.",
|
|
||||||
"name": "Support",
|
|
||||||
"nameRef": "Support"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Can be played outside of combat when no spells or skills are pending. Happens after your opponent has a chance to react.",
|
|
||||||
"name": "Slow",
|
|
||||||
"nameRef": "Slow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Completely removed from the game. Doesn't cause Last Breath and can't be revived.",
|
|
||||||
"name": "Obliterate",
|
|
||||||
"nameRef": "Obliterate"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "These abilities trigger when you resolve a spell.",
|
|
||||||
"name": "Imbue",
|
|
||||||
"nameRef": "Imbue"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": " ",
|
|
||||||
"name": "Targon",
|
|
||||||
"nameRef": "MtTargon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": " ",
|
|
||||||
"name": "Shadow Isles",
|
|
||||||
"nameRef": "ShadowIsles"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Missing Translation",
|
|
||||||
"name": "Missing Translation",
|
|
||||||
"nameRef": "AuraVisualFakeKeyword"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Equip to a unit to grant the listed bonuses. If the unit leaves play, the equipment will return to your hand. You may play each equipment at most once per round.",
|
|
||||||
"name": "Equipment",
|
|
||||||
"nameRef": "Equipment"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": " ",
|
|
||||||
"name": "Ionia",
|
|
||||||
"nameRef": "Ionia"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Bonus if this is NOT the first card you play in a round.",
|
|
||||||
"name": "Nightfall",
|
|
||||||
"nameRef": "Nightfall"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": " ",
|
|
||||||
"name": "Piltover & Zaun",
|
|
||||||
"nameRef": "PiltoverZaun"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": " ",
|
|
||||||
"name": "Freljord",
|
|
||||||
"nameRef": "Freljord"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "When I'm summoned, refill 1 spell mana.",
|
|
||||||
"name": "Attune",
|
|
||||||
"nameRef": "Attune"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Bonus if this is the FIRST card you play in a round.",
|
|
||||||
"name": "Daybreak",
|
|
||||||
"nameRef": "Daybreak"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Can block Elusive units",
|
|
||||||
"name": "Blocks Elusive",
|
|
||||||
"nameRef": "BlocksElusive"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Missing Translation",
|
|
||||||
"name": "Missing Translation",
|
|
||||||
"nameRef": "SilenceIndividualKeyword"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A unit's spell-like effect that allows enemy reactions.",
|
|
||||||
"name": "Skill",
|
|
||||||
"nameRef": "Skill"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "A card triggers its plunder ability when played if you damaged the enemy Nexus this round.",
|
|
||||||
"name": "Plunder",
|
|
||||||
"nameRef": "Plunder"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "While attacking, it strikes both before AND at the same time as its blocker.",
|
|
||||||
"name": "Double Attack",
|
|
||||||
"nameRef": "DoubleStrike"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "The enemy can challenge this unit, forcing it to block.",
|
|
||||||
"name": "Vulnerable",
|
|
||||||
"nameRef": "Vulnerable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Can only be blocked by an Elusive unit.",
|
|
||||||
"name": "Elusive",
|
|
||||||
"nameRef": "Elusive"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Remove a unit from combat. It can't attack or block for the rest of the round.",
|
|
||||||
"name": "Stun",
|
|
||||||
"nameRef": "Stun"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Each round, the first time an allied card targets me, grant me +1|+1.",
|
|
||||||
"name": "Fated",
|
|
||||||
"nameRef": "Fated"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "When I kill a unit, grant me +1|+1.",
|
|
||||||
"name": "Fury",
|
|
||||||
"nameRef": "Fury"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Negates the next damage the unit would take. Lasts one round.",
|
|
||||||
"name": "Barrier",
|
|
||||||
"nameRef": "Barrier"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Can't attack or block.",
|
|
||||||
"name": "Immobile",
|
|
||||||
"nameRef": "Immobile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "After I die, for the rest of the game when allies attack, hallow your first attacker giving it +1|+0 that round",
|
|
||||||
"name": "Hallowed",
|
|
||||||
"nameRef": "Hallowed"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "I have +2|+2 once you've had Units with 6+ other unique positive keywords in play this game.",
|
|
||||||
"name": "Evolve",
|
|
||||||
"nameRef": "Evolve"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Set a unit's Power to 0 this round. It can be changed after.",
|
|
||||||
"name": "Frostbite",
|
|
||||||
"nameRef": "Frostbite"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Excess damage I deal to my blocker is dealt to the enemy Nexus.",
|
|
||||||
"name": "Overwhelm",
|
|
||||||
"nameRef": "Overwhelm"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "While attacking, strikes before its blocker.",
|
|
||||||
"name": "Quick Attack",
|
|
||||||
"nameRef": "QuickStrike"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Takes 1 less damage from all sources.",
|
|
||||||
"name": "Tough",
|
|
||||||
"nameRef": "Tough"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Heals fully at the end of each round.",
|
|
||||||
"name": "Regeneration",
|
|
||||||
"nameRef": "Regeneration"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Removes all text and keywords from a unit.",
|
|
||||||
"name": "Silenced",
|
|
||||||
"nameRef": "Silenced"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Negates the next enemy spell or skill that would affect me.",
|
|
||||||
"name": "SpellShield",
|
|
||||||
"nameRef": "SpellShield"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Damage this unit deals heals its Nexus that amount.",
|
|
||||||
"name": "Lifesteal",
|
|
||||||
"nameRef": "Lifesteal"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "When you play a created card, grant me +1|+0.",
|
|
||||||
"name": "Augment",
|
|
||||||
"nameRef": "Augment"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "When this strikes while attacking, it deals 1 to the enemy Nexus. This keyword can stack.",
|
|
||||||
"name": "Impact",
|
|
||||||
"nameRef": "Impact"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "The first time only Scout units attack each round, ready your attack.",
|
|
||||||
"name": "Scout",
|
|
||||||
"nameRef": "Scout"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "This unit dies when it strikes or when the round ends.",
|
|
||||||
"name": "Ephemeral",
|
|
||||||
"nameRef": "Ephemeral"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "When you attack while I'm on top of your deck, I Lurk, granting Lurker allies everywhere +1|+0. Max once per round.",
|
|
||||||
"name": "Lurk",
|
|
||||||
"nameRef": "Lurker"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "I strike with my Health instead of my Power.",
|
|
||||||
"name": "Formidable",
|
|
||||||
"nameRef": "Formidable"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Can choose which enemy unit blocks.",
|
|
||||||
"name": "Challenger",
|
|
||||||
"nameRef": "Challenger"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Can only be blocked by enemies with 3 or more Power.",
|
|
||||||
"name": "Fearsome",
|
|
||||||
"nameRef": "Fearsome"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": " ",
|
|
||||||
"name": "Can't Block",
|
|
||||||
"nameRef": "CantBlock"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "",
|
|
||||||
"name": "Deep",
|
|
||||||
"nameRef": "Deep"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"regions": [
|
|
||||||
{
|
|
||||||
"abbreviation": "NX",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-noxus.png",
|
|
||||||
"name": "Noxus",
|
|
||||||
"nameRef": "Noxus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "DE",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-demacia.png",
|
|
||||||
"name": "Demacia",
|
|
||||||
"nameRef": "Demacia"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "Jhin",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-jhin.png",
|
|
||||||
"name": "Jhin",
|
|
||||||
"nameRef": "Jhin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "Varus",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-varus.png",
|
|
||||||
"name": "Varus",
|
|
||||||
"nameRef": "Varus"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "Jax",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-jax.png",
|
|
||||||
"name": "Jax",
|
|
||||||
"nameRef": "Jax"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "Kayn",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-kayn.png",
|
|
||||||
"name": "Kayn",
|
|
||||||
"nameRef": "Kayn"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "Evelynn",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-evelynn.png",
|
|
||||||
"name": "Evelynn",
|
|
||||||
"nameRef": "Evelynn"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "Bard",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-bard.png",
|
|
||||||
"name": "Bard",
|
|
||||||
"nameRef": "Bard"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "RU",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-runeterra.png",
|
|
||||||
"name": "Runeterra",
|
|
||||||
"nameRef": "Runeterra"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "FR",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-freljord.png",
|
|
||||||
"name": "Freljord",
|
|
||||||
"nameRef": "Freljord"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "SI",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-shadowisles.png",
|
|
||||||
"name": "Shadow Isles",
|
|
||||||
"nameRef": "ShadowIsles"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "MT",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-targon.png",
|
|
||||||
"name": "Targon",
|
|
||||||
"nameRef": "Targon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "IO",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-ionia.png",
|
|
||||||
"name": "Ionia",
|
|
||||||
"nameRef": "Ionia"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "SH",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-shurima.png",
|
|
||||||
"name": "Shurima",
|
|
||||||
"nameRef": "Shurima"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "BW",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-bilgewater.png",
|
|
||||||
"name": "Bilgewater",
|
|
||||||
"nameRef": "Bilgewater"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "PZ",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-piltoverzaun.png",
|
|
||||||
"name": "Piltover & Zaun",
|
|
||||||
"nameRef": "PiltoverZaun"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abbreviation": "BC",
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/regions/icon-bandlecity.png",
|
|
||||||
"name": "Bandle City",
|
|
||||||
"nameRef": "BandleCity"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"spellSpeeds": [
|
|
||||||
{
|
|
||||||
"name": "Slow",
|
|
||||||
"nameRef": "Slow"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Burst",
|
|
||||||
"nameRef": "Burst"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Fast",
|
|
||||||
"nameRef": "Fast"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rarities": [
|
|
||||||
{
|
|
||||||
"name": "COMMON",
|
|
||||||
"nameRef": "Common"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "RARE",
|
|
||||||
"nameRef": "Rare"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "EPIC",
|
|
||||||
"nameRef": "Epic"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Champion",
|
|
||||||
"nameRef": "Champion"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "None",
|
|
||||||
"nameRef": "None"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sets": [
|
|
||||||
{
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/sets/set3_crispmip.png",
|
|
||||||
"name": "Call of the Mountain",
|
|
||||||
"nameRef": "Set3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/sets/set5_crispmip.png",
|
|
||||||
"name": "Beyond the Bandlewood",
|
|
||||||
"nameRef": "Set5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/sets/set1_crispmip.png",
|
|
||||||
"name": "Foundations",
|
|
||||||
"nameRef": "Set1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/sets/set2_crispmip.png",
|
|
||||||
"name": "Rising Tides",
|
|
||||||
"nameRef": "Set2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/sets/set6ab_crispmip.png",
|
|
||||||
"name": "Worldwalker",
|
|
||||||
"nameRef": "Set6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/sets/set4_crispmip.png",
|
|
||||||
"name": "Empires of the Ascended",
|
|
||||||
"nameRef": "Set4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/sets/setevent_crispmip.png",
|
|
||||||
"name": "Events",
|
|
||||||
"nameRef": "SetEvent"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"iconAbsolutePath": "http://dd.b.pvp.net/3_17_0/core/en_us/img/sets/set6cde_crispmip.png",
|
|
||||||
"name": "The Darkin Saga",
|
|
||||||
"nameRef": "Set6cde"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"locales": [
|
|
||||||
"en_us"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
Copyright Riot Games, Inc. 2019
|
|
|
@ -1,40 +0,0 @@
|
||||||
metadata.json
|
|
||||||
{
|
|
||||||
"locales": ["{string}", ...],
|
|
||||||
"clientHash": "{string}"
|
|
||||||
"gameplayDataHash": "{string}",
|
|
||||||
"timestamp": "{YYYYMMDDhhmm}",
|
|
||||||
"patchlineRef": "{string}"
|
|
||||||
}
|
|
||||||
|
|
||||||
cards.json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "{cardCode}",
|
|
||||||
"idComponents":
|
|
||||||
{
|
|
||||||
"set": "setNumber",
|
|
||||||
"region":
|
|
||||||
{
|
|
||||||
"id": "{shortRegionCode}",
|
|
||||||
"name": "{regionName}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"name": "{name}",
|
|
||||||
"type": "{type}",
|
|
||||||
"subType": "{subType}",
|
|
||||||
"superType": "{superType}",
|
|
||||||
"description": "{description}",
|
|
||||||
"keywords": [],
|
|
||||||
"associatedCards": [{cardCode}, ...]
|
|
||||||
"health": "{health}",
|
|
||||||
"attack": "{attack}",
|
|
||||||
"cost": "{cost}",
|
|
||||||
"assets":
|
|
||||||
{
|
|
||||||
"gameAbsolutePath": "http://{cdn}/{bundleName}/set1/en_us/img/card/game/{cardCode}.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
{...}
|
|
||||||
]
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"locales": [
|
|
||||||
"en_us"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
Copyright Riot Games, Inc. 2019
|
|
|
@ -1,40 +0,0 @@
|
||||||
metadata.json
|
|
||||||
{
|
|
||||||
"locales": ["{string}", ...],
|
|
||||||
"clientHash": "{string}"
|
|
||||||
"gameplayDataHash": "{string}",
|
|
||||||
"timestamp": "{YYYYMMDDhhmm}",
|
|
||||||
"patchlineRef": "{string}"
|
|
||||||
}
|
|
||||||
|
|
||||||
cards.json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "{cardCode}",
|
|
||||||
"idComponents":
|
|
||||||
{
|
|
||||||
"set": "setNumber",
|
|
||||||
"region":
|
|
||||||
{
|
|
||||||
"id": "{shortRegionCode}",
|
|
||||||
"name": "{regionName}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"name": "{name}",
|
|
||||||
"type": "{type}",
|
|
||||||
"subType": "{subType}",
|
|
||||||
"superType": "{superType}",
|
|
||||||
"description": "{description}",
|
|
||||||
"keywords": [],
|
|
||||||
"associatedCards": [{cardCode}, ...]
|
|
||||||
"health": "{health}",
|
|
||||||
"attack": "{attack}",
|
|
||||||
"cost": "{cost}",
|
|
||||||
"assets":
|
|
||||||
{
|
|
||||||
"gameAbsolutePath": "http://{cdn}/{bundleName}/set1/en_us/img/card/game/{cardCode}.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
{...}
|
|
||||||
]
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"locales": [
|
|
||||||
"en_us"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
Copyright Riot Games, Inc. 2019
|
|
|
@ -1,40 +0,0 @@
|
||||||
metadata.json
|
|
||||||
{
|
|
||||||
"locales": ["{string}", ...],
|
|
||||||
"clientHash": "{string}"
|
|
||||||
"gameplayDataHash": "{string}",
|
|
||||||
"timestamp": "{YYYYMMDDhhmm}",
|
|
||||||
"patchlineRef": "{string}"
|
|
||||||
}
|
|
||||||
|
|
||||||
cards.json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "{cardCode}",
|
|
||||||
"idComponents":
|
|
||||||
{
|
|
||||||
"set": "setNumber",
|
|
||||||
"region":
|
|
||||||
{
|
|
||||||
"id": "{shortRegionCode}",
|
|
||||||
"name": "{regionName}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"name": "{name}",
|
|
||||||
"type": "{type}",
|
|
||||||
"subType": "{subType}",
|
|
||||||
"superType": "{superType}",
|
|
||||||
"description": "{description}",
|
|
||||||
"keywords": [],
|
|
||||||
"associatedCards": [{cardCode}, ...]
|
|
||||||
"health": "{health}",
|
|
||||||
"attack": "{attack}",
|
|
||||||
"cost": "{cost}",
|
|
||||||
"assets":
|
|
||||||
{
|
|
||||||
"gameAbsolutePath": "http://{cdn}/{bundleName}/set1/en_us/img/card/game/{cardCode}.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
{...}
|
|
||||||
]
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"locales": [
|
|
||||||
"en_us"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
Copyright Riot Games, Inc. 2019
|
|
|
@ -1,40 +0,0 @@
|
||||||
metadata.json
|
|
||||||
{
|
|
||||||
"locales": ["{string}", ...],
|
|
||||||
"clientHash": "{string}"
|
|
||||||
"gameplayDataHash": "{string}",
|
|
||||||
"timestamp": "{YYYYMMDDhhmm}",
|
|
||||||
"patchlineRef": "{string}"
|
|
||||||
}
|
|
||||||
|
|
||||||
cards.json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "{cardCode}",
|
|
||||||
"idComponents":
|
|
||||||
{
|
|
||||||
"set": "setNumber",
|
|
||||||
"region":
|
|
||||||
{
|
|
||||||
"id": "{shortRegionCode}",
|
|
||||||
"name": "{regionName}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"name": "{name}",
|
|
||||||
"type": "{type}",
|
|
||||||
"subType": "{subType}",
|
|
||||||
"superType": "{superType}",
|
|
||||||
"description": "{description}",
|
|
||||||
"keywords": [],
|
|
||||||
"associatedCards": [{cardCode}, ...]
|
|
||||||
"health": "{health}",
|
|
||||||
"attack": "{attack}",
|
|
||||||
"cost": "{cost}",
|
|
||||||
"assets":
|
|
||||||
{
|
|
||||||
"gameAbsolutePath": "http://{cdn}/{bundleName}/set1/en_us/img/card/game/{cardCode}.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
{...}
|
|
||||||
]
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"locales": [
|
|
||||||
"en_us"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
Copyright Riot Games, Inc. 2019
|
|
|
@ -1,40 +0,0 @@
|
||||||
metadata.json
|
|
||||||
{
|
|
||||||
"locales": ["{string}", ...],
|
|
||||||
"clientHash": "{string}"
|
|
||||||
"gameplayDataHash": "{string}",
|
|
||||||
"timestamp": "{YYYYMMDDhhmm}",
|
|
||||||
"patchlineRef": "{string}"
|
|
||||||
}
|
|
||||||
|
|
||||||
cards.json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "{cardCode}",
|
|
||||||
"idComponents":
|
|
||||||
{
|
|
||||||
"set": "setNumber",
|
|
||||||
"region":
|
|
||||||
{
|
|
||||||
"id": "{shortRegionCode}",
|
|
||||||
"name": "{regionName}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"name": "{name}",
|
|
||||||
"type": "{type}",
|
|
||||||
"subType": "{subType}",
|
|
||||||
"superType": "{superType}",
|
|
||||||
"description": "{description}",
|
|
||||||
"keywords": [],
|
|
||||||
"associatedCards": [{cardCode}, ...]
|
|
||||||
"health": "{health}",
|
|
||||||
"attack": "{attack}",
|
|
||||||
"cost": "{cost}",
|
|
||||||
"assets":
|
|
||||||
{
|
|
||||||
"gameAbsolutePath": "http://{cdn}/{bundleName}/set1/en_us/img/card/game/{cardCode}.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
{...}
|
|
||||||
]
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"locales": [
|
|
||||||
"en_us"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
Copyright Riot Games, Inc. 2019
|
|
|
@ -1,40 +0,0 @@
|
||||||
metadata.json
|
|
||||||
{
|
|
||||||
"locales": ["{string}", ...],
|
|
||||||
"clientHash": "{string}"
|
|
||||||
"gameplayDataHash": "{string}",
|
|
||||||
"timestamp": "{YYYYMMDDhhmm}",
|
|
||||||
"patchlineRef": "{string}"
|
|
||||||
}
|
|
||||||
|
|
||||||
cards.json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "{cardCode}",
|
|
||||||
"idComponents":
|
|
||||||
{
|
|
||||||
"set": "setNumber",
|
|
||||||
"region":
|
|
||||||
{
|
|
||||||
"id": "{shortRegionCode}",
|
|
||||||
"name": "{regionName}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"name": "{name}",
|
|
||||||
"type": "{type}",
|
|
||||||
"subType": "{subType}",
|
|
||||||
"superType": "{superType}",
|
|
||||||
"description": "{description}",
|
|
||||||
"keywords": [],
|
|
||||||
"associatedCards": [{cardCode}, ...]
|
|
||||||
"health": "{health}",
|
|
||||||
"attack": "{attack}",
|
|
||||||
"cost": "{cost}",
|
|
||||||
"assets":
|
|
||||||
{
|
|
||||||
"gameAbsolutePath": "http://{cdn}/{bundleName}/set1/en_us/img/card/game/{cardCode}.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
{...}
|
|
||||||
]
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"locales": [
|
|
||||||
"en_us"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
Copyright Riot Games, Inc. 2019
|
|
|
@ -1,40 +0,0 @@
|
||||||
metadata.json
|
|
||||||
{
|
|
||||||
"locales": ["{string}", ...],
|
|
||||||
"clientHash": "{string}"
|
|
||||||
"gameplayDataHash": "{string}",
|
|
||||||
"timestamp": "{YYYYMMDDhhmm}",
|
|
||||||
"patchlineRef": "{string}"
|
|
||||||
}
|
|
||||||
|
|
||||||
cards.json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "{cardCode}",
|
|
||||||
"idComponents":
|
|
||||||
{
|
|
||||||
"set": "setNumber",
|
|
||||||
"region":
|
|
||||||
{
|
|
||||||
"id": "{shortRegionCode}",
|
|
||||||
"name": "{regionName}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"name": "{name}",
|
|
||||||
"type": "{type}",
|
|
||||||
"subType": "{subType}",
|
|
||||||
"superType": "{superType}",
|
|
||||||
"description": "{description}",
|
|
||||||
"keywords": [],
|
|
||||||
"associatedCards": [{cardCode}, ...]
|
|
||||||
"health": "{health}",
|
|
||||||
"attack": "{attack}",
|
|
||||||
"cost": "{cost}",
|
|
||||||
"assets":
|
|
||||||
{
|
|
||||||
"gameAbsolutePath": "http://{cdn}/{bundleName}/set1/en_us/img/card/game/{cardCode}.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
{...}
|
|
||||||
]
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"locales": [
|
|
||||||
"en_us"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -9,12 +9,14 @@ pub enum LoadingError {
|
||||||
GettingLocale,
|
GettingLocale,
|
||||||
/// Could not get the bundle name from the operating system.
|
/// Could not get the bundle name from the operating system.
|
||||||
GettingBundleName,
|
GettingBundleName,
|
||||||
/// Could not convert the bundle name from a [OsString](std::ffi::OsString) to a [String].
|
|
||||||
ConvertingBundleName,
|
|
||||||
/// Could not use [File::open](std::fs::File::open) on a data file.
|
/// Could not use [File::open](std::fs::File::open) on a data file.
|
||||||
OpeningFile(std::io::Error),
|
OpeningFile(std::io::Error),
|
||||||
/// Could not deserialize a data file.
|
/// Could not deserialize a data file.
|
||||||
Deserializing(serde_json::Error),
|
Deserializing(serde_json::Error),
|
||||||
|
/// Could not fetch a data file from a remote location.
|
||||||
|
RemoteFetching(reqwest::Error),
|
||||||
|
/// Could not deserialize a data file from a remote location.
|
||||||
|
RemoteDeserializing(reqwest::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The result of the loading of a Legends of Runeterra bundle.
|
/// The result of the loading of a Legends of Runeterra bundle.
|
||||||
|
|
|
@ -21,12 +21,6 @@ pub mod vocabterm;
|
||||||
/// [Core Bundle]: https://developer.riotgames.com/docs/lor#data-dragon_core-bundles
|
/// [Core Bundle]: https://developer.riotgames.com/docs/lor#data-dragon_core-bundles
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct CoreBundle {
|
pub struct CoreBundle {
|
||||||
/// The name of the root directory of the bundle.
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
/// The contents of the `metadata.json` file.
|
|
||||||
pub metadata: BundleMetadata,
|
|
||||||
|
|
||||||
/// The contents of the `[locale]/data/globals-[locale].json` file.
|
/// The contents of the `[locale]/data/globals-[locale].json` file.
|
||||||
pub globals: globals::LocalizedGlobalsVecs,
|
pub globals: globals::LocalizedGlobalsVecs,
|
||||||
}
|
}
|
||||||
|
@ -36,13 +30,6 @@ impl CoreBundle {
|
||||||
pub fn load(bundle_path: &Path) -> LoadingResult<Self> {
|
pub fn load(bundle_path: &Path) -> LoadingResult<Self> {
|
||||||
let metadata = BundleMetadata::load(&bundle_path.join("metadata.json"))?;
|
let metadata = BundleMetadata::load(&bundle_path.join("metadata.json"))?;
|
||||||
|
|
||||||
let name = bundle_path
|
|
||||||
.file_name()
|
|
||||||
.ok_or(LoadingError::GettingBundleName)?
|
|
||||||
.to_str()
|
|
||||||
.ok_or(LoadingError::ConvertingBundleName)?
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let locale = metadata.locale().ok_or(LoadingError::GettingLocale)?;
|
let locale = metadata.locale().ok_or(LoadingError::GettingLocale)?;
|
||||||
|
|
||||||
let globals_path = &bundle_path
|
let globals_path = &bundle_path
|
||||||
|
@ -53,13 +40,26 @@ impl CoreBundle {
|
||||||
let globals = globals::LocalizedGlobalsVecs::load(globals_path)?;
|
let globals = globals::LocalizedGlobalsVecs::load(globals_path)?;
|
||||||
|
|
||||||
Ok(CoreBundle {
|
Ok(CoreBundle {
|
||||||
name,
|
|
||||||
metadata,
|
|
||||||
globals,
|
globals,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch from `base_url` the Core Bundle data with the given `locale`.
|
||||||
|
pub async fn fetch(client: &reqwest::Client, base_url: &str, locale: &str) -> LoadingResult<Self> {
|
||||||
|
let globals = client
|
||||||
|
.get(format!("{base_url}/core/{locale}/data/globals-{locale}.json"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(LoadingError::RemoteFetching)?
|
||||||
|
.json::<globals::LocalizedGlobalsVecs>()
|
||||||
|
.await
|
||||||
|
.map_err(LoadingError::RemoteDeserializing)?;
|
||||||
|
|
||||||
|
Ok(Self {globals})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Create [`globals::LocalizedGlobalsIndexes`] from the core bundle in the current working directory.
|
/// Create [`globals::LocalizedGlobalsIndexes`] from the core bundle in the current working directory.
|
||||||
///
|
///
|
||||||
/// This function tries to load data from the first directory matching the [glob] `./data/core-*`.
|
/// This function tries to load data from the first directory matching the [glob] `./data/core-*`.
|
||||||
|
@ -71,7 +71,43 @@ pub fn create_globalindexes_from_wd() -> globals::LocalizedGlobalsIndexes {
|
||||||
.expect("a valid core bundle to exist");
|
.expect("a valid core bundle to exist");
|
||||||
|
|
||||||
let core = CoreBundle::load(&path)
|
let core = CoreBundle::load(&path)
|
||||||
.expect("to be able to load `core-en_us` bundle");
|
.expect("to be able to load CoreBundle bundle");
|
||||||
|
|
||||||
globals::LocalizedGlobalsIndexes::from(core.globals)
|
globals::LocalizedGlobalsIndexes::from(core.globals)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create [`globals::LocalizedGlobalsIndexes`] from the latest english data in Data Dragon.
|
||||||
|
///
|
||||||
|
/// This function tries to load data from `https://dd.b.pvp.net/latest`.
|
||||||
|
pub async fn create_globalindexes_from_dd_latest(locale: &str) -> globals::LocalizedGlobalsIndexes {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
log::debug!("Fetching {} CoreBundle from Data Dragon...", locale);
|
||||||
|
|
||||||
|
let core = CoreBundle::fetch(&client, "https://dd.b.pvp.net/latest", locale).await
|
||||||
|
.expect("to be able to fetch CoreBundle");
|
||||||
|
|
||||||
|
log::debug!("Fetched {} CoreBundle: it defines {} regions, {} keywords, {} rarities, {} sets, {} spell speeds, and {} vocab terms!", locale, &core.globals.regions.len(), &core.globals.keywords.len(), &core.globals.rarities.len(), &core.globals.sets.len(), &core.globals.spell_speeds.len(), &core.globals.vocab_terms.len());
|
||||||
|
|
||||||
|
globals::LocalizedGlobalsIndexes::from(core.globals)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
macro_rules! test_fetch {
|
||||||
|
( $id:ident, $version:literal, $locale:literal ) => {
|
||||||
|
#[tokio::test]
|
||||||
|
async fn $id() {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let result = crate::data::corebundle::CoreBundle::fetch(&client, &format!("https://dd.b.pvp.net/{}", $version), $locale).await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test_fetch!(test_fetch_3_17_0_en_us, "3_17_0", "en_us");
|
||||||
|
test_fetch!(test_fetch_3_17_0_it_it, "3_17_0", "it_it");
|
||||||
|
test_fetch!(test_fetch_latest_en_us, "latest", "en_us");
|
||||||
|
}
|
|
@ -896,6 +896,11 @@ mod tests {
|
||||||
deck!("CMBAGBAHANTXEBQBAUCAOFJGFIYQEAIBAUOQIBAHGM5HM6ICAECAOOYCAECRSGY"),
|
deck!("CMBAGBAHANTXEBQBAUCAOFJGFIYQEAIBAUOQIBAHGM5HM6ICAECAOOYCAECRSGY"),
|
||||||
Deck::eternal, true
|
Deck::eternal, true
|
||||||
);
|
);
|
||||||
|
test_legality!(
|
||||||
|
test_legality_eternal_paltri,
|
||||||
|
deck!("CQAAADABAICACAIFBLAACAIFAEHQCBQBEQBAGBADAQBAIAIKBUBAKBAWDUBQIBACA4GAMAIBAMCAYHJBGADAMBAOCQKRMKBLA4AQIAQ3D4QSIKZYBACAODJ3JRIW3AABQIAYUAI"),
|
||||||
|
Deck::eternal, false
|
||||||
|
);
|
||||||
|
|
||||||
test_legality!(
|
test_legality!(
|
||||||
test_legality_singleton_lonelyporo1,
|
test_legality_singleton_lonelyporo1,
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
//! Module defining [CardArt].
|
//! Module defining [CardArt].
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
/// The illustration of a [Card](super::card::Card), also referred to as an *art asset*.
|
/// The illustration of a [Card](super::card::Card), also referred to as an *art asset*.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct CardArt {
|
pub struct CardArt {
|
||||||
|
@ -30,52 +27,105 @@ pub struct CardArt {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CardArt {
|
impl CardArt {
|
||||||
/// URL to the `.jpg` image of the `en_us` locale of the rendered card, via my custom S3 mirror.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```text
|
|
||||||
/// https://objectstorage.eu-milan-1.oraclecloud.com/n/axxdmk4y92aq/b/porobot-storage/o/set1-en_us/en_us/img/cards/01DE001.jpg
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
pub fn card_jpg(&self) -> String {
|
|
||||||
lazy_static! {
|
|
||||||
static ref GET_JPG: Regex = Regex::new(
|
|
||||||
r#"https?://dd[.]b[.]pvp[.]net/[^/]+/(?P<bundle>[^/]+)/(?P<locale>[^/]+)/img/cards/(?P<code>.+)[.]png$"#
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
GET_JPG
|
/// Get the URL to convert the image at the given URL into JPG using imgproxy.
|
||||||
.replace_all(
|
#[cfg(feature = "jpg")]
|
||||||
&self.card_png,
|
fn imgproxy_convert_to_jpg(url: &str) -> String {
|
||||||
"https://objectstorage.eu-milan-1.oraclecloud.com/n/axxdmk4y92aq/b/porobot-storage/o/$bundle-$locale/$locale/img/cards/$code.jpg",
|
use base64::Engine;
|
||||||
)
|
|
||||||
.to_string()
|
let url = base64::prelude::BASE64_URL_SAFE.encode(url);
|
||||||
|
let url = format!("/{url}.jpg");
|
||||||
|
|
||||||
|
log::trace!("Created JPG conversion URL: {url}");
|
||||||
|
|
||||||
|
url
|
||||||
}
|
}
|
||||||
|
|
||||||
/// URL to the `.jpg` image of the `en_us` locale of the full card art, via my custom S3 mirror.
|
/// Add the HMAC required by imgproxy to authenticate the source of the image requester to the URL.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// ```text
|
/// If the `POROXY_KEY` or `POROXY_SALT` variables are not defined.
|
||||||
/// https://objectstorage.eu-milan-1.oraclecloud.com/n/axxdmk4y92aq/b/porobot-storage/o/set1-en_us/en_us/img/cards/01DE001-full.jpg
|
|
||||||
/// ```
|
|
||||||
///
|
///
|
||||||
pub fn full_jpg(&self) -> String {
|
#[cfg(feature = "jpg")]
|
||||||
lazy_static! {
|
fn imgproxy_authenticate_url(url: &str) -> String {
|
||||||
static ref GET_JPG: Regex = Regex::new(
|
use base64::Engine;
|
||||||
r#"https?://dd[.]b[.]pvp[.]net/[^/]+/(?P<bundle>[^/]+)/(?P<locale>[^/]+)/img/cards/(?P<code>.+)[.]png$"#
|
use hmac::Mac;
|
||||||
)
|
use std::env;
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
GET_JPG
|
let key = env::var("POROXY_KEY")
|
||||||
.replace_all(
|
.expect("POROXY_KEY to be set");
|
||||||
&self.full_png,
|
let key = hex::decode(key)
|
||||||
"https://objectstorage.eu-milan-1.oraclecloud.com/n/axxdmk4y92aq/b/porobot-storage/o/$bundle-$locale/$locale/img/cards/$code.jpg",
|
.expect("POROXY_KEY to be a valid hex code");
|
||||||
|
|
||||||
|
let salt = env::var("POROXY_SALT")
|
||||||
|
.expect("POROXY_SALT to be set");
|
||||||
|
let salt = hex::decode(salt)
|
||||||
|
.expect("POROXY_SALT to be a valid hex code");
|
||||||
|
let salt: String = String::from_utf8(salt)
|
||||||
|
.expect("salt to be a valid UTF-8 string");
|
||||||
|
|
||||||
|
let mut hmac = hmac::Hmac::<sha2::Sha256>::new_from_slice(key.as_slice())
|
||||||
|
.expect("HMAC to be initialized successfully");
|
||||||
|
hmac.update(&format!("{salt}{url}").into_bytes());
|
||||||
|
let hmac = hmac.finalize().into_bytes();
|
||||||
|
let hmac = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(hmac);
|
||||||
|
|
||||||
|
let url = format!("/{hmac}{url}");
|
||||||
|
|
||||||
|
log::trace!("Created authenticated URL: {url}");
|
||||||
|
|
||||||
|
url
|
||||||
|
}
|
||||||
|
|
||||||
|
/// URL to the `.jpg` image of the rendered card, via imgproxy.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If the `POROXY_HOST`, `POROXY_KEY` and `POROXY_SALT` variables are not defined.
|
||||||
|
///
|
||||||
|
#[cfg(feature = "jpg")]
|
||||||
|
pub fn card_jpg(&self) -> String {
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
let host = env::var("POROXY_HOST")
|
||||||
|
.expect("POROXY_HOST to be set");
|
||||||
|
|
||||||
|
let url = Self::imgproxy_authenticate_url(
|
||||||
|
&Self::imgproxy_convert_to_jpg(
|
||||||
|
&self.card_png
|
||||||
)
|
)
|
||||||
.to_string()
|
);
|
||||||
|
|
||||||
|
let url = format!("{host}{url}");
|
||||||
|
log::trace!("Accessed card_jpg: {url}");
|
||||||
|
|
||||||
|
url
|
||||||
|
}
|
||||||
|
|
||||||
|
/// URL to the `.jpg` image of the rendered card, via imgproxy.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If the `POROXY_HOST`, `POROXY_KEY` and `POROXY_SALT` variables are not defined.
|
||||||
|
///
|
||||||
|
#[cfg(feature = "jpg")]
|
||||||
|
pub fn full_jpg(&self) -> String {
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
let host = env::var("POROXY_HOST")
|
||||||
|
.expect("POROXY_HOST to be set");
|
||||||
|
|
||||||
|
let url = Self::imgproxy_authenticate_url(
|
||||||
|
&Self::imgproxy_convert_to_jpg(
|
||||||
|
&self.full_png
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let url = format!("{host}{url}");
|
||||||
|
log::trace!("Accessed full_jpg: {url}");
|
||||||
|
|
||||||
|
url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,23 +137,4 @@ mod tests {
|
||||||
fn deserialize() {
|
fn deserialize() {
|
||||||
assert_eq!(serde_json::de::from_str::<'static, CardArt>(r#"{"gameAbsolutePath": "https://dd.b.pvp.net/latest/set1/en_us/img/cards/01DE001.png", "fullAbsolutePath": "https://dd.b.pvp.net/latest/set1/en_us/img/cards/01DE001-full.png"}"#).unwrap(), CardArt { card_png: String::from("https://dd.b.pvp.net/latest/set1/en_us/img/cards/01DE001.png"), full_png: String::from("https://dd.b.pvp.net/latest/set1/en_us/img/cards/01DE001-full.png") });
|
assert_eq!(serde_json::de::from_str::<'static, CardArt>(r#"{"gameAbsolutePath": "https://dd.b.pvp.net/latest/set1/en_us/img/cards/01DE001.png", "fullAbsolutePath": "https://dd.b.pvp.net/latest/set1/en_us/img/cards/01DE001-full.png"}"#).unwrap(), CardArt { card_png: String::from("https://dd.b.pvp.net/latest/set1/en_us/img/cards/01DE001.png"), full_png: String::from("https://dd.b.pvp.net/latest/set1/en_us/img/cards/01DE001-full.png") });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn png_to_jpg() {
|
|
||||||
let art = CardArt {
|
|
||||||
card_png: String::from("https://dd.b.pvp.net/latest/set1/en_us/img/cards/01DE001.png"),
|
|
||||||
full_png: String::from(
|
|
||||||
"https://dd.b.pvp.net/latest/set1/en_us/img/cards/01DE001-full.png",
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
art.card_jpg(),
|
|
||||||
"https://objectstorage.eu-milan-1.oraclecloud.com/n/axxdmk4y92aq/b/porobot-storage/o/set1-en_us/en_us/img/cards/01DE001.jpg"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
art.full_jpg(),
|
|
||||||
"https://objectstorage.eu-milan-1.oraclecloud.com/n/axxdmk4y92aq/b/porobot-storage/o/set1-en_us/en_us/img/cards/01DE001-full.jpg"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,15 +24,10 @@ pub mod r#type;
|
||||||
///
|
///
|
||||||
/// [Data Dragon]: https://developer.riotgames.com/docs/lor#data-dragon
|
/// [Data Dragon]: https://developer.riotgames.com/docs/lor#data-dragon
|
||||||
/// [Set Bundle]: https://developer.riotgames.com/docs/lor#data-dragon_set-bundles
|
/// [Set Bundle]: https://developer.riotgames.com/docs/lor#data-dragon_set-bundles
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct SetBundle {
|
pub struct SetBundle {
|
||||||
/// The contents of the `metadata.json` file.
|
|
||||||
pub metadata: BundleMetadata,
|
|
||||||
|
|
||||||
/// The contents of the `[locale]/data/globals-[locale].json` file.
|
/// The contents of the `[locale]/data/globals-[locale].json` file.
|
||||||
pub cards: Vec<card::Card>,
|
pub cards: Vec<card::Card>,
|
||||||
|
|
||||||
/// The name of the root directory of the bundle.
|
|
||||||
pub name: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SetBundle {
|
impl SetBundle {
|
||||||
|
@ -53,22 +48,68 @@ impl SetBundle {
|
||||||
&bundle_path.join(locale).join("data").join(&json_filename)
|
&bundle_path.join(locale).join("data").join(&json_filename)
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = name
|
|
||||||
.to_str()
|
|
||||||
.ok_or(LoadingError::ConvertingBundleName)?
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let cards = File::open(data_path).map_err(LoadingError::OpeningFile)?;
|
let cards = File::open(data_path).map_err(LoadingError::OpeningFile)?;
|
||||||
|
|
||||||
let cards = serde_json::de::from_reader::<File, Vec<card::Card>>(cards)
|
let cards = serde_json::de::from_reader::<File, Vec<card::Card>>(cards)
|
||||||
.map_err(LoadingError::Deserializing)?;
|
.map_err(LoadingError::Deserializing)?;
|
||||||
|
|
||||||
Ok(SetBundle {
|
Ok(SetBundle {
|
||||||
metadata,
|
|
||||||
cards,
|
cards,
|
||||||
name,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch from `base_url` the Set Bundle data of the given `set` with the given `locale`.
|
||||||
|
pub async fn fetch(client: &reqwest::Client, base_url: &str, locale: &str, set: &str) -> LoadingResult<Self> {
|
||||||
|
let cards = client
|
||||||
|
.get(format!("{base_url}/{set}/{locale}/data/{set}-{locale}.json"))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(LoadingError::RemoteFetching)?
|
||||||
|
.json::<Vec<card::Card>>()
|
||||||
|
.await
|
||||||
|
.map_err(LoadingError::RemoteDeserializing)?;
|
||||||
|
|
||||||
|
Ok(Self {cards})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
macro_rules! test_fetch {
|
||||||
|
( $id:ident, $version:literal, $locale:literal, $set:literal ) => {
|
||||||
|
#[tokio::test]
|
||||||
|
async fn $id() {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let result = crate::data::setbundle::SetBundle::fetch(&client, &format!("https://dd.b.pvp.net/{}", $version), $locale, $set).await;
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test_fetch!(test_fetch_3_17_0_en_us_set1, "3_17_0", "en_us", "set1");
|
||||||
|
test_fetch!(test_fetch_3_17_0_en_us_set2, "3_17_0", "en_us", "set2");
|
||||||
|
test_fetch!(test_fetch_3_17_0_en_us_set3, "3_17_0", "en_us", "set3");
|
||||||
|
test_fetch!(test_fetch_3_17_0_en_us_set4, "3_17_0", "en_us", "set4");
|
||||||
|
test_fetch!(test_fetch_3_17_0_en_us_set5, "3_17_0", "en_us", "set5");
|
||||||
|
test_fetch!(test_fetch_3_17_0_en_us_set6, "3_17_0", "en_us", "set6");
|
||||||
|
test_fetch!(test_fetch_3_17_0_en_us_set6cde, "3_17_0", "en_us", "set6cde");
|
||||||
|
|
||||||
|
test_fetch!(test_fetch_3_17_0_it_it_set1, "3_17_0", "it_it", "set1");
|
||||||
|
test_fetch!(test_fetch_3_17_0_it_it_set2, "3_17_0", "it_it", "set2");
|
||||||
|
test_fetch!(test_fetch_3_17_0_it_it_set3, "3_17_0", "it_it", "set3");
|
||||||
|
test_fetch!(test_fetch_3_17_0_it_it_set4, "3_17_0", "it_it", "set4");
|
||||||
|
test_fetch!(test_fetch_3_17_0_it_it_set5, "3_17_0", "it_it", "set5");
|
||||||
|
test_fetch!(test_fetch_3_17_0_it_it_set6, "3_17_0", "it_it", "set6");
|
||||||
|
test_fetch!(test_fetch_3_17_0_it_it_set6cde, "3_17_0", "it_it", "set6cde");
|
||||||
|
|
||||||
|
test_fetch!(test_fetch_latest_en_us_set1, "latest", "en_us", "set1");
|
||||||
|
test_fetch!(test_fetch_latest_en_us_set2, "latest", "en_us", "set2");
|
||||||
|
test_fetch!(test_fetch_latest_en_us_set3, "latest", "en_us", "set3");
|
||||||
|
test_fetch!(test_fetch_latest_en_us_set4, "latest", "en_us", "set4");
|
||||||
|
test_fetch!(test_fetch_latest_en_us_set5, "latest", "en_us", "set5");
|
||||||
|
test_fetch!(test_fetch_latest_en_us_set6, "latest", "en_us", "set6");
|
||||||
|
test_fetch!(test_fetch_latest_en_us_set6cde, "latest", "en_us", "set6cde");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,3 +145,40 @@ pub fn create_cardindex_from_wd() -> card::CardIndex {
|
||||||
create_cardindex_from_paths(paths)
|
create_cardindex_from_paths(paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List of all known Data Dragon set codes.
|
||||||
|
///
|
||||||
|
/// See [the Riot Developer Portal](https://developer.riotgames.com/docs/lor#data-dragon_set-bundles) for more details.
|
||||||
|
///
|
||||||
|
/// Not related with [`set::CardSet`].
|
||||||
|
pub const DD_KNOWN_SET_CODES: [&str; 7] = [
|
||||||
|
"set1",
|
||||||
|
"set2",
|
||||||
|
"set3",
|
||||||
|
"set4",
|
||||||
|
"set5",
|
||||||
|
"set6",
|
||||||
|
"set6cde",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Create a [`card::CardIndex`] from the latest known english data in Data Dragon.
|
||||||
|
///
|
||||||
|
/// This function tries to load data from `https://dd.b.pvp.net/latest`.
|
||||||
|
pub async fn create_cardindex_from_dd_latest(locale: &str) -> card::CardIndex {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let mut index = card::CardIndex::new();
|
||||||
|
|
||||||
|
for set_code in DD_KNOWN_SET_CODES {
|
||||||
|
log::debug!("Fetching {} SetBundle with code {} from Data Dragon...", locale, &set_code);
|
||||||
|
|
||||||
|
let set = SetBundle::fetch(&client, "https://dd.b.pvp.net/latest", locale, set_code).await
|
||||||
|
.expect("to be able to fetch set bundle");
|
||||||
|
|
||||||
|
log::debug!("Fetched {} SetBundle with code {}: it defines {} cards!", locale, &set_code, set.cards.len());
|
||||||
|
|
||||||
|
for card in set.cards {
|
||||||
|
index.insert(card.code.clone(), card);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
index
|
||||||
|
}
|
|
@ -360,6 +360,7 @@ impl serenity::client::EventHandler for EventHandler {
|
||||||
log::info!("{} is ready!", &ready.user.name);
|
log::info!("{} is ready!", &ready.user.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||||
match interaction {
|
match interaction {
|
||||||
Interaction::ApplicationCommand(command) => {
|
Interaction::ApplicationCommand(command) => {
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
use log::*;
|
use log::*;
|
||||||
use serenity::prelude::*;
|
use serenity::prelude::*;
|
||||||
use crate::data::corebundle::create_globalindexes_from_wd;
|
use crate::data::corebundle::create_globalindexes_from_dd_latest;
|
||||||
use crate::data::setbundle::create_cardindex_from_wd;
|
use crate::data::setbundle::create_cardindex_from_dd_latest;
|
||||||
use crate::discord::handler::EventHandler;
|
use crate::discord::handler::EventHandler;
|
||||||
use crate::search::cardsearch::CardSearchEngine;
|
use crate::search::cardsearch::CardSearchEngine;
|
||||||
|
|
||||||
|
@ -13,12 +13,17 @@ pub async fn main() {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
debug!("Logger initialized successfully!");
|
debug!("Logger initialized successfully!");
|
||||||
|
|
||||||
|
debug!("Detecting locale to use...");
|
||||||
|
let locale = env::var("DATA_DRAGON_LOCALE")
|
||||||
|
.expect("DATA_DRAGON_LOCALE to be set");
|
||||||
|
debug!("Using {} locale!", &locale);
|
||||||
|
|
||||||
debug!("Creating LocalizedGlobalIndexes...");
|
debug!("Creating LocalizedGlobalIndexes...");
|
||||||
let globals = create_globalindexes_from_wd();
|
let globals = create_globalindexes_from_dd_latest(&locale).await;
|
||||||
debug!("Created LocalizedGlobalIndexes!");
|
debug!("Created LocalizedGlobalIndexes!");
|
||||||
|
|
||||||
debug!("Creating CardIndex...");
|
debug!("Creating CardIndex...");
|
||||||
let cards = create_cardindex_from_wd();
|
let cards = create_cardindex_from_dd_latest(&locale).await;
|
||||||
debug!("Created CardIndex!");
|
debug!("Created CardIndex!");
|
||||||
|
|
||||||
debug!("Creating CardSearchEngine...");
|
debug!("Creating CardSearchEngine...");
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
//! Module defining the [`main`] function for `patched_porobot_telegram`.
|
//! Module defining the [`main`] function for `patched_porobot_telegram`.
|
||||||
|
|
||||||
use crate::data::corebundle::create_globalindexes_from_wd;
|
use std::env;
|
||||||
use crate::data::setbundle::create_cardindex_from_wd;
|
use crate::data::corebundle::create_globalindexes_from_dd_latest;
|
||||||
|
use crate::data::setbundle::create_cardindex_from_dd_latest;
|
||||||
use crate::search::cardsearch::CardSearchEngine;
|
use crate::search::cardsearch::CardSearchEngine;
|
||||||
use crate::telegram::handler::{inline_query_handler, message_handler};
|
use crate::telegram::handler::{inline_query_handler, message_handler};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
@ -13,12 +14,17 @@ pub async fn main() {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
debug!("Logger initialized successfully!");
|
debug!("Logger initialized successfully!");
|
||||||
|
|
||||||
|
debug!("Detecting locale to use...");
|
||||||
|
let locale = env::var("DATA_DRAGON_LOCALE")
|
||||||
|
.expect("DATA_DRAGON_LOCALE to be set");
|
||||||
|
debug!("Using {} locale!", &locale);
|
||||||
|
|
||||||
debug!("Creating LocalizedGlobalIndexes...");
|
debug!("Creating LocalizedGlobalIndexes...");
|
||||||
let globals = create_globalindexes_from_wd();
|
let globals = create_globalindexes_from_dd_latest(&locale).await;
|
||||||
debug!("Created LocalizedGlobalIndexes!");
|
debug!("Created LocalizedGlobalIndexes!");
|
||||||
|
|
||||||
debug!("Creating CardIndex...");
|
debug!("Creating CardIndex...");
|
||||||
let cards = create_cardindex_from_wd();
|
let cards = create_cardindex_from_dd_latest(&locale).await;
|
||||||
debug!("Created CardIndex!");
|
debug!("Created CardIndex!");
|
||||||
|
|
||||||
debug!("Creating CardSearchEngine...");
|
debug!("Creating CardSearchEngine...");
|
||||||
|
|
Loading…
Reference in a new issue