From d3ad9c7923e6490af9501a3de7ba6c69392665b6 Mon Sep 17 00:00:00 2001 From: Anna Date: Sun, 28 Oct 2018 17:29:54 -0400 Subject: [PATCH] feat: add linkshell parsing --- html/Linkshells/lala world.html | 1267 ++++++++++++++++++++++++++ html/Searches/Linkshell.html | 1490 +++++++++++++++++++++++++++++++ src/logic.rs | 1 + src/logic/linkshell.rs | 96 ++ src/logic/search.rs | 1 + src/logic/search/character.rs | 2 +- src/logic/search/linkshell.rs | 79 ++ src/models.rs | 1 + src/models/linkshell.rs | 25 + src/models/search.rs | 1 + src/models/search/linkshell.rs | 10 + 11 files changed, 2972 insertions(+), 1 deletion(-) create mode 100644 html/Linkshells/lala world.html create mode 100644 html/Searches/Linkshell.html create mode 100644 src/logic/linkshell.rs create mode 100644 src/logic/search/linkshell.rs create mode 100644 src/models/linkshell.rs create mode 100644 src/models/search/linkshell.rs diff --git a/html/Linkshells/lala world.html b/html/Linkshells/lala world.html new file mode 100644 index 0000000..f5a7689 --- /dev/null +++ b/html/Linkshells/lala world.html @@ -0,0 +1,1267 @@ + + + +lala world | FINAL FANTASY XIV, The Lodestone + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + +
+ + +
+ + + + + + +
+ + + +
+ +
+ + + + + + + + + + + + + + +
+ + + + +

Linkshells

+ + + +
+ +
+ +
+ +

Linkshells

+ +
+
+
+ +
+

lala world

+ +
+ + +
+ + + + + +

Members

+ +
+
+ +
+ + + + + + + + + +
+ +
+
110 Total
+ +
+

Filter by Grand Company

+ +
+ +
+ +
+ +
+ +
+ +
+
+ + +
    + +
  • +
  • + + +
  • Page 1 of 3
  • + + +
  • +
  • + +
+ + + + +
Prinny Dawnbringer

Prinny Dawnbringer

Adamantoise

  • 70
Master
Inglorious Bastreds
+ + +
Adio Tomenaki

Adio Tomenaki

Adamantoise

  • 70
Leader
Order of Wild Rose
+ + +
Arawn Knight

Arawn Knight

Adamantoise

  • 70
Leader
The Shin Kickers
+ + +
Arockne Delmare

Arockne Delmare

Adamantoise

  • 70
Leader
Eminence
+ + +
Big Fatone

Big Fatone

Adamantoise

  • 70
Leader
The Squad
+ + +
Billy Bear

Billy Bear

Adamantoise

  • 70
Leader
Knightriders
+ + +
Caduceus Deus

Caduceus Deus

Adamantoise

  • 70
Leader
Ascendant
+ + +
Cherry Limeade

Cherry Limeade

Adamantoise

  • 70
Leader
Rising Chorus
+ + +
Chibi Terasu

Chibi Terasu

Adamantoise

  • 70
Leader
-Insert Coin-
+ + +
Dak Kallo

Dak Kallo

Adamantoise

  • 67
Leader
Order of Wild Rose
+ + +
Dead Master

Dead Master

Adamantoise

  • 70
Leader
Fox's Den
+ + +
Duvivi Duvi

Duvivi Duvi

Adamantoise

  • 70
Leader
Magitek Mercenaries
+ + +
Erimu Desiden

Erimu Desiden

Adamantoise

  • 63
Leader
Jenova's Witness
+ + +
Exclamation Mark

Exclamation Mark

Adamantoise

  • 70
Leader
The Shin Kickers
+ + +
Fatal Flaw

Fatal Flaw

Adamantoise

  • 70
Leader
+ + +
Felli Antalk

Felli Antalk

Adamantoise

  • 70
Leader
Noir
+ + +
Fors Taki

Fors Taki

Adamantoise

  • 70
Leader
Ascendant
+ + +
Gekko Jiyu

Gekko Jiyu

Adamantoise

  • 70
Leader
Existence Error
+ + +
Ghost Zen

Ghost Zen

Adamantoise

  • 70
Leader
Ghostly Galaxy
+ + +
Gracelynn Carralest

Gracelynn Carralest

