Skip to content

Commit 25486f8

Browse files
committed
add cwls search
1 parent d5a2e55 commit 25486f8

10 files changed

Lines changed: 506 additions & 8 deletions

File tree

NetStone.Test/Tests.cs

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using NetStone.GameData.Packs;
66
using NetStone.Model.Parseables.Character;
77
using NetStone.Search.Character;
8+
using NetStone.Search.CWLS;
89
using NetStone.Search.FreeCompany;
910
using NetStone.StaticData;
1011
using NUnit.Framework;
@@ -575,16 +576,44 @@ public async Task CheckCrossworldLinkShell()
575576
foreach (var member in cwls.Members)
576577
{
577578
Console.WriteLine($"{member.Name} ({member.Rank}) {member.RankIcon}\n" +
578-
$"Id: {member.Id}\n" +
579-
$"Avatar: {member.Avatar}\n" +
580-
$"Server: {member.Server}\n" +
581-
$"LS Rank: {member.LinkshellRank}\n" +
582-
$"LS Rank Icon: {member.LinkshellRankIcon}");
583-
579+
$"\tId: {member.Id}\n" +
580+
$"\tAvatar: {member.Avatar}\n" +
581+
$"\tServer: {member.Server}\n" +
582+
$"\tLS Rank: {member.LinkshellRank}\n" +
583+
$"\tLS Rank Icon: {member.LinkshellRankIcon}");
584584
}
585585
cwls = await cwls.GetNextPage();
586586
}
587-
587+
}
588+
589+
[Test]
590+
public async Task CheckCrossworldLinkShellSearch()
591+
{
592+
var emptyQuery = new CrossWorldLinkShellSearchQuery()
593+
{
594+
Name = "abcedfas",
595+
};
596+
var emptyResult = await this.lodestone.SearchCrossWorldLinkshell(emptyQuery);
597+
Assert.IsNotNull(emptyResult);
598+
Assert.False(emptyResult.HasResults);
599+
var query = new CrossWorldLinkShellSearchQuery()
600+
{
601+
Name = "Hell",
602+
ActiveMembers = CrossWorldLinkShellSearchQuery.ActiveMemberChoice.ElevenToThirty,
603+
DataCenter = "Chaos",
604+
};
605+
var results = await this.lodestone.SearchCrossWorldLinkshell(query);
606+
Assert.IsNotNull(results);
607+
Assert.True(results.HasResults);
608+
Assert.AreEqual(2, results.NumPages);
609+
while (results is not null)
610+
{
611+
foreach (var result in results.Results)
612+
{
613+
Console.WriteLine($"{result.Name} ({result.Id}): {result.ActiveMembers}\n");
614+
}
615+
results = await results.GetNextPage();
616+
}
588617
}
589618

590619
[Test]

NetStone/Definitions/DefinitionsContainer.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ public abstract class DefinitionsContainer : IDisposable
9696
/// </summary>
9797
public CrossWorldLinkShellMemberDefinition CrossWorldLinkShellMember { get; protected set; }
9898

99+
/// <summary>
100+
/// Definitions for cross world link shell searches
101+
/// </summary>
102+
public PagedDefinition<CrossWorldLinkShellSearchEntryDefinition> CrossWorldLinkShellSearch { get; protected set; }
103+
99104
/// <summary>
100105
/// Definitions for link shells
101106
/// </summary>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Newtonsoft.Json;
2+
3+
namespace NetStone.Definitions.Model.CWLS;
4+
/// <summary>
5+
/// Definition container for one Cross World Link Shell search result entry
6+
/// </summary>
7+
public class CrossWorldLinkShellSearchEntryDefinition : PagedEntryDefinition
8+
{
9+
/// <summary>
10+
/// ID
11+
/// </summary>
12+
[JsonProperty("ID")] public DefinitionsPack Id { get; set; }
13+
14+
/// <summary>
15+
/// Name
16+
/// </summary>
17+
[JsonProperty("NAME")] public DefinitionsPack Name { get; set; }
18+
19+
/// <summary>
20+
/// Rank
21+
/// </summary>
22+
[JsonProperty("DC")] public DefinitionsPack Dc { get; set; }
23+
24+
/// <summary>
25+
/// Rank Icon
26+
/// </summary>
27+
[JsonProperty("ACTIVE_MEMBERS")] public DefinitionsPack ActiveMembers { get; set; }
28+
}

