1
Fork 0
mirror of https://github.com/Steffo99/patched-porobot.git synced 2025-01-05 00:09:43 +00:00

Compare commits

..

1 commit

Author SHA1 Message Date
cb10ae5a4b
Merge 8cc1c2b8f5 into 8c1db44f7f 2023-06-25 02:19:32 +02:00
35 changed files with 1403 additions and 10116 deletions

34
Cargo.lock generated
View file

@ -313,7 +313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
dependencies = [
"cfg-if",
"hashbrown 0.12.3",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
@ -393,12 +393,6 @@ dependencies = [
"termcolor",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "erasable"
version = "1.2.1"
@ -636,9 +630,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "h2"
version = "0.3.24"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f"
dependencies = [
"bytes",
"fnv",
@ -662,12 +656,6 @@ dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -811,12 +799,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.1.0"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"equivalent",
"hashbrown 0.14.3",
"autocfg",
"hashbrown",
]
[[package]]
@ -944,7 +932,7 @@ version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
dependencies = [
"hashbrown 0.12.3",
"hashbrown",
]
[[package]]
@ -1157,7 +1145,7 @@ dependencies = [
[[package]]
name = "patched_porobot"
version = "0.15.0"
version = "0.13.0"
dependencies = [
"anyhow",
"base64 0.21.0",
@ -2373,9 +2361,9 @@ dependencies = [
[[package]]
name = "webpki"
version = "0.22.2"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [
"ring",
"untrusted",

View file

@ -1,6 +1,6 @@
[package]
name = "patched_porobot"
version = "0.15.0"
version = "0.13.0"
authors = ["Stefano Pigozzi <me@steffo.eu>"]
edition = "2021"
description = "Legends of Runeterra card database utilities and bots"

View file

@ -1,13 +1,7 @@
<div align="center">
![](icon.png)
# Patched Porobot
# ![](icon.png) Patched Porobot
Legends of Runeterra game data crate and chat bots
</div>
## Links
[![Telegram Bot](https://img.shields.io/badge/telegram%20bot-done-success)](https://t.me/patchedporobot)
@ -15,12 +9,12 @@ Legends of Runeterra game data crate and chat bots
[![Discord Bot](https://img.shields.io/badge/discord%20bot-done-success)](https://discord.com/api/oauth2/authorize?client_id=1071989978743193672&scope=applications.commands)
![Matrix Bot](https://img.shields.io/badge/matrix%20bot-to%20do-inactive)
![Fediverse Bot](https://img.shields.io/badge/fediverse%20bot-to%20do-inactive)
[![Crates.io](https://img.shields.io/crates/v/patched_porobot)](https://crates.io/crates/patched_porobot)
[![Documentation](https://img.shields.io/docsrs/patched_porobot)](https://docs.rs/patched_porobot/latest/patched_porobot/)
[![Documentation](https://img.shields.io/docsrs/patched_porobot)](https://docs.rs/patched_porobot/0.9.2/patched_porobot/)
[![Chat](https://img.shields.io/matrix/patched_porobot:ryg.one?server_fqdn=matrix.ryg.one)](https://matrix.to/#/#patched_porobot:ryg.one)
## Screenshots
@ -33,8 +27,6 @@ Legends of Runeterra game data crate and chat bots
</details>
---
<details>
<summary>The message the bot sends when it detects an interaction from the user, such as the default /start command.</summary>
@ -42,8 +34,6 @@ Legends of Runeterra game data crate and chat bots
</details>
---
<details>
<summary>The card search prompt that appears when attempting to use the bot in a chat.</summary>
@ -51,8 +41,6 @@ Legends of Runeterra game data crate and chat bots
</details>
---
<details>
<summary>A search for "poro". Many poros are displayed, and also Braum Level 2, since it contains "poro" in its description.</summary>
@ -60,8 +48,6 @@ Legends of Runeterra game data crate and chat bots
</details>
---
<details>
<summary>The message sent when a card is clicked from the menu. It contains both the card image and a plain text render of the card (for accessibility). Additionally, the flavor text, the artist name, and a link to the full illustration are provided.</summary>
@ -69,62 +55,12 @@ Legends of Runeterra game data crate and chat bots
</details>
---
<details>
<summary>A search for a deck code, followed by "My new deck". It returns a button saying «Deck "My new deck" with 14 cards»</summary>
![](media/td-deck.png)
</details>
---
<details>
<summary>The message sent when the Deck button is clicked from the menu. It contains the name of the deck, followed by the formats it's playable in, its regions, and the cards that it contains. Champions are underlined.</summary>
![](media/td-eternal.png)
</details>
### Discord bot
<details>
<summary>The message the bot sends when an user sends <code>/help</code> command.</summary>
![](media/ds-help.png)
</details>
---
<details>
<summary>The message the bot sends when an user sends the <code>/card query: patched porobot</code> command.</summary>
![](media/ds-card.png)
</details>
---
<details>
<summary>The message the sends when an user sends the <code>/deck code: CECQCAQCA4AQIAYKAIAQGLRWAQAQECAPEUXAIAQDAEBQOCIBAIAQEMJYAA name: My new deck</code> command.</summary>
![](media/ds-deck.png)
</details>
## Licenses
### Riot Games
Patched Porobot isn't endorsed by Riot Games and doesn't reflect the views or opinions of Riot Games or anyone officially involved in producing or managing Riot Games properties. Riot Games, and all associated properties are trademarks or registered trademarks of Riot Games, Inc.
### Open Source Licenses
<details>
<summary>List of licenses as output by <code>cargo license</code></summary>
<summary>List of licenses as output by cargo license</summary>
- **(Apache-2.0 OR MIT) AND BSD-3-Clause** (1): encoding_rs
- **(MIT OR Apache-2.0) AND Unicode-DFS-2016** (1): unicode-ident
@ -147,4 +83,4 @@ Patched Porobot isn't endorsed by Riot Games and doesn't reflect the views or op
- **Unlicense** (1): measure_time
- **zlib-acknowledgement** (1): fastdivide
</details>
</details>

View file

@ -1,7 +1,7 @@
{
"vocabTerms": [
{
"description": "When you summon this unit, it gets its allegiance bonus if the top card of your deck matches its region.",
"description": "When you summon this, it gets its allegiance bonus if the top card of your deck matches its region.",
"name": "Allegiance",
"nameRef": "Allegiance"
},
@ -20,11 +20,6 @@
"name": "Reforge",
"nameRef": "Reforge"
},
{
"description": "Secretly transforms into another unit before entering play. They won't reveal their true identity to your opponent until they leave play or level up.",
"name": "Disguise",
"nameRef": "Disguise"
},
{
"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",
@ -70,21 +65,11 @@
"name": "Round End",
"nameRef": "RoundEnd"
},
{
"description": "Units with 8+ power or health",
"name": "Titanic",
"nameRef": "Titanic"
},
{
"description": "Completely removed from the game. Doesn't cause Last Breath and can't be revived.",
"name": "Obliterate",
"nameRef": "Obliterate"
},
{
"description": "Shuffle a card into your deck and reduce its cost by 1",
"name": "Updraft",
"nameRef": "Updraft"
},
{
"description": "This is how much damage the unit can withstand. If it reaches zero, the unit dies.",
"name": "Health",
@ -150,11 +135,6 @@
"name": "Round Start",
"nameRef": "RoundStart"
},
{
"description": "A unit's spell-like effect that allows enemy reactions. It's elemental!",
"name": "Elemental Skill",
"nameRef": "ElementalSkill"
},
{
"description": "The opponent in The Path of Champions.",
"name": "Foe",
@ -205,11 +185,6 @@
"name": "Manifest",
"nameRef": "Manifest"
},
{
"description": "Can be played hidden if you have no other hidden Ambush allies. Hidden allies are 2 cost 2|2s. You may pay a hidden ally's Ambush cost to transform it into its base card.",
"name": "Ambush",
"nameRef": "Ambush"
},
{
"description": "Transform allies Equipped with Darkin Equipment into their Darkin unit forms. If they are Champions, they Level Up.",
"name": "Assimilate",
@ -277,11 +252,6 @@
"name": "Fleeting",
"nameRef": "Fleeting"
},
{
"description": "A unit's spell-like effect that allows enemy reactions. It's elemental!",
"name": "Elemental Skill",
"nameRef": "ElementalSkill"
},
{
"description": "Missing Translation",
"name": "Missing Translation",
@ -453,7 +423,7 @@
"nameRef": "Skill"
},
{
"description": "A card activates its plunder ability when played if you damaged the enemy Nexus this round.",
"description": "A card triggers its plunder ability when played if you damaged the enemy Nexus this round.",
"name": "Plunder",
"nameRef": "Plunder"
},
@ -606,127 +576,115 @@
"regions": [
{
"abbreviation": "NX",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-noxus.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-noxus.png",
"name": "Noxus",
"nameRef": "Noxus"
},
{
"abbreviation": "RYZE",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-ryze.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-ryze.png",
"name": "Ryze",
"nameRef": "Ryze"
},
{
"abbreviation": "Jhin",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-jhin.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-jhin.png",
"name": "Jhin",
"nameRef": "Jhin"
},
{
"abbreviation": "Varus",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-varus.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-varus.png",
"name": "Varus",
"nameRef": "Varus"
},
{
"abbreviation": "Aatrox",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-aatrox.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-aatrox.png",
"name": "Aatrox",
"nameRef": "Aatrox"
},
{
"abbreviation": "Neeko",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-neeko.png",
"name": "Neeko",
"nameRef": "Neeko"
},
{
"abbreviation": "Jax",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-jax.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-jax.png",
"name": "Jax",
"nameRef": "Jax"
},
{
"abbreviation": "Kayn",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-kayn.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-kayn.png",
"name": "Kayn",
"nameRef": "Kayn"
},
{
"abbreviation": "POROKING",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-poroking.png",
"name": "Poro King",
"nameRef": "PoroKing"
},
{
"abbreviation": "Evelynn",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-evelynn.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-evelynn.png",
"name": "Evelynn",
"nameRef": "Evelynn"
},
{
"abbreviation": "Bard",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-bard.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-bard.png",
"name": "Bard",
"nameRef": "Bard"
},
{
"abbreviation": "DE",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-demacia.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-demacia.png",
"name": "Demacia",
"nameRef": "Demacia"
},
{
"abbreviation": "RU",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-runeterra.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-runeterra.png",
"name": "Runeterra",
"nameRef": "Runeterra"
},
{
"abbreviation": "FR",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-freljord.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-freljord.png",
"name": "Freljord",
"nameRef": "Freljord"
},
{
"abbreviation": "SI",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-shadowisles.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-shadowisles.png",
"name": "Shadow Isles",
"nameRef": "ShadowIsles"
},
{
"abbreviation": "MT",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-targon.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-targon.png",
"name": "Targon",
"nameRef": "Targon"
},
{
"abbreviation": "IO",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-ionia.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-ionia.png",
"name": "Ionia",
"nameRef": "Ionia"
},
{
"abbreviation": "SH",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-shurima.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-shurima.png",
"name": "Shurima",
"nameRef": "Shurima"
},
{
"abbreviation": "BW",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-bilgewater.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-bilgewater.png",
"name": "Bilgewater",
"nameRef": "Bilgewater"
},
{
"abbreviation": "PZ",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-piltoverzaun.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-piltoverzaun.png",
"name": "Piltover & Zaun",
"nameRef": "PiltoverZaun"
},
{
"abbreviation": "BC",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-bandlecity.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-bandlecity.png",
"name": "Bandle City",
"nameRef": "BandleCity"
}
@ -769,64 +727,54 @@
],
"sets": [
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set3_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set3_crispmip.png",
"name": "Call of the Mountain",
"nameRef": "Set3"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set5_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set5_crispmip.png",
"name": "Beyond the Bandlewood",
"nameRef": "Set5"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set1_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set1_crispmip.png",
"name": "Foundations",
"nameRef": "Set1"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set2_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set2_crispmip.png",
"name": "Rising Tides",
"nameRef": "Set2"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set6ab_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set6ab_crispmip.png",
"name": "Worldwalker",
"nameRef": "Set6"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set4_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set4_crispmip.png",
"name": "Empires of the Ascended",
"nameRef": "Set4"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set7_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set7_crispmip.png",
"name": "Glory in Navori",
"nameRef": "Set7"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set8_crispmip.png",
"name": "Fate's Voyage",
"nameRef": "Set8"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set7b_crispmip.png",
"name": "Heart of the Huntress",
"nameRef": "Set7b"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/setevent_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/setevent_crispmip.png",
"name": "Events",
"nameRef": "SetEvent"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set6cde_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set6cde_crispmip.png",
"name": "The Darkin Saga",
"nameRef": "Set6cde"
}
],
"formats": [
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/formats/queue_select_standard_toggle_active.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/formats/queue_select_standard_toggle_active.png",
"name": "Standard",
"nameRef": "client_Formats_Standard_name"
},
@ -836,7 +784,7 @@
"nameRef": "client_Deckbuilder_RulesFilters_Singleton"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/formats/queue_select_eternal_toggle_active.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/formats/queue_select_eternal_toggle_active.png",
"name": "Eternal",
"nameRef": "client_Formats_Eternal_name"
},
@ -850,27 +798,5 @@
"name": "Even Cost Cards",
"nameRef": "client_Formats_EvenCostCards_name"
}
],
"adventureRarities": [
{
"name": "COMMON",
"nameRef": "Common"
},
{
"name": "RARE",
"nameRef": "Rare"
},
{
"name": "EPIC",
"nameRef": "Epic"
},
{
"name": "LEGENDARY",
"nameRef": "Legendary"
},
{
"name": "SPECIAL",
"nameRef": "Special"
}
]
}

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
Copyright Riot Games, Inc. 2019

View file

@ -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

View file

@ -1,5 +0,0 @@
{
"locales": [
"en_us"
]
}

View file

@ -1 +0,0 @@
Copyright Riot Games, Inc. 2019

View file

@ -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

View file

@ -1,5 +0,0 @@
{
"locales": [
"en_us"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 KiB

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View file

@ -101,12 +101,6 @@ pub async fn create_globalindexes_from_dd_latest(locale: &str) -> globals::Local
#[cfg(test)]
mod tests {
use crate::data::setbundle::format::CardFormat;
use crate::data::setbundle::keyword::CardKeyword;
use crate::data::setbundle::rarity::CardRarity;
use crate::data::setbundle::region::CardRegion;
use crate::data::setbundle::set::CardSet;
use crate::data::setbundle::speed::SpellSpeed;
macro_rules! test_fetch {
( $id:ident, $version:literal, $locale:literal ) => {
#[tokio::test]
@ -118,29 +112,7 @@ mod tests {
};
}
test_fetch!(test_fetch_4_9_0_en_us, "4_9_0", "en_us");
test_fetch!(test_fetch_4_9_0_it_it, "4_9_0", "it_it");
test_fetch!(test_fetch_4_5_0_en_us, "4_5_0", "en_us");
test_fetch!(test_fetch_4_5_0_it_it, "4_5_0", "it_it");
test_fetch!(test_fetch_latest_en_us, "latest", "en_us");
macro_rules! test_supported {
( $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;
let result = result.expect("fetch request to be successful");
result.globals.keywords.iter().for_each(|o| assert_ne!(o.keyword, CardKeyword::Unsupported, "{:?} is unsupported", o));
result.globals.regions.iter().for_each(|o| assert_ne!(o.region, CardRegion::Unsupported, "{:?} is unsupported", o));
result.globals.spell_speeds.iter().for_each(|o| assert_ne!(o.spell_speed, SpellSpeed::Unsupported, "{:?} is unsupported", o));
result.globals.rarities.iter().for_each(|o| assert_ne!(o.rarity, CardRarity::Unsupported, "{:?} is unsupported", o));
result.globals.sets.iter().for_each(|o| assert_ne!(o.set, CardSet::Unsupported, "{:?} is unsupported", o));
result.globals.formats.iter().for_each(|o| assert_ne!(o.format, CardFormat::Unsupported, "{:?} is unsupported", o));
}
};
}
test_supported!(test_supported_4_9_0_en_us, "4_9_0", "en_us");
test_supported!(test_supported_4_9_0_it_it, "4_9_0", "it_it");
test_supported!(test_supported_latest_en_us, "latest", "en_us");
}
}

View file

@ -520,7 +520,9 @@ impl Deck {
///
pub fn cards_are_allowed_in(&self, cards: &CardIndex, format: CardFormat) -> bool {
self.contents.keys()
.filter_map(|cc| cc.to_card(cards))
.map(|cc| cc.to_card(&cards))
.filter(|o| o.is_some())
.map(|o| o.unwrap())
.all(|c| c.formats.contains(&format))
}

View file

@ -196,7 +196,6 @@ pub enum CardKeyword {
/// Double Attack.
///
/// > While attacking, it strikes both before AND at the same time as its blocker.
#[serde(rename = "DoubleStrike")]
DoubleAttack,
/// Vulnerable.
@ -348,30 +347,6 @@ pub enum CardKeyword {
/// > 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.
Equipment,
/// Capture.
///
/// > A Captured card is removed from the game. It returns when the Capturing unit leaves play.
Capture,
/// Attack.
///
/// > Get this effect when this unit attacks.
#[serde(rename = "AttackSkillMark")]
Attack,
/// Skill with the elemental marker.
///
/// > A unit's spell-like effect that allows enemy reactions. It's elemental!
ElementalSkill,
/// Level up!
///
/// > A champion in play levels up everywhere once this condition is met. Some champions must be in play to see their condition progress.
LevelUp,
/// ???
Freljord,
/// Unsupported card keyword.
#[serde(other)]
Unsupported,
@ -466,11 +441,6 @@ impl CardKeyword {
CardKeyword::Plunder => "",
CardKeyword::BlockElusive => "",
CardKeyword::Flow => "",
CardKeyword::Capture => "<:capture:1056024295190577153>",
CardKeyword::Attack => "",
CardKeyword::ElementalSkill => "<:elementalskill:1165762476974026814>",
CardKeyword::LevelUp => "",
CardKeyword::Freljord => "<:freljord:1056024331437735936>",
CardKeyword::Unsupported => "<:invaliddeck:1056022952396730438>",
}
}
@ -533,7 +503,7 @@ mod tests {
test_deserialization!(deserialize_silenceindividualkeyword, r#""SilenceIndividualKeyword""#, CardKeyword::SilenceIndividualKeyword);
test_deserialization!(deserialize_skill, r#""Skill""#, CardKeyword::Skill);
test_deserialization!(deserialize_plunder, r#""Plunder""#, CardKeyword::Plunder);
test_deserialization!(deserialize_doubleattack, r#""DoubleStrike""#, CardKeyword::DoubleAttack);
test_deserialization!(deserialize_doubleattack, r#""DoubleAttack""#, CardKeyword::DoubleAttack);
test_deserialization!(deserialize_vulnerable, r#""Vulnerable""#, CardKeyword::Vulnerable);
test_deserialization!(deserialize_elusive, r#""Elusive""#, CardKeyword::Elusive);
test_deserialization!(deserialize_stun, r#""Stun""#, CardKeyword::Stun);
@ -565,10 +535,5 @@ mod tests {
test_deserialization!(deserialize_deep, r#""Deep""#, CardKeyword::Deep);
test_deserialization!(deserialize_flow, r#""Flow""#, CardKeyword::Flow);
test_deserialization!(deserialize_equipment, r#""Equipment""#, CardKeyword::Equipment);
test_deserialization!(deserialize_capture, r#""Capture""#, CardKeyword::Capture);
test_deserialization!(deserialize_attack, r#""AttackSkillMark""#, CardKeyword::Attack);
test_deserialization!(deserialize_elementalskill, r#""ElementalSkill""#, CardKeyword::ElementalSkill);
test_deserialization!(deserialize_levelup, r#""LevelUp""#, CardKeyword::LevelUp);
test_deserialization!(deserialize_freljord, r#""Freljord""#, CardKeyword::Freljord);
test_deserialization!(deserialize_unsupported, r#""Xyzzy""#, CardKeyword::Unsupported);
}

View file

@ -96,27 +96,25 @@ mod tests {
};
}
test_fetch!(test_fetch_4_9_0_en_us_set1, "4_9_0", "en_us", "set1");
test_fetch!(test_fetch_4_9_0_en_us_set2, "4_9_0", "en_us", "set2");
test_fetch!(test_fetch_4_9_0_en_us_set3, "4_9_0", "en_us", "set3");
test_fetch!(test_fetch_4_9_0_en_us_set4, "4_9_0", "en_us", "set4");
test_fetch!(test_fetch_4_9_0_en_us_set5, "4_9_0", "en_us", "set5");
test_fetch!(test_fetch_4_9_0_en_us_set6, "4_9_0", "en_us", "set6");
test_fetch!(test_fetch_4_9_0_en_us_set6cde, "4_9_0", "en_us", "set6cde");
test_fetch!(test_fetch_4_9_0_en_us_set7, "4_9_0", "en_us", "set7");
test_fetch!(test_fetch_4_9_0_en_us_set7b, "4_9_0", "en_us", "set7b");
test_fetch!(test_fetch_4_9_0_en_us_set8, "4_9_0", "en_us", "set8");
test_fetch!(test_fetch_4_5_0_en_us_set1, "4_5_0", "en_us", "set1");
test_fetch!(test_fetch_4_5_0_en_us_set2, "4_5_0", "en_us", "set2");
test_fetch!(test_fetch_4_5_0_en_us_set3, "4_5_0", "en_us", "set3");
test_fetch!(test_fetch_4_5_0_en_us_set4, "4_5_0", "en_us", "set4");
test_fetch!(test_fetch_4_5_0_en_us_set5, "4_5_0", "en_us", "set5");
test_fetch!(test_fetch_4_5_0_en_us_set6, "4_5_0", "en_us", "set6");
test_fetch!(test_fetch_4_5_0_en_us_set6cde, "4_5_0", "en_us", "set6cde");
test_fetch!(test_fetch_4_5_0_en_us_set7, "4_5_0", "en_us", "set7");
// test_fetch!(test_fetch_4_5_0_en_us_set7b, "4_5_0", "en_us", "set7b"); // TODO: Unignore me when set releases
test_fetch!(test_fetch_4_9_0_it_it_set1, "4_9_0", "it_it", "set1");
test_fetch!(test_fetch_4_9_0_it_it_set2, "4_9_0", "it_it", "set2");
test_fetch!(test_fetch_4_9_0_it_it_set3, "4_9_0", "it_it", "set3");
test_fetch!(test_fetch_4_9_0_it_it_set4, "4_9_0", "it_it", "set4");
test_fetch!(test_fetch_4_9_0_it_it_set5, "4_9_0", "it_it", "set5");
test_fetch!(test_fetch_4_9_0_it_it_set6, "4_9_0", "it_it", "set6");
test_fetch!(test_fetch_4_9_0_it_it_set6cde, "4_9_0", "it_it", "set6cde");
test_fetch!(test_fetch_4_9_0_it_it_set7, "4_9_0", "it_it", "set7");
test_fetch!(test_fetch_4_9_0_it_it_set7b, "4_9_0", "it_it", "set7b");
test_fetch!(test_fetch_4_9_0_it_it_set8, "4_9_0", "it_it", "set8");
test_fetch!(test_fetch_4_5_0_it_it_set1, "4_5_0", "it_it", "set1");
test_fetch!(test_fetch_4_5_0_it_it_set2, "4_5_0", "it_it", "set2");
test_fetch!(test_fetch_4_5_0_it_it_set3, "4_5_0", "it_it", "set3");
test_fetch!(test_fetch_4_5_0_it_it_set4, "4_5_0", "it_it", "set4");
test_fetch!(test_fetch_4_5_0_it_it_set5, "4_5_0", "it_it", "set5");
test_fetch!(test_fetch_4_5_0_it_it_set6, "4_5_0", "it_it", "set6");
test_fetch!(test_fetch_4_5_0_it_it_set6cde, "4_5_0", "it_it", "set6cde");
test_fetch!(test_fetch_4_5_0_it_it_set7, "4_5_0", "it_it", "set7");
// test_fetch!(test_fetch_4_5_0_it_it_set7b, "4_5_0", "it_it", "set7b"); // TODO: Unignore me when set releases
test_fetch!(test_fetch_latest_en_us_set1, "latest", "en_us", "set1");
test_fetch!(test_fetch_latest_en_us_set2, "latest", "en_us", "set2");
@ -127,7 +125,6 @@ mod tests {
test_fetch!(test_fetch_latest_en_us_set6cde, "latest", "en_us", "set6cde");
test_fetch!(test_fetch_latest_en_us_set7, "latest", "en_us", "set7");
test_fetch!(test_fetch_latest_en_us_set7b, "latest", "en_us", "set7b");
test_fetch!(test_fetch_latest_en_us_set8, "latest", "en_us", "set8");
}

View file

@ -3,7 +3,6 @@
use crate::data::corebundle::rarity::{LocalizedCardRarity, LocalizedCardRarityIndex};
/// A possible [Card](super::card::Card) rarity.
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum CardRarity {
/// The card has no rarity, as it probably is not [collectible](super::card::Card::collectible).
@ -20,10 +19,6 @@ pub enum CardRarity {
/// A champion (orange hexagon) card, sometimes referred to as *Legendary*.
Champion,
/// Unsupported rarity.
#[serde(other)]
Unsupported,
}
impl CardRarity {
@ -38,32 +33,6 @@ impl CardRarity {
) -> Option<&'hm LocalizedCardRarity> {
hm.get(self)
}
/// Get the Discord emoji code associated with this [`CardRarity`].
pub fn discord_emoji(&self) -> &'static str {
match self {
CardRarity::None => "",
CardRarity::Common => "<:common:1056024315046412358>",
CardRarity::Rare => "<:rare:1056022907433799690>",
CardRarity::Epic => "<:epic:1056023004028608622>",
CardRarity::Champion => "<:champion:1056024303856001034>",
CardRarity::Unsupported => "",
}
}
/// Get the color associated with this [`CardRarity`].
///
/// Used for example to determine the color of the Discord embed.
pub fn color(&self) -> u32 {
match self {
CardRarity::None => 0x202225,
CardRarity::Common => 0x1e6a49,
CardRarity::Rare => 0x244778,
CardRarity::Epic => 0x502970,
CardRarity::Champion => 0x81541f,
CardRarity::Unsupported => 0xff0000,
}
}
}
#[cfg(test)]
@ -87,5 +56,9 @@ mod tests {
test_deserialization!(deserialize_rare, r#""Rare""#, CardRarity::Rare);
test_deserialization!(deserialize_epic, r#""Epic""#, CardRarity::Epic);
test_deserialization!(deserialize_champion, r#""Champion""#, CardRarity::Champion);
test_deserialization!(deserialize_unsupported, r#""Xyzzy""#, CardRarity::Unsupported);
#[test]
fn deserialize_fallback() {
assert!(serde_json::de::from_str::<'static, CardRarity>("Xyzzy").is_err());
}
}

View file

@ -32,28 +32,6 @@ pub enum CardRegion {
/// Runeterra.
Runeterra,
/// Runeterra: Ryze.
Ryze,
/// Runeterra: Jhin.
Jhin,
/// Runeterra: Varus.
Varus,
/// Runeterra: Aatrox.
Aatrox,
/// Runeterra: Neeko.
Neeko,
/// Runeterra: Jax.
Jax,
/// Runeterra: Kayn.
Kayn,
/// Runeterra: Poro King.
PoroKing,
/// Runeterra: Evelynn.
Evelynn,
/// Runeterra: Bard.
Bard,
/// Unsupported region.
#[serde(other)]
Unsupported,
@ -112,30 +90,6 @@ impl CardRegion {
}
}
/// Get the long code of this [`CardRegion`].
///
/// If the region has no short code, it will return [`None`].
///
/// Used for deck hashtags in the Telegram bot.
pub fn to_tag(&self) -> Option<&'static str> {
match self {
Self::Demacia => Some("Demacia"),
Self::Freljord => Some("Freljord"),
Self::Ionia => Some("Ionia"),
Self::Noxus => Some("Noxus"),
Self::PiltoverZaun => Some("PiltoverZaun"),
Self::ShadowIsles => Some("ShadowIsles"),
Self::Bilgewater => Some("Bilgewater"),
Self::Shurima => Some("Shurima"),
Self::Targon => Some("Targon"),
Self::BandleCity => Some("BandleCity"),
Self::Runeterra => Some("Runeterra"),
_ => None,
}
}
/// Get the human friendly
/// Get the Discord emoji code associated with this [`CardRegion`].
pub fn discord_emoji(&self) -> &'static str {
match self {
@ -150,16 +104,6 @@ impl CardRegion {
CardRegion::PiltoverZaun => "<:piltoverzaun:1056022918959734835>",
CardRegion::BandleCity => "<:bandlecity:1056024280493735976>",
CardRegion::Runeterra => "<:runeterra:1056022895031238727>",
CardRegion::Ryze => "",
CardRegion::Jhin => "",
CardRegion::Varus => "",
CardRegion::Aatrox => "",
CardRegion::Neeko => "",
CardRegion::Jax => "",
CardRegion::Kayn => "",
CardRegion::PoroKing => "",
CardRegion::Evelynn => "",
CardRegion::Bard => "",
CardRegion::Unsupported => "<:invaliddeck:1056022952396730438>",
}
}
@ -236,15 +180,5 @@ mod tests {
test_deserialization!(deserialize_piltoverzaun, r#""PiltoverZaun""#, CardRegion::PiltoverZaun);
test_deserialization!(deserialize_bandlecity, r#""BandleCity""#, CardRegion::BandleCity);
test_deserialization!(deserialize_runeterra, r#""Runeterra""#, CardRegion::Runeterra);
test_deserialization!(deserialize_runeterra_ryze, r#""Ryze""#, CardRegion::Ryze);
test_deserialization!(deserialize_runeterra_jhin, r#""Jhin""#, CardRegion::Jhin);
test_deserialization!(deserialize_runeterra_varus, r#""Varus""#, CardRegion::Varus);
test_deserialization!(deserialize_runeterra_aatrox, r#""Aatrox""#, CardRegion::Aatrox);
test_deserialization!(deserialize_runeterra_neeko, r#""Neeko""#, CardRegion::Neeko);
test_deserialization!(deserialize_runeterra_jax, r#""Jax""#, CardRegion::Jax);
test_deserialization!(deserialize_runeterra_kayn, r#""Kayn""#, CardRegion::Kayn);
test_deserialization!(deserialize_runeterra_poroking, r#""PoroKing""#, CardRegion::PoroKing);
test_deserialization!(deserialize_runeterra_evelynn, r#""Evelynn""#, CardRegion::Evelynn);
test_deserialization!(deserialize_runeterra_bard, r#""Bard""#, CardRegion::Bard);
test_deserialization!(deserialize_fallback, r#""Xyzzy""#, CardRegion::Unsupported);
}

View file

@ -40,14 +40,6 @@ pub enum CardSet {
#[serde(rename = "Set7")]
GloryInNavori,
/// Heart of the Huntress.
#[serde(rename = "Set7b")]
HeartOfTheHuntress,
/// Fate's Voyage.
#[serde(rename = "Set8")]
FatesVoyage,
/// Events, cards released "outside" a set.
#[serde(rename = "SetEvent")]
Events,
@ -102,24 +94,6 @@ impl CardSet {
_ => None,
}
}
/// Get the Discord emoji code associated with this [`CardSet`].
pub fn discord_emoji(&self) -> &'static str {
match self {
CardSet::Foundations => "<:foundations:1071644734667366410>",
CardSet::RisingTides => "<:rising_tides:1071644736126976160>",
CardSet::CallOfTheMountain => "<:call_of_the_mountain:1071644738555478076>",
CardSet::EmpiresOfTheAscended => "<:empires_of_the_ascended:1071644740342255616>",
CardSet::BeyondTheBandlewood => "<:beyond_the_bandlewood:1071644742640750734>",
CardSet::Worldwalker => "<:worldwalker:1071644743798370315>",
CardSet::TheDarkinSaga => "<:the_darkin_saga:1071644746411417610>",
CardSet::GloryInNavori => "<:glory_in_navori:1095363395890458756>",
CardSet::HeartOfTheHuntress => "<:heart_of_the_huntress:1165769749922320494>",
CardSet::FatesVoyage => "<:fates_voyage:1165769932995317851>",
CardSet::Events => "",
CardSet::Unsupported => "<:invaliddeck:1056022952396730438>",
}
}
}
/// Get the [`CardSet`] from its internal id.
@ -183,9 +157,6 @@ mod tests {
test_deserialization!(deserialize_set5, r#""Set5""#, CardSet::BeyondTheBandlewood);
test_deserialization!(deserialize_set6, r#""Set6""#, CardSet::Worldwalker);
test_deserialization!(deserialize_set6cde, r#""Set6cde""#, CardSet::TheDarkinSaga);
test_deserialization!(deserialize_set7, r#""Set7""#, CardSet::GloryInNavori);
test_deserialization!(deserialize_set7b, r#""Set7b""#, CardSet::HeartOfTheHuntress);
test_deserialization!(deserialize_set8, r#""Set8""#, CardSet::FatesVoyage);
test_deserialization!(deserialize_setevent, r#""SetEvent""#, CardSet::Events);
test_deserialization!(deserialize_fallback, r#""Xyzzy""#, CardSet::Unsupported);
}

View file

@ -3,7 +3,6 @@
use crate::data::corebundle::speed::{LocalizedSpellSpeed, LocalizedSpellSpeedIndex};
/// A possible [`Spell`](super::type::CardType::Spell) speed.
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum SpellSpeed {
/// Non-spell cards have this speed.
@ -15,9 +14,6 @@ pub enum SpellSpeed {
Fast,
/// Either a Burst or a Focus spell; to disambiguate between the two, check for the `Focus` keyword.
Burst,
/// Unsupported spell speed.
#[serde(other)]
Unsupported,
}
impl SpellSpeed {
@ -54,5 +50,9 @@ mod tests {
test_deserialization!(deserialize_slow, r#""Slow""#, SpellSpeed::Slow);
test_deserialization!(deserialize_fast, r#""Fast""#, SpellSpeed::Fast);
test_deserialization!(deserialize_burst, r#""Burst""#, SpellSpeed::Burst);
test_deserialization!(deserialize_unsupported, r#""Xyzzy""#, SpellSpeed::Unsupported);
#[test]
fn deserialize_fallback() {
assert!(serde_json::de::from_str::<'static, SpellSpeed>("Xyzzy").is_err());
}
}

View file

@ -9,9 +9,6 @@ pub enum CardType {
/// A spell.
Spell,
/// A equipment.
Equipment,
/// An unit: either a minion, or a champion.
///
/// Champions have their [supertype](super::card::Card::supertype) set to `Champion`, and their [rarity](super::card::Card::rarity) set to [CardRarity::Champion](super::rarity::CardRarity::Champion) as well.
@ -36,7 +33,6 @@ pub enum CardType {
impl From<&CardType> for &'static str {
fn from(r#type: &CardType) -> Self {
match r#type {
CardType::Equipment => "Equipment",
CardType::Spell => "Spell",
CardType::Unit => "Unit",
CardType::Ability => "Ability",

View file

@ -12,6 +12,8 @@ use crate::data::deckcode::deck::Deck;
use crate::data::deckcode::format::DeckCodeFormat;
use crate::data::setbundle::r#type::CardType;
use crate::data::setbundle::rarity::CardRarity;
use crate::data::setbundle::region::CardRegion;
use crate::data::setbundle::set::CardSet;
use crate::data::setbundle::supertype::CardSupertype;
use crate::search::cardsearch::CardSearchEngine;
@ -92,11 +94,13 @@ impl EventHandler {
}
if !card.keywords.is_empty() {
e.field("Keywords", card.keywords.iter().map(|r| {
let icon = r.discord_emoji();
let text = r.localized(&engine.globals.keywords).map_or_else(|| String::from("Unknown"), |l| l.name.clone());
format!("{icon} {text}")
}).join(", "), true);
e.field("Keywords", card.keywords.iter().map(|r|
format!(
"{} {}",
r.discord_emoji(),
r.localized(&engine.globals.keywords).map_or_else(|| String::from("Missing translation"), |l| l.name.clone())
)
).join(", "), true);
}
e.field("Mana cost", format!("{} mana", card.cost), true);
@ -117,30 +121,56 @@ impl EventHandler {
vec
}.join(", "), true);
e.field("Regions", card.regions.iter().map(|r| {
let icon = r.discord_emoji();
let text = r.localized(&engine.globals.regions).map_or_else(|| String::from("Unknown"), |r| r.name.clone());
format!("{icon} {text}")
e.field("Regions", card.regions.iter().map(|r| match r {
CardRegion::Noxus => "<:noxus:1056022924169064498> Noxus",
CardRegion::Demacia => "<:demacia:1056023014128484412> Demacia",
CardRegion::Freljord => "<:freljord:1056024331437735936> Freljord",
CardRegion::ShadowIsles => "<:shadowisles:1056022886848135292> Shadow Isles",
CardRegion::Targon => "<:targon:1056022866174418944> Targon",
CardRegion::Ionia => "<:ionia:1056022949569777708> Ionia",
CardRegion::Bilgewater => "<:bilgewater:1056024288215437484> Bilgewater",
CardRegion::Shurima => "<:shurima:1056022884616765500> Shurima",
CardRegion::PiltoverZaun => "<:piltoverzaun:1056022918959734835> Piltover & Zaun",
CardRegion::BandleCity => "<:bandlecity:1056024280493735976> Bandle City",
CardRegion::Runeterra => "<:runeterra:1056022895031238727> Runeterra",
CardRegion::Unsupported => "<:invaliddeck:1056022952396730438> Unknown",
}).join(", "), false);
e.field("Set", {
let icon = card.set.discord_emoji();
let text = card.set.localized(&engine.globals.sets).map_or_else(|| String::from("Unknown"), |r| r.name.clone());
format!("{icon} {text}")
e.field("Set", match card.set {
CardSet::Foundations => "<:foundations:1071644734667366410> Foundations",
CardSet::RisingTides => "<:rising_tides:1071644736126976160> Rising Tides",
CardSet::CallOfTheMountain => "<:call_of_the_mountain:1071644738555478076> Call of the Mountain",
CardSet::EmpiresOfTheAscended => "<:empires_of_the_ascended:1071644740342255616> Empires of the Ascended",
CardSet::BeyondTheBandlewood => "<:beyond_the_bandlewood:1071644742640750734> Beyond the Bandlewood",
CardSet::Worldwalker => "<:worldwalker:1071644743798370315> Worldwalker",
CardSet::TheDarkinSaga => "<:the_darkin_saga:1071644746411417610> The Darkin Saga",
CardSet::GloryInNavori => "<:glory_in_navori:1095363395890458756>
Glory in Navori",
CardSet::Events => "Events", // TODO: Add icon
CardSet::Unsupported => "<:invaliddeck:1056022952396730438> Unknown",
}, true);
let actual_rarity = match card.supertype {
CardSupertype::Champion => CardRarity::Champion,
_ => card.rarity
};
e.field("Rarity", match card.supertype {
CardSupertype::Champion => "<:champion:1056024303856001034> Champion",
_ => match card.rarity {
CardRarity::None => "None",
CardRarity::Common => "<:common:1056024315046412358> Common",
CardRarity::Rare => "<:rare:1056022907433799690> Rare",
CardRarity::Epic => "<:epic:1056023004028608622> Epic",
CardRarity::Champion => "<:champion:1056024303856001034> Champion",
}
}, true);
e.field("Rarity", {
let icon = actual_rarity.discord_emoji();
let text = actual_rarity.localized(&engine.globals.rarities).map_or_else(|| String::from("Unknown"), |r| r.name.clone());
format!("{icon} {text}")
} , true);
e.color(actual_rarity.color());
e.color(match card.supertype {
CardSupertype::Champion => 0x81541f,
_ => match card.rarity {
CardRarity::None => 0x202225,
CardRarity::Common => 0x1e6a49,
CardRarity::Rare => 0x244778,
CardRarity::Epic => 0x502970,
CardRarity::Champion => 0x81541f,
}
});
if !card.localized_flavor_text.is_empty() {
e.footer(|f| f.text(card.localized_flavor_text.clone()));

View file

@ -35,7 +35,7 @@ pub struct CardSearchEngine {
}
impl CardSearchEngine {
/// Create the [TextAnalyzer] for card text.
/// Create the [tantivy::tokenizer::TextAnalyzer] for card text.
///
/// It should not alter text significantly, as it may contain important game vocabulary terms.
fn tokenizer() -> TextAnalyzer {
@ -44,7 +44,7 @@ impl CardSearchEngine {
TextAnalyzer::from(SimpleTokenizer).filter(LowerCaser)
}
/// Create the [TextOptions] for card codes.
/// Create the [tantivy::schema::TextOptions] for card codes.
///
/// Card codes should:
/// - TODO: be tokenized without alterations;
@ -63,7 +63,7 @@ impl CardSearchEngine {
.set_fast()
}
/// Create the [TextOptions] for card keywords.
/// Create the [tantivy::schema::TextOptions] for card keywords.
///
/// Card keywords should:
/// - be tokenized with the [CardSearchEngine::tokenizer];
@ -78,7 +78,7 @@ impl CardSearchEngine {
)
}
/// Create the [TextOptions] for card text fields.
/// Create the [tantivy::schema::TextOptions] for card text fields.
///
/// Card text should:
/// - TODO: be tokenized with the tokenizer for the locale language;
@ -93,7 +93,7 @@ impl CardSearchEngine {
)
}
/// Create the [NumericOptions] for card numeric fields.
/// Create the [tantivy::schema::NumericOptions] for card numeric fields.
///
/// Card numbers should:
/// - be indexed.

View file

@ -23,162 +23,118 @@ use teloxide::utils::html::escape;
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
pub fn display_card(globals: &LocalizedGlobalsIndexes, card: &Card) -> String {
let title = format!("<b><u>{}</u></b>\n", escape(&card.name),);
let title: String = display_title(&card.name);
let r#type: String = display_type(globals, card);
let stats = display_stats(card);
let subtypes: String = display_subtypes(&card.subtypes);
let header = format!("{} ({})\n{}\n{}\n", &title, &r#type, &stats, &subtypes);
let keywords = display_keywords(&card.keywords, &globals.keywords);
let description = display_description(&card.localized_description_text);
let levelup = display_levelup(&card.localized_levelup_text);
let body = format!("{}{}{}", &keywords, &description, &levelup);
let set = display_set(&card.set, &globals.sets);
let regions = display_regions(&card.regions, &globals.regions);
let flavor = display_flavor(&card.main_art().expect("Card to have at least one illustration").full_png, &card.localized_flavor_text);
let footer = format!("{}{}\n{}", &set, &regions, &flavor);
format!("{}\n\n{}\n\n{}", &header, &body, &footer)
}
/// Render a [Card]'s title in [Telegram Bot HTML].
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
fn display_title(name: &str) -> String {
format!("<b><u>{}</u></b>", escape(name))
}
/// Render the [Card]'s in-game type in [Telegram Bot HTML].
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
fn display_type(globals: &LocalizedGlobalsIndexes, card: &Card) -> String {
match card.r#type {
CardType::Spell => {
"Spell".to_string()
}
CardType::Equipment => {
CardKeyword::Equipment.localized(&globals.keywords).map(|l| l.name.clone()).unwrap_or_else(|| "EQUIPMENT".to_string())
}
CardType::Unit => {
if card.supertype.eq(&CardSupertype::Champion) {
"Champion".to_string()
}
else {
"Unit".to_string()
}
}
CardType::Ability => {
if card.keywords.contains(&CardKeyword::Skill) {
CardKeyword::Skill.localized(&globals.keywords).map(|l| l.name.clone()).unwrap_or_else(|| "SKILL".to_string())
}
else {
"Ability".to_string() // Does this exist?
}
}
CardType::Landmark => {
CardKeyword::Landmark.localized(&globals.keywords).map(|l| l.name.clone()).unwrap_or_else(|| "LANDMARK".to_string())
}
CardType::Trap => {
if card.keywords.contains(&CardKeyword::Trap) {
CardKeyword::Trap.localized(&globals.keywords).map(|l| l.name.clone()).unwrap_or_else(|| "TRAP".to_string())
}
else if card.keywords.contains(&CardKeyword::Boon) {
CardKeyword::Boon.localized(&globals.keywords).map(|l| l.name.clone()).unwrap_or_else(|| "Boon".to_string())
}
else {
"Trigger".to_string() // Does this exist?
}
}
CardType::Unsupported => {
"UNKNOWN?".to_string()
}
}
}
fn display_stats(card: &Card) -> String {
match &card.r#type {
CardType::Spell => format!("{} mana", escape(&card.cost.to_string())),
let stats = match &card.r#type {
CardType::Spell => format!("{} mana\n\n", escape(&card.cost.to_string()),),
CardType::Unit => format!(
"{} mana · {}|{}",
"{} mana {}|{}\n\n",
escape(&card.cost.to_string()),
escape(&card.attack.to_string()),
escape(&card.health.to_string()),
),
CardType::Landmark => format!("{} mana", &card.cost),
CardType::Landmark => format!("{} mana\n\n", &card.cost),
_ => "".to_string(),
}
};
let set = display_set(&card.set, &globals.sets);
let regions = display_regions(&card.regions, &globals.regions);
let r#type = display_types(&card.r#type, &card.supertype, &card.subtypes);
let breadcrumbs = format!("{} {} {}\n\n", &set, &regions, &r#type);
let keywords = display_keywords(&card.keywords, &globals.keywords);
let description = display_description(&card.localized_description_text);
let levelup = display_levelup(&card.localized_levelup_text);
let flavor = format!("<i>{}</i>\n", escape(&card.localized_flavor_text));
let artist = format!(
r#"<a href="{}">Illustration</a> by {}"#,
&card
.main_art()
.expect("Card to have at least one illustration")
.full_png,
escape(&card.artist_name)
);
format!(
"{}{}{}{}{}{}-----\n{}{}",
&title, &breadcrumbs, &keywords, &stats, &description, &levelup, &flavor, &artist,
)
}
/// Render the [CardSubtype]s in [Telegram Bot HTML].
/// Render a [CardSet] in [Telegram Bot HTML].
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
fn display_subtypes(subtypes: &[CardSubtype]) -> String {
let result = subtypes
.iter()
.map(|s| titlecase(s))
.map(|s| escape(&s))
.map(|s| format!("<i>{s}</i>"))
.join(", ");
fn display_set(set: &CardSet, hm: &LocalizedCardSetIndex) -> String {
format!(
"<i>{}</i>",
set.localized(hm)
.map(|o| format!("<i>{}</i>", escape(&o.name)))
.unwrap_or_else(|| "Unknown".to_string())
)
}
if result.is_empty() {
result
} else {
result + "\n"
/// Render a slice of [CardRegion]s in [Telegram Bot HTML].
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
fn display_regions(regions: &[CardRegion], hm: &LocalizedCardRegionIndex) -> String {
regions
.iter()
.map(|region| {
region
.localized(hm)
.map(|o| format!("<i>{}</i>", escape(&o.name)))
.unwrap_or_else(|| "Unknown".to_string())
})
.join(", ")
}
/// Render the [CardType], the [CardSupertype] and the [CardSubtype]s in [Telegram Bot HTML].
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
fn display_types(r#type: &CardType, supertype: &CardSupertype, subtypes: &[CardSubtype]) -> String {
let mut result = String::new();
result.push_str(
match supertype {
CardSupertype::Champion => "<i>Champion</i> ",
CardSupertype::Unsupported => "<i>Unknown</i> ",
_ => "",
}
);
result.push_str(&format!("<i>{}</i>", escape(&String::from(r#type)),));
if !subtypes.is_empty() {
result.push_str(&format!(
" {}",
subtypes
.iter()
.map(|subtype| format!("<i>{}</i>", escape(subtype)))
.join(", ")
))
}
result
}
/// Render a slice of [CardKeyword]s in [Telegram Bot HTML].
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
fn display_keywords(keywords: &[CardKeyword], hm: &LocalizedCardKeywordIndex) -> String {
let result = keywords
.iter()
.filter(|keyword| !matches!(keyword,
CardKeyword::Countdown |
CardKeyword::OnPlay |
CardKeyword::Landmark |
CardKeyword::Shurima |
CardKeyword::Noxus |
CardKeyword::ClobberNoEmptySlotRequirement |
CardKeyword::Nab |
CardKeyword::Enlightened |
CardKeyword::Invoke |
CardKeyword::Drain |
CardKeyword::LastBreath |
CardKeyword::Demacia |
CardKeyword::BandleCity |
CardKeyword::Bilgewater |
CardKeyword::Runeterra |
CardKeyword::Recall |
CardKeyword::Weakest |
CardKeyword::Support |
CardKeyword::Obliterate |
CardKeyword::Imbue |
CardKeyword::Targon |
CardKeyword::ShadowIsles |
CardKeyword::AuraVisualFakeKeyword |
CardKeyword::Ionia |
CardKeyword::PiltoverZaun |
CardKeyword::SilenceIndividualKeyword |
CardKeyword::Plunder |
CardKeyword::Silenced
))
.map(|keyword| keyword
.localized(hm)
.map(|o| format!("[<b>{}</b>: {}]\n", escape(&o.name), escape(&o.description)))
.unwrap_or_else(|| "[<b>UNKNOWN?</b>]\n".to_string()))
.join("");
if result.is_empty() {
result
} else {
result + "\n"
}
format!(
"{}\n",
keywords
.iter()
.map(|keyword| keyword
.localized(hm)
.map(|o| format!("[<b>{}</b>]", escape(&o.name)))
.unwrap_or_else(|| "Unknown".to_string()))
.join(" ")
)
}
/// Render a [Card::localized_description_text] in [Telegram Bot HTML].
@ -203,46 +159,6 @@ fn display_levelup(levelup: &String) -> String {
}
}
/// Render a [CardSet] in [Telegram Bot HTML].
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
fn display_set(set: &CardSet, hm: &LocalizedCardSetIndex) -> String {
format!(
"<i>{}</i>",
set.localized(hm)
.map(|o| format!("<i>{}</i>", escape(&o.name)))
.unwrap_or_else(|| "UNKNOWN?".to_string())
)
}
/// Render a slice of [CardRegion]s in [Telegram Bot HTML].
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
fn display_regions(regions: &[CardRegion], hm: &LocalizedCardRegionIndex) -> String {
let result = regions
.iter()
.map(|region| {
region
.localized(hm)
.map(|o| format!("<u>{}</u>", escape(&o.name)))
.unwrap_or_else(|| "UNKNOWN?".to_string())
})
.join(", ");
if result.is_empty() {
result
} else {
format!(" · {}", &result)
}
}
/// Render the flavor text of a [Card] in [Telegram Bot HTML], linking to the given art.
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
fn display_flavor(art_url: &str, localized_flavor_text: &str) -> String {
format!("<a href=\"{}\"><i>{}</i></a>", &art_url, escape(localized_flavor_text))
}
/// Render a [Deck] in [Telegram Bot HTML], with an optional `name`.
///
/// [Telegram Bot HTML]: https://core.telegram.org/bots/api#html-style
@ -286,19 +202,19 @@ pub fn display_deck(index: &CardIndex, deck: &Deck, code: &str, name: &Option<&s
})
.join("\n");
let mut tags: Vec<String> = vec![];
let mut tags: Vec<&'static str> = vec![];
let regions = if let Some(regions) = deck.standard(index) {
tags.push("#Standard_4_5".to_string());
tags.push("#Standard_4_3");
regions
} else if let Some(regions) = deck.eternal(index) {
tags.push("#Eternal".to_string());
tags.push("#Eternal");
regions
} else if let Some(regions) = deck.unlimited_champions(index) {
tags.push("#UnlimitedChampions".to_string());
tags.push("#UnlimitedChampions");
regions
} else if let Some(regions) = deck.singleton(index) {
tags.push("#Singleton".to_string());
tags.push("#Singleton");
regions
} else {
HashSet::new()
@ -306,8 +222,18 @@ pub fn display_deck(index: &CardIndex, deck: &Deck, code: &str, name: &Option<&s
for region in regions {
tags.push(match region {
CardRegion::Unsupported => "<i>Unknown</i>".to_string(),
_ => format!("#{}", region.to_tag().map_or_else(|| "<i>Unknown</i>", |r| r)),
CardRegion::Noxus => "#Noxus",
CardRegion::Demacia => "#Demacia",
CardRegion::Freljord => "#Freljord",
CardRegion::ShadowIsles => "#ShadowIsles",
CardRegion::Targon => "#Targon",
CardRegion::Ionia => "#Ionia",
CardRegion::Bilgewater => "#Bilgewater",
CardRegion::Shurima => "#Shurima",
CardRegion::PiltoverZaun => "#PiltoverZaun",
CardRegion::BandleCity => "#BandleCity",
CardRegion::Runeterra => "#Runeterra",
CardRegion::Unsupported => "<i>Unknown</i>",
})
}
@ -319,14 +245,3 @@ pub fn display_deck(index: &CardIndex, deck: &Deck, code: &str, name: &Option<&s
None => format!("<code>{}</code>\n{}\n{}", &code, &tags, &cards),
}
}
// https://stackoverflow.com/a/38406885/4334568
fn titlecase(s: &str) -> String {
let s = s.to_lowercase();
let mut c = s.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
}
}