Adamantoise

  • 62
Leader
Chocobo Wranglers
+ + +
Herzaleid Powerstone

Herzaleid Powerstone

Adamantoise

  • 70
Leader
Eorzean Hunters inc.
+ + +
Ieva Kaiser

Ieva Kaiser

Adamantoise

  • 70
Leader
Calm Down
+ + +
Jen Fox

Jen Fox

Adamantoise

  • 56
Leader
BLACK BUTLER
+ + +
Jojorobo Rererobo

Jojorobo Rererobo

Adamantoise

  • 70
Leader
Wonderland
+ + +
Juwin Silverhand

Juwin Silverhand

Adamantoise

  • 70
Leader
The Tenshodo
+ + +
Kagari Izuriha

Kagari Izuriha

Adamantoise

  • 70
Leader
Castle in the Sky
+ + +
Killua Saiyu

Killua Saiyu

Adamantoise

  • 70
Leader
Final Fantasy
+ + +
L'mihn Sunwalker

L'mihn Sunwalker

Adamantoise

  • 70
Leader
The Shin Kickers
+ + +
Lilgynja Nynja

Lilgynja Nynja

Adamantoise

  • 70
Leader
Ash to Embers
+ + +
Lily Faerydae

Lily Faerydae

Adamantoise

  • 70
Leader
Solitary
+ + +
Lirax Leingod

Lirax Leingod

Adamantoise

  • 70
Leader
Palace of Magic
+ + +
Lucca Roroshan

Lucca Roroshan

Adamantoise

  • 70
Leader
Clan Nutsy
+ + +
Lumi Senpai

Lumi Senpai

Adamantoise

  • 70
Leader
Remnants of Erebus
+ + +
Marlen Turenas

Marlen Turenas

Adamantoise

  • 70
Leader
Mischief Managed
+ + +
Mezhren Taru

Mezhren Taru

Adamantoise

  • 70
Leader
Kawaii Tea Party
+ + +
Millefeuille Chapu

Millefeuille Chapu

Adamantoise

  • 70
Leader
Genbu Slayers
+ + +
Misiisii Isme

Misiisii Isme

Adamantoise

  • 70
Leader
Cleric Stance
+ + +
Miyuki Kimura

Miyuki Kimura

Adamantoise

  • 70
Leader
The Unbound
+ + +
Nick Harrelson

Nick Harrelson

Adamantoise

  • 66
Leader
Allies
+ + +
Nigredo Cinqmars

Nigredo Cinqmars

Adamantoise

  • 70
Leader
For we are many
+ + +
Pucca Pucca

Pucca Pucca

Adamantoise

  • 70
Leader
Palace of Magic
+ + +
Puck Man

Puck Man

Adamantoise

  • 69
Leader
The Kupo Nutcases
+ + +
Queen Baby

Queen Baby

Adamantoise

  • 70
Leader
Ascendant
+ + +
Ragnor Strawberry

Ragnor Strawberry

Adamantoise

  • 70
Leader
+ + +
Red Onion

Red Onion

Adamantoise

  • 70
Leader
Quasar
+ + +
Rf Switchblade

Rf Switchblade

Adamantoise

  • 70
Leader
SenseOfRightAlliance
+ + +
Sai Duck

Sai Duck

Adamantoise

  • 70
Leader
Ash to Embers
+ + +
Salty Phish

Salty Phish

Adamantoise

  • 70
Leader
Fox's Den
+ + +
Satu Situ

Satu Situ

Adamantoise

  • 70
Leader
Seventh Heaven
+ + +
Sora Mashimaro

Sora Mashimaro

Adamantoise

  • 70
Leader
Quasar
+ + + +
    + +
  • +
  • + + +
  • Page 1 of 3
  • + + +
  • +
  • + +
+ + + + + +
+
+ +
+ + + + +
+ ForumsMog StationOfficial Blog +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + +
+ + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + + + + +
+ + + +
+

Community Wall

+ +
+
+

Recent Activity

+ +
+

+ Filter which items are to be displayed below.
+ + * Notifications for standings updates are shared across all Worlds.
+ * Notifications for PvP team formations are shared for all languages.
+ * Notifications for free company formations are shared for all languages. +
+