NetStone/Definitions/XivApiDefinitionsContainer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public override async Task Reload()
6060

6161
this.CrossWorldLinkShell = await GetDefinition<CrossWorldLinkShellDefinition>("cwls/cwls.json");
6262
this.CrossWorldLinkShellMember = await GetDefinition<CrossWorldLinkShellMemberDefinition>("cwls/members.json");
63+
this.CrossWorldLinkShellSearch = await GetDefinition<PagedDefinition<CrossWorldLinkShellSearchEntryDefinition>>("search/cwls.json");
6364

6465
this.LinkShell = await GetDefinition<LinkShellDefinition>("linkshell/ls.json");
6566
this.LinkShellMember = await GetDefinition<LinkShellMemberDefinition>("linkshell/members.json");

NetStone/LodestoneClient.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
using NetStone.Model.Parseables.FreeCompany.Members;
1616
using NetStone.Model.Parseables.Linkshell;
1717
using NetStone.Model.Parseables.Search.Character;
18+
using NetStone.Model.Parseables.Search.CWLS;
1819
using NetStone.Model.Parseables.Search.FreeCompany;
1920
using NetStone.Search.Character;
21+
using NetStone.Search.CWLS;
2022
using NetStone.Search.FreeCompany;
2123

2224
namespace NetStone;
@@ -162,6 +164,15 @@ await GetParsed($"/lodestone/character/{query.BuildQueryString()}&page={page}",
162164
await GetParsed($"/lodestone/crossworld_linkshell/{id}?page={page}",
163165
node => new LodestoneCrossWorldLinkShell(this, node, this.Definitions,id));
164166

167+
/// <summary>
168+
/// Search lodestone for a character with the specified query.
169+
/// </summary>
170+
/// <param name="query"><see cref="CharacterSearchQuery"/> object detailing search parameters</param>
171+
/// <param name="page">The page of search results to fetch.</param>
172+
/// <returns><see cref="CharacterSearchPage"/> containing search results.</returns>
173+
public async Task<CrossWorldLinkShellSearchPage?> SearchCrossWorldLinkshell(CrossWorldLinkShellSearchQuery query, int page = 1) =>
174+
await GetParsed($"/lodestone/crossworld_linkshell/{query.BuildQueryString()}&page={page}",
175+
node => new CrossWorldLinkShellSearchPage(this, node, this.Definitions.CrossWorldLinkShellSearch, query));
165176

166177
/// <summary>
167178
/// Gets a link shell by its id.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System.Threading.Tasks;
2+
using HtmlAgilityPack;
3+
using NetStone.Definitions.Model.CWLS;
4+
using NetStone.Model.Parseables.Character;
5+
6+
namespace NetStone.Model.Parseables.Search.CWLS;
7+
8+
public class CrossWorldLinkShellSearchEntry : LodestoneParseable
9+
{
10+
private readonly LodestoneClient client;
11+
private readonly CrossWorldLinkShellSearchEntryDefinition definition;
12+
13+
///
14+
public CrossWorldLinkShellSearchEntry(LodestoneClient client, HtmlNode rootNode, CrossWorldLinkShellSearchEntryDefinition definition) :
15+
base(rootNode)
16+
{
17+
this.client = client;
18+
this.definition = definition;
19+
}
20+
21+
/// <summary>
22+
/// Character name
23+
/// </summary>
24+
public string Name => Parse(this.definition.Name);
25+
26+
/// <summary>
27+
/// Lodestone Id
28+
/// </summary>
29+
public string? Id => ParseHrefId(this.definition.Id);
30+
31+
public int ActiveMembers => int.TryParse(Parse(this.definition.ActiveMembers), out var parsed) ? parsed : -1;
32+
33+
/// <summary>
34+
/// Fetch character profile
35+
/// </summary>
36+
/// <returns>Task of retrieving character</returns>
37+
public async Task<LodestoneCharacter?> GetCharacter() =>
38+
this.Id is null ? null : await this.client.GetCharacter(this.Id);
39+
40+
///<inheritdoc />
41+
public override string ToString() => this.Name;
42+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using HtmlAgilityPack;
5+
using NetStone.Definitions;
6+
using NetStone.Definitions.Model;
7+
using NetStone.Definitions.Model.CWLS;
8+
using NetStone.Search.CWLS;
9+
10+
namespace NetStone.Model.Parseables.Search.CWLS;
11+
12+
/// <summary>
13+
/// Models cross world link shell search results
14+
/// </summary>
15+
public class CrossWorldLinkShellSearchPage : LodestoneParseable, IPaginatedResult<CrossWorldLinkShellSearchPage>
16+
{
17+
private readonly LodestoneClient client;
18+
private readonly CrossWorldLinkShellSearchQuery currentQuery;
19+
20+
private readonly PagedDefinition<CrossWorldLinkShellSearchEntryDefinition> pageDefinition;
21+
22+
/// <summary>
23+
/// Constructs character search results
24+
/// </summary>
25+
/// <param name="client"></param>
26+
/// <param name="rootNode"></param>
27+
/// <param name="pageDefinition"></param>
28+
/// <param name="currentQuery"></param>
29+
public CrossWorldLinkShellSearchPage(LodestoneClient client, HtmlNode rootNode, PagedDefinition<CrossWorldLinkShellSearchEntryDefinition> pageDefinition,
30+
CrossWorldLinkShellSearchQuery currentQuery) : base(rootNode)
31+
{
32+
this.client = client;
33+
this.currentQuery = currentQuery;
34+
this.pageDefinition = pageDefinition;
35+
}
36+
37+
/// <summary>
38+
/// Indicates if any results are present
39+
/// </summary>
40+
public bool HasResults => !HasNode(this.pageDefinition.NoResultsFound);
41+
42+
private CrossWorldLinkShellSearchEntry[]? parsedResults;
43+
44+
/// <summary>
45+
/// List all results
46+
/// </summary>
47+
public IEnumerable<CrossWorldLinkShellSearchEntry> Results
48+
{
49+
get
50+
{
51+
if (!this.HasResults)
52+
return Array.Empty<CrossWorldLinkShellSearchEntry>();
53+
54+
if (this.parsedResults == null)
55+
ParseSearchResults();
56+
57+
return this.parsedResults!;
58+
}
59+
}
60+
61+
private void ParseSearchResults()
62+
{
63+
var container = QueryContainer(this.pageDefinition);
64+
65+
this.parsedResults = new CrossWorldLinkShellSearchEntry[container.Length];
66+
for (var i = 0; i < this.parsedResults.Length; i++)
67+
{
68+
this.parsedResults[i] = new CrossWorldLinkShellSearchEntry(this.client, container[i], this.pageDefinition.Entry);
69+
}
70+
}
71+
72+
private int? currentPageVal;
73+
74+
///<inheritdoc />
75+
public int CurrentPage
76+
{
77+
get
78+
{
79+
if (!this.HasResults)
80+
return 0;
81+
82+
if (!this.currentPageVal.HasValue)
83+
ParsePagesCount();
84+
85+
return this.currentPageVal!.Value;
86+
}
87+
}
88+
89+
private int? numPagesVal;
90+
91+
///<inheritdoc />
92+
public int NumPages
93+
{
94+
get
95+
{
96+
if (!this.HasResults)
97+
return 0;
98+
99+
if (!this.numPagesVal.HasValue)
100+
ParsePagesCount();
101+
102+
return this.numPagesVal!.Value;
103+
}
104+
}
105+
106+
private void ParsePagesCount()
107+
{
108+
var results = ParseRegex(this.pageDefinition.PageInfo);
109+
110+
this.currentPageVal = int.Parse(results["CurrentPage"].Value);
111+
this.numPagesVal = int.Parse(results["NumPages"].Value);
112+
}
113+
114+
///<inheritdoc />
115+
public async Task<CrossWorldLinkShellSearchPage?> GetNextPage()
116+
{
117+
if (!this.HasResults)
118+
return null;
119+
120+
if (this.CurrentPage == this.NumPages)
121+
return null;
122+
123+
return await this.client.SearchCrossWorldLinkshell(this.currentQuery, this.CurrentPage + 1);
124+
}
125+
}

0 commit comments

Comments
 (0)