feat: add linkshell parsing

This commit is contained in:
Anna 2018-10-28 17:29:54 -04:00
parent d6f914b9c7
commit d3ad9c7923
11 changed files with 2972 additions and 1 deletions

1267
html/Linkshells/lala world.html vendored Normal file

File diff suppressed because one or more lines are too long

1490
html/Searches/Linkshell.html vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -24,6 +24,7 @@ macro_rules! selectors {
pub mod character;
pub mod free_company;
pub mod linkshell;
pub mod search;
pub use self::{

96
src/logic/linkshell.rs Normal file
View File

@ -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<Linkshell> {
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<World> {
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<u8> {
let text = plain_parse(&html, &*LS_ACTIVE_MEMBERS)?;
text.split(' ').next().unwrap().parse().map_err(Error::InvalidNumber)
}
fn parse_members(html_str: &str) -> Result<Paginated<LinkshellMember>> {
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<LinkshellMember> = html
.select(&*ITEM_ENTRY)
.map(|x| parse_single(x))
.collect::<Result<_>>()?;
Ok(Paginated {
pagination,
results,
})
}
fn parse_single<'a>(html: ElementRef<'a>) -> Result<LinkshellMember> {
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<Option<Role>> {
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)
}

View File

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

View File

@ -50,7 +50,7 @@ pub fn parse(html: &str) -> Result<Paginated<CharacterSearchItem>> {
})
}
fn parse_single<'a>(html: ElementRef<'a>) -> Result<CharacterSearchItem> {
crate fn parse_single<'a>(html: ElementRef<'a>) -> Result<CharacterSearchItem> {
let id = parse_id(html)?;
let name = plain_parse(html, &*ITEM_NAME)?;

View File

@ -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<Paginated<LinkshellSearchItem>> {
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<LinkshellSearchItem> = html
.select(&*ITEM_ENTRY)
.map(|x| parse_single(x))
.collect::<Result<_>>()?;
Ok(Paginated {
pagination,
results,
})
}
fn parse_single<'a>(html: ElementRef<'a>) -> Result<LinkshellSearchItem> {
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<u64> {
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<World> {
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<u8> {
plain_parse(html, &*ITEM_ACTIVE_MEMBERS)
.and_then(|x| x.parse().map_err(Error::InvalidNumber))
}

View File

@ -26,6 +26,7 @@ macro_rules! ffxiv_enum {
pub mod character;
pub mod free_company;
pub mod linkshell;
pub mod search;
ffxiv_enum!(

25
src/models/linkshell.rs Normal file
View File

@ -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<LinkshellMember>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LinkshellMember {
#[serde(flatten)]
pub character: CharacterSearchItem,
pub role: Option<Role>,
}
ffxiv_enum!(Role {
Master => "master",
Leader => "leader",
});

View File

@ -1,5 +1,6 @@
pub mod character;
pub mod free_company;
pub mod linkshell;
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Pagination {

View File

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