+ + +
+
Sort by
Data Center / World
Primary language
Displaying
+
+ + +
+
+ + + + + +
+ + + + + + + + + + + + + + +
+ +
+ +
+ + + + + + +
+ +

Cookie Policy

+

This website uses cookies. If you do not wish us to set cookies on your device, please do not use the website. Please read the Square Enix cookies policy for more information. Your use of the website is also subject to the terms in the Square Enix website terms of use and privacy policy and by using the website you are accepting those terms. The Square Enix terms of use, privacy policy and cookies policy can also be found through links at the bottom of the page.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/html/Searches/Linkshell.html b/html/Searches/Linkshell.html new file mode 100644 index 0000000..aa97489 --- /dev/null +++ b/html/Searches/Linkshell.html @@ -0,0 +1,1490 @@ + + + +Linkshells | FINAL FANTASY XIV, The Lodestone + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + + +
+ + +
+ + + + + + +
+ + + +
+ +
+ + + + + + + + + + + + + + +
+ + + + +

Linkshells

+ + + +
+ +
+ +
+

Linkshell Search

+

Search Results

+ +
+
+
+ +

Data Center / World

+
+ +
+

Active Members

+
+ +
+
+ +
+
+ + +
Linkshell: lala world| Data Center/World : All| Active Members: All
+ + + +
+ + + +
+
+ +
+
16 Total
+
+
+ + + + + +
    + +
  • +
  • + + +
  • Page 1 of 1
  • + + +
  • +
  • + +
+ + + + +
+ +
+ +
+
+

Lala against World

+

Sargatanas

+
+
+ Active Members: 4 +
+
+
+ +
+ +
+ +
+
+

Lala World

+

Exodus

+
+
+ Active Members: 1 +
+
+
+ +
+ +
+ +
+
+

Lala World

+

Gungnir

+
+
+ Active Members: 2 +
+
+
+ +
+ +
+ +
+
+

Lala World

+

Diabolos

+
+
+ Active Members: 5 +
+
+
+ +
+ +
+ +
+
+

lala world

+

Adamantoise

+
+
+ Active Members: 110 +
+
+
+ +
+ +
+ +
+
+

Lala's Secret World

+

Sargatanas

+
+
+ Active Members: 128 +
+
+
+ +
+ +
+ +
+
+

lala's world

+

Moogle

+
+
+ Active Members: 1 +
+
+
+ +
+ +
+ +
+
+

Lalafell Underworld

+

Zalera

+
+
+ Active Members: 6 +
+
+
+ +
+ +
+ +
+
+

Lalafell World

+

Odin

+
+
+ Active Members: 2 +
+
+
+ +
+ +
+ +
+
+

Lalafell World

+

Moogle

+
+
+ Active Members: 2 +
+
+
+ +
+ +
+ +
+
+

Lalafell World

+

Faerie

+
+
+ Active Members: 52 +
+
+
+ +
+ +
+ +
+
+

Lalafell's World

+

Adamantoise

+
+
+ Active Members: 7 +
+
+
+ +
+ +
+ +
+
+

Lalafell's World

+

Ragnarok

+
+
+ Active Members: 3 +
+
+
+ +
+ +
+ +
+
+

Lalaworld

+

Balmung

+
+
+ Active Members: 1 +
+
+
+ +
+ +
+ +
+
+

LaLaWorld

+

Shiva

+
+
+ Active Members: 1 +
+
+
+ +
+ +
+ +
+
+

World Dominalala

+

Diabolos

+
+
+ Active Members: 5 +
+
+
+ + +
    + +
  • +
  • + + +
  • Page 1 of 1
  • + + +
  • +
  • + +
+ + + + + + +
+
+ +
+ + + + +
+ ForumsMog StationOfficial Blog +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + +
+ + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + + + + +
+ + + +
+

Community Wall

+ +
+
+

Recent Activity

+ +
+

+ Filter which items are to be displayed below.
+ + * Notifications for standings updates are shared across all Worlds.
+ * Notifications for PvP team formations are shared for all languages.
+ * Notifications for free company formations are shared for all languages. +
+

+ + +
+
Sort by
Data Center / World
Primary language
Displaying
+
+ + +
+
+ + + + + +
+ + + + + + + + + + + + + + +
+ +
+ +
+ + + + + + +
+ +

Cookie Policy

+

