feat: add linkshell parsing
This commit is contained in:
parent
d6f914b9c7
commit
d3ad9c7923
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -24,6 +24,7 @@ macro_rules! selectors {
|
|||
|
||||
pub mod character;
|
||||
pub mod free_company;
|
||||
pub mod linkshell;
|
||||
pub mod search;
|
||||
|
||||
pub use self::{
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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))
|
||||
}
|
|
@ -26,6 +26,7 @@ macro_rules! ffxiv_enum {
|
|||
|
||||
pub mod character;
|
||||
pub mod free_company;
|
||||
pub mod linkshell;
|
||||
pub mod search;
|
||||
|
||||
ffxiv_enum!(
|
||||
|
|
|
@ -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",
|
||||
});
|
|
@ -1,5 +1,6 @@
|
|||
pub mod character;
|
||||
pub mod free_company;
|
||||
pub mod linkshell;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct Pagination {
|
||||
|
|
|
@ -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,
|
||||
}
|
Loading…
Reference in New Issue