1
Fork 0
mirror of https://github.com/Steffo99/patched-porobot.git synced 2024-10-16 09:37:27 +00:00

Compare commits

...

40 commits

Author SHA1 Message Date
4bdb01eb61
Merge 8cc1c2b8f5 into 8ae11125ed 2024-09-26 02:00:11 +00:00
8ae11125ed
Update README 2024-09-15 05:45:25 +02:00
dependabot[bot]
0c81b64030 Bump h2 from 0.3.17 to 0.3.24
Bumps [h2](https://github.com/hyperium/h2) from 0.3.17 to 0.3.24.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.24/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.17...v0.3.24)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-21 17:41:59 +01:00
96aabc5552
Link to the latest version of the docs in the README 2023-10-23 03:02:50 +02:00
9c7bf31e57
Bump version to 0.15.0 2023-10-23 01:34:46 +02:00
6472da3432
Remove unused imports 2023-10-23 01:31:24 +02:00
bbadb3b5ae
Refactor Telegram card displaying 2023-10-23 01:31:11 +02:00
115af23e4b
Add CardRegion::to_tag 2023-10-23 01:01:48 +02:00
75aab93b57
Refactor Discord embed generator 2023-10-23 00:34:08 +02:00
85aac42dd2
Add CardRarity::color() 2023-10-23 00:34:00 +02:00
97899f3ca2
Add CardRarity::discord_emoji() 2023-10-23 00:30:14 +02:00
c8c389c0b1
Add CardSet::discord_emoji() 2023-10-22 23:54:38 +02:00
e56fce556e
Add corebundle::test_supported! to make sure all globals are supported 2023-10-22 23:38:50 +02:00
a3dbd8b76f
Add Runeterran regions, such as CardRegion::Ryze 2023-10-22 23:35:32 +02:00
3a1821846d
Rename the ref for CardKeyword::DoubleAttack from "DoubleAttack" to "DoubleStrike" 2023-10-22 23:29:30 +02:00
9a930724de
Add CardKeyword::Freljord 2023-10-22 23:27:55 +02:00
8b3ca83c65
Add CardKeyword::LevelUp 2023-10-22 23:26:25 +02:00
24a67cf445
Add CardKeyword::ElementalSkill 2023-10-22 23:24:43 +02:00
9ed2220be9
Add CardKeyword::Attack 2023-10-22 23:21:27 +02:00
e1edda3041
Add CardKeyword::Capture 2023-10-22 23:17:09 +02:00
7041b75d2f
Add CardRarity::Unsupported and #[non_exaustive] 2023-10-22 23:05:23 +02:00
e9e051b72e
Add SpellSpeed::Unsupported and #[non_exaustive] 2023-10-22 23:04:57 +02:00
0aee8e76dc
Add CardSet::FatesVoyage 2023-10-22 22:52:42 +02:00
9de0b24931
Update tests for game version 4_9_0 2023-10-22 22:51:06 +02:00
c778b5cc82
Use matches! macro in display_keywords 2023-10-22 22:28:43 +02:00
c321ff6b15
Remove unnecessary reference 2023-10-22 22:28:43 +02:00
dependabot[bot]
fb67ee7907 Bump webpki from 0.22.0 to 0.22.2
Bumps [webpki](https://github.com/briansmith/webpki) from 0.22.0 to 0.22.2.
- [Commits](https://github.com/briansmith/webpki/commits)

---
updated-dependencies:
- dependency-name: webpki
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-03 14:18:52 +02:00
c94b325b7e
Update links 2023-09-18 12:44:31 +02:00
ad66b29a87
Update README 2023-07-04 21:19:29 +02:00
9eec837632
Bump lockfile to 0.14.0 2023-07-04 21:19:24 +02:00
929a71b1ee
Bump version to 0.14.0 2023-07-03 05:28:03 +02:00
140a35e6a4
Add CardType::Equipment and rewrite Telegram rendering code 2023-07-03 05:27:50 +02:00
933907cd5f
Replace map and flatten with filter_map 2023-07-03 03:10:09 +02:00
96613c6626
Remove unnecessary reference 2023-07-03 03:09:43 +02:00
618ebb4d50
Replace filter for Some followed by unwrap with flatten 2023-07-03 03:07:42 +02:00
f549bf5fef
Add Discord name for Heart of the Huntress 2023-07-03 03:05:16 +02:00
0a820667b3
Add CardSet::HeartOfTheHuntress 2023-07-03 03:00:14 +02:00
394397bed3
Apply doc suggestions by IntelliJ 2023-06-25 03:32:13 +02:00
8cc1c2b8f5
Default to UID and GID 1000 2023-04-05 12:27:35 +02:00
29e3853867
Run container as a non-privileged user
Uses the `USER` instruction.

See https://stackoverflow.com/questions/68155641/should-i-run-things-inside-a-docker-container-as-non-root-for-safety .
2023-04-05 12:24:10 +02:00
36 changed files with 10126 additions and 1412 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",
"hashbrown 0.12.3",
"lock_api",
"once_cell",
"parking_lot_core",
@ -393,6 +393,12 @@ 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"
@ -630,9 +636,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "h2"
version = "0.3.17"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f"
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
dependencies = [
"bytes",
"fnv",
@ -656,6 +662,12 @@ 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"
@ -799,12 +811,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "1.9.3"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"autocfg",
"hashbrown",
"equivalent",
"hashbrown 0.14.3",
]
[[package]]
@ -932,7 +944,7 @@ version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
dependencies = [
"hashbrown",
"hashbrown 0.12.3",
]
[[package]]
@ -1145,7 +1157,7 @@ dependencies = [
[[package]]
name = "patched_porobot"
version = "0.13.0"
version = "0.15.0"
dependencies = [
"anyhow",
"base64 0.21.0",
@ -2361,9 +2373,9 @@ dependencies = [
[[package]]
name = "webpki"
version = "0.22.0"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f"
dependencies = [
"ring",
"untrusted",

View file

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

View file

@ -69,6 +69,7 @@ COPY --from=builder \
/usr/src/patched_porobot/target/*/release/patched_porobot_matrix \
/usr/bin/
USER ${UID:-1000}:${GID:-1000}
ENTRYPOINT []
CMD []

View file

@ -1,7 +1,13 @@
# ![](icon.png) Patched Porobot
<div align="center">
![](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)
@ -9,12 +15,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/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)
[![Documentation](https://img.shields.io/docsrs/patched_porobot)](https://docs.rs/patched_porobot/latest/patched_porobot/)
## Screenshots
@ -27,6 +33,8 @@ 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>
@ -34,6 +42,8 @@ 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>
@ -41,6 +51,8 @@ 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>
@ -48,6 +60,8 @@ 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>
@ -55,12 +69,62 @@ 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 cargo license</summary>
<summary>List of licenses as output by <code>cargo license</code></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

View file

@ -1,7 +1,7 @@
{
"vocabTerms": [
{
"description": "When you summon this, it gets its allegiance bonus if the top card of your deck matches its region.",
"description": "When you summon this unit, it gets its allegiance bonus if the top card of your deck matches its region.",
"name": "Allegiance",
"nameRef": "Allegiance"
},
@ -20,6 +20,11 @@
"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",
@ -65,11 +70,21 @@
"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",
@ -135,6 +150,11 @@
"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",
@ -185,6 +205,11 @@
"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",
@ -252,6 +277,11 @@
"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",
@ -423,7 +453,7 @@
"nameRef": "Skill"
},
{
"description": "A card triggers its plunder ability when played if you damaged the enemy Nexus this round.",
"description": "A card activates its plunder ability when played if you damaged the enemy Nexus this round.",
"name": "Plunder",
"nameRef": "Plunder"
},
@ -576,115 +606,127 @@
"regions": [
{
"abbreviation": "NX",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-noxus.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-noxus.png",
"name": "Noxus",
"nameRef": "Noxus"
},
{
"abbreviation": "RYZE",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-ryze.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-ryze.png",
"name": "Ryze",
"nameRef": "Ryze"
},
{
"abbreviation": "Jhin",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-jhin.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-jhin.png",
"name": "Jhin",
"nameRef": "Jhin"
},
{
"abbreviation": "Varus",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-varus.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-varus.png",
"name": "Varus",
"nameRef": "Varus"
},
{
"abbreviation": "Aatrox",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-aatrox.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_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_3_0/core/en_us/img/regions/icon-jax.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-jax.png",
"name": "Jax",
"nameRef": "Jax"
},
{
"abbreviation": "Kayn",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-kayn.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_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_3_0/core/en_us/img/regions/icon-evelynn.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-evelynn.png",
"name": "Evelynn",
"nameRef": "Evelynn"
},
{
"abbreviation": "Bard",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-bard.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-bard.png",
"name": "Bard",
"nameRef": "Bard"
},
{
"abbreviation": "DE",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-demacia.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-demacia.png",
"name": "Demacia",
"nameRef": "Demacia"
},
{
"abbreviation": "RU",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-runeterra.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-runeterra.png",
"name": "Runeterra",
"nameRef": "Runeterra"
},
{
"abbreviation": "FR",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-freljord.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-freljord.png",
"name": "Freljord",
"nameRef": "Freljord"
},
{
"abbreviation": "SI",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-shadowisles.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-shadowisles.png",
"name": "Shadow Isles",
"nameRef": "ShadowIsles"
},
{
"abbreviation": "MT",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-targon.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-targon.png",
"name": "Targon",
"nameRef": "Targon"
},
{
"abbreviation": "IO",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-ionia.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-ionia.png",
"name": "Ionia",
"nameRef": "Ionia"
},
{
"abbreviation": "SH",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-shurima.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-shurima.png",
"name": "Shurima",
"nameRef": "Shurima"
},
{
"abbreviation": "BW",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-bilgewater.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-bilgewater.png",
"name": "Bilgewater",
"nameRef": "Bilgewater"
},
{
"abbreviation": "PZ",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-piltoverzaun.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-piltoverzaun.png",
"name": "Piltover & Zaun",
"nameRef": "PiltoverZaun"
},
{
"abbreviation": "BC",
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/regions/icon-bandlecity.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/regions/icon-bandlecity.png",
"name": "Bandle City",
"nameRef": "BandleCity"
}
@ -727,54 +769,64 @@
],
"sets": [
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set3_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set3_crispmip.png",
"name": "Call of the Mountain",
"nameRef": "Set3"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set5_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set5_crispmip.png",
"name": "Beyond the Bandlewood",
"nameRef": "Set5"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set1_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set1_crispmip.png",
"name": "Foundations",
"nameRef": "Set1"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set2_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set2_crispmip.png",
"name": "Rising Tides",
"nameRef": "Set2"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set6ab_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set6ab_crispmip.png",
"name": "Worldwalker",
"nameRef": "Set6"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set4_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set4_crispmip.png",
"name": "Empires of the Ascended",
"nameRef": "Set4"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set7_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set7_crispmip.png",
"name": "Glory in Navori",
"nameRef": "Set7"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/setevent_crispmip.png",
"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",
"name": "Events",
"nameRef": "SetEvent"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/sets/set6cde_crispmip.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/sets/set6cde_crispmip.png",
"name": "The Darkin Saga",
"nameRef": "Set6cde"
}
],
"formats": [
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/formats/queue_select_standard_toggle_active.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/formats/queue_select_standard_toggle_active.png",
"name": "Standard",
"nameRef": "client_Formats_Standard_name"
},
@ -784,7 +836,7 @@
"nameRef": "client_Deckbuilder_RulesFilters_Singleton"
},
{
"iconAbsolutePath": "http://dd.b.pvp.net/4_3_0/core/en_us/img/formats/queue_select_eternal_toggle_active.png",
"iconAbsolutePath": "http://dd.b.pvp.net/4_10_0/core/en_us/img/formats/queue_select_eternal_toggle_active.png",
"name": "Eternal",
"nameRef": "client_Formats_Eternal_name"
},
@ -798,5 +850,27 @@
"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

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

View file

@ -0,0 +1,40 @@
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

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

View file

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

40
data/set8-en_us/README.md Normal file
View file

@ -0,0 +1,40 @@
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

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

BIN
media/ds-card.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
media/ds-deck.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
media/ds-help.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
media/td-deck.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
media/td-eternal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 KiB

After

Width:  |  Height:  |  Size: 411 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 151 KiB

View file

@ -101,6 +101,12 @@ 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]
@ -112,7 +118,29 @@ mod tests {
};
}
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_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_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,9 +520,7 @@ impl Deck {
///
pub fn cards_are_allowed_in(&self, cards: &CardIndex, format: CardFormat) -> bool {
self.contents.keys()
.map(|cc| cc.to_card(&cards))
.filter(|o| o.is_some())
.map(|o| o.unwrap())
.filter_map(|cc| cc.to_card(cards))
.all(|c| c.formats.contains(&format))
}

View file

@ -196,6 +196,7 @@ pub enum CardKeyword {
/// Double Attack.
///
/// > While attacking, it strikes both before AND at the same time as its blocker.
#[serde(rename = "DoubleStrike")]
DoubleAttack,
/// Vulnerable.
@ -347,6 +348,30 @@ 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,
@ -441,6 +466,11 @@ 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>",
}
}
@ -503,7 +533,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#""DoubleAttack""#, CardKeyword::DoubleAttack);
test_deserialization!(deserialize_doubleattack, r#""DoubleStrike""#, 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);
@ -535,5 +565,10 @@ 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,25 +96,27 @@ mod tests {
};
}
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_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_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_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_latest_en_us_set1, "latest", "en_us", "set1");
test_fetch!(test_fetch_latest_en_us_set2, "latest", "en_us", "set2");
@ -125,6 +127,7 @@ 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,6 +3,7 @@
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).
@ -19,6 +20,10 @@ pub enum CardRarity {
/// A champion (orange hexagon) card, sometimes referred to as *Legendary*.
Champion,
/// Unsupported rarity.
#[serde(other)]
Unsupported,
}
impl CardRarity {
@ -33,6 +38,32 @@ 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)]
@ -56,9 +87,5 @@ 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]
fn deserialize_fallback() {
assert!(serde_json::de::from_str::<'static, CardRarity>("Xyzzy").is_err());
}
test_deserialization!(deserialize_unsupported, r#""Xyzzy""#, CardRarity::Unsupported);
}

View file

@ -32,6 +32,28 @@ 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,
@ -90,6 +112,30 @@ 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 {
@ -104,6 +150,16 @@ 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>",
}
}
@ -180,5 +236,15 @@ 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,6 +40,14 @@ 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,
@ -94,6 +102,24 @@ 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.
@ -157,6 +183,9 @@ 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,6 +3,7 @@
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.
@ -14,6 +15,9 @@ 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 {
@ -50,9 +54,5 @@ 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]
fn deserialize_fallback() {
assert!(serde_json::de::from_str::<'static, SpellSpeed>("Xyzzy").is_err());
}
test_deserialization!(deserialize_unsupported, r#""Xyzzy""#, SpellSpeed::Unsupported);
}

View file

@ -9,6 +9,9 @@ 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.
@ -33,6 +36,7 @@ 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,8 +12,6 @@ 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;
@ -94,13 +92,11 @@ impl EventHandler {
}
if !card.keywords.is_empty() {
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("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("Mana cost", format!("{} mana", card.cost), true);
@ -121,56 +117,30 @@ impl EventHandler {
vec
}.join(", "), true);
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",
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}")
}).join(", "), false);
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",
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}")
}, true);
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);
let actual_rarity = match card.supertype {
CardSupertype::Champion => CardRarity::Champion,
_ => card.rarity
};
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,
}
});
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());
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 [tantivy::tokenizer::TextAnalyzer] for card text.
/// Create the [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 [tantivy::schema::TextOptions] for card codes.
/// Create the [TextOptions] for card codes.
///
/// Card codes should:
/// - TODO: be tokenized without alterations;
@ -63,7 +63,7 @@ impl CardSearchEngine {
.set_fast()
}
/// Create the [tantivy::schema::TextOptions] for card keywords.
/// Create the [TextOptions] for card keywords.
///
/// Card keywords should:
/// - be tokenized with the [CardSearchEngine::tokenizer];
@ -78,7 +78,7 @@ impl CardSearchEngine {
)
}
/// Create the [tantivy::schema::TextOptions] for card text fields.
/// Create the [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 [tantivy::schema::NumericOptions] for card numeric fields.
/// Create the [NumericOptions] for card numeric fields.
///
/// Card numbers should:
/// - be indexed.

View file

@ -23,118 +23,162 @@ 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 stats = match &card.r#type {
CardType::Spell => format!("{} mana\n\n", escape(&card.cost.to_string()),),
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())),
CardType::Unit => format!(
"{} mana {}|{}\n\n",
"{} mana · {}|{}",
escape(&card.cost.to_string()),
escape(&card.attack.to_string()),
escape(&card.health.to_string()),
),
CardType::Landmark => format!("{} mana\n\n", &card.cost),
CardType::Landmark => format!("{} mana", &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 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 {
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 the [CardSubtype]s 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(", ");
if result.is_empty() {
result
} else {
result + "\n"
}
}
/// 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 {
format!(
"{}\n",
keywords
.iter()
.map(|keyword| keyword
.localized(hm)
.map(|o| format!("[<b>{}</b>]", escape(&o.name)))
.unwrap_or_else(|| "Unknown".to_string()))
.join(" ")
)
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"
}
}
/// Render a [Card::localized_description_text] in [Telegram Bot HTML].
@ -159,6 +203,46 @@ 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
@ -202,19 +286,19 @@ pub fn display_deck(index: &CardIndex, deck: &Deck, code: &str, name: &Option<&s
})
.join("\n");
let mut tags: Vec<&'static str> = vec![];
let mut tags: Vec<String> = vec![];
let regions = if let Some(regions) = deck.standard(index) {
tags.push("#Standard_4_3");
tags.push("#Standard_4_5".to_string());
regions
} else if let Some(regions) = deck.eternal(index) {
tags.push("#Eternal");
tags.push("#Eternal".to_string());
regions
} else if let Some(regions) = deck.unlimited_champions(index) {
tags.push("#UnlimitedChampions");
tags.push("#UnlimitedChampions".to_string());
regions
} else if let Some(regions) = deck.singleton(index) {
tags.push("#Singleton");
tags.push("#Singleton".to_string());
regions
} else {
HashSet::new()
@ -222,18 +306,8 @@ pub fn display_deck(index: &CardIndex, deck: &Deck, code: &str, name: &Option<&s
for region in regions {
tags.push(match region {
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>",
CardRegion::Unsupported => "<i>Unknown</i>".to_string(),
_ => format!("#{}", region.to_tag().map_or_else(|| "<i>Unknown</i>", |r| r)),
})
}
@ -245,3 +319,14 @@ 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(),
}
}