This website uses cookies. If you do not wish us to set cookies on your device, please do not use the website. Please read the Square Enix cookies policy for more information. Your use of the website is also subject to the terms in the Square Enix website terms of use and privacy policy and by using the website you are accepting those terms. The Square Enix terms of use, privacy policy and cookies policy can also be found through links at the bottom of the page.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/logic.rs b/src/logic.rs index 81dc695..5f42d64 100644 --- a/src/logic.rs +++ b/src/logic.rs @@ -24,6 +24,7 @@ macro_rules! selectors { pub mod character; pub mod free_company; +pub mod linkshell; pub mod search; pub use self::{ diff --git a/src/logic/linkshell.rs b/src/logic/linkshell.rs new file mode 100644 index 0000000..fc1c830 --- /dev/null +++ b/src/logic/linkshell.rs @@ -0,0 +1,96 @@ +use crate::{ + error::*, + logic::plain_parse, + models::{ + linkshell::{Linkshell, LinkshellMember, Role}, + search::Paginated, + }, +}; + +use ffxiv_types::World; + +use scraper::{Html, ElementRef}; + +use std::str::FromStr; + +selectors!( + LS_NAME => "h3.heading__linkshell__name"; + LS_WORLD => "p.entry__world"; + LS_ACTIVE_MEMBERS => "div.parts__total"; + + ITEM_ENTRY => ".ldst__window .entry"; + ITEM_ROLE => ".entry__chara_info__linkshell > span"; +); + +pub fn parse(id: u64, html_str: &str) -> Result { + let html = Html::parse_document(html_str); + + let name = plain_parse(&html, &*LS_NAME)?; + let world = parse_world(&html)?; + let active_members = parse_active_members(&html)?; + let members = parse_members(html_str)?; + + Ok(Linkshell { + id, + name, + world, + active_members, + members, + }) +} + +fn parse_world(html: &Html) -> Result { + let world_str = plain_parse(html, &*LS_WORLD)?; + let trimmed = world_str.trim(); + World::from_str(trimmed) + .map_err(|_| Error::invalid_content("a world", Some(trimmed))) +} + +fn parse_active_members(html: &Html) -> Result { + let text = plain_parse(&html, &*LS_ACTIVE_MEMBERS)?; + text.split(' ').next().unwrap().parse().map_err(Error::InvalidNumber) +} + +fn parse_members(html_str: &str) -> Result> { + let html = Html::parse_document(html_str); + + let pagination = crate::logic::search::parse_pagination(&html)?; + + // has results but requested an invalid page + if pagination.total_results != 0 && pagination.current_page == 0 { + return Err(Error::InvalidPage(pagination.total_pages)); + } + + let results: Vec = html + .select(&*ITEM_ENTRY) + .map(|x| parse_single(x)) + .collect::>()?; + + Ok(Paginated { + pagination, + results, + }) +} + + +fn parse_single<'a>(html: ElementRef<'a>) -> Result { + let character = super::search::character::parse_single(html)?; + + let role = parse_role(html)?; + + Ok(LinkshellMember { + character, + role, + }) +} + +fn parse_role<'a>(html: ElementRef<'a>) -> Result> { + let role = match html.select(&*ITEM_ROLE).next() { + Some(r) => r, + None => return Ok(None), + }; + let role_str: String = role.text().collect(); + Role::parse(role_str.trim()) + .ok_or_else(|| Error::invalid_content("valid linkshell role", Some(role_str.trim()))) + .map(Some) +} diff --git a/src/logic/search.rs b/src/logic/search.rs index 243e374..27cf73c 100644 --- a/src/logic/search.rs +++ b/src/logic/search.rs @@ -7,6 +7,7 @@ use scraper::Html; pub mod character; pub mod free_company; +pub mod linkshell; pub use self::{ character::parse as parse_character_search, diff --git a/src/logic/search/character.rs b/src/logic/search/character.rs index 7c99763..7602f3d 100644 --- a/src/logic/search/character.rs +++ b/src/logic/search/character.rs @@ -50,7 +50,7 @@ pub fn parse(html: &str) -> Result> { }) } -fn parse_single<'a>(html: ElementRef<'a>) -> Result { +crate fn parse_single<'a>(html: ElementRef<'a>) -> Result { let id = parse_id(html)?; let name = plain_parse(html, &*ITEM_NAME)?; diff --git a/src/logic/search/linkshell.rs b/src/logic/search/linkshell.rs new file mode 100644 index 0000000..480c191 --- /dev/null +++ b/src/logic/search/linkshell.rs @@ -0,0 +1,79 @@ +use crate::{ + error::*, + logic::plain_parse_elem as plain_parse, + models::{ + search::{ + Paginated, + linkshell::LinkshellSearchItem, + }, + }, +}; + +use ffxiv_types::World; + +use scraper::{Html, ElementRef}; + +use std::str::FromStr; + +selectors!( + ITEM_ENTRY => ".ldst__window > .entry"; + + ITEM_ID => ".entry__link--line"; + ITEM_NAME => ".entry__linkshell > .entry__name"; + ITEM_WORLD => ".entry__linkshell > p.entry__world"; + ITEM_ACTIVE_MEMBERS => ".entry .entry__linkshell__member > span"; +); + +pub fn parse(s: &str) -> Result> { + let html = Html::parse_document(s); + + let pagination = crate::logic::search::parse_pagination(&html)?; + + // has results but requested an invalid page + if pagination.total_results != 0 && pagination.current_page == 0 { + return Err(Error::InvalidPage(pagination.total_pages)); + } + + let results: Vec = html + .select(&*ITEM_ENTRY) + .map(|x| parse_single(x)) + .collect::>()?; + + Ok(Paginated { + pagination, + results, + }) +} + +fn parse_single<'a>(html: ElementRef<'a>) -> Result { + let id = parse_id(html)?; + let name = plain_parse(html, &*ITEM_NAME)?; + let world = parse_world(html)?; + let active_members = parse_active_members(html)?; + + Ok(LinkshellSearchItem { + id, + name, + world, + active_members, + }) +} + +fn parse_id<'a>(html: ElementRef<'a>) -> Result { + let e = html + .select(&*ITEM_ID) + .next() + .ok_or_else(|| Error::missing_element(&*ITEM_ID))?; + crate::logic::parse_id(e.value()) +} + +fn parse_world<'a>(html: ElementRef<'a>) -> Result { + let world_str = plain_parse(html, &*ITEM_WORLD)?; + World::from_str(&world_str) + .map_err(|_| Error::invalid_content("valid world", Some(&world_str))) +} + +fn parse_active_members<'a>(html: ElementRef<'a>) -> Result { + plain_parse(html, &*ITEM_ACTIVE_MEMBERS) + .and_then(|x| x.parse().map_err(Error::InvalidNumber)) +} diff --git a/src/models.rs b/src/models.rs index 2322309..596efb0 100644 --- a/src/models.rs +++ b/src/models.rs @@ -26,6 +26,7 @@ macro_rules! ffxiv_enum { pub mod character; pub mod free_company; +pub mod linkshell; pub mod search; ffxiv_enum!( diff --git a/src/models/linkshell.rs b/src/models/linkshell.rs new file mode 100644 index 0000000..24851ee --- /dev/null +++ b/src/models/linkshell.rs @@ -0,0 +1,25 @@ +use super::search::{Paginated, character::CharacterSearchItem}; + +use ffxiv_types::World; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Linkshell { + #[serde(with = "crate::util::serde::u64_str")] + pub id: u64, + pub name: String, + pub world: World, + pub active_members: u8, + pub members: Paginated, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LinkshellMember { + #[serde(flatten)] + pub character: CharacterSearchItem, + pub role: Option, +} + +ffxiv_enum!(Role { + Master => "master", + Leader => "leader", +}); diff --git a/src/models/search.rs b/src/models/search.rs index c70c678..c8f10cd 100644 --- a/src/models/search.rs +++ b/src/models/search.rs @@ -1,5 +1,6 @@ pub mod character; pub mod free_company; +pub mod linkshell; #[derive(Debug, Default, Serialize, Deserialize)] pub struct Pagination { diff --git a/src/models/search/linkshell.rs b/src/models/search/linkshell.rs new file mode 100644 index 0000000..8a48541 --- /dev/null +++ b/src/models/search/linkshell.rs @@ -0,0 +1,10 @@ +use ffxiv_types::World; + +#[derive(Debug, Serialize, Deserialize)] +pub struct LinkshellSearchItem { + #[serde(with = "crate::util::serde::u64_str")] + pub id: u64, + pub name: String, + pub world: World, + pub active_members: u8, +}