using HtmlAgilityPack;
using LANCommander.SDK;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Http;
using System.Reflection;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
namespace LANCommander.PCGamingWiki
{
public class PCGamingWikiClient
{
#region Internal models
public class GetPageIdModel
{
public class Cargoquery
{
public class Title
{
public string PageID { get; set; }
public string Page { get; set; }
[JsonProperty("Steam AppID")]
public string SteamAppID { get; set; }
}
public Title title { get; set; }
}
public List cargoquery { get; } = new List();
}
public class GetPageContentModel
{
public class Parse
{
public class Text
{
[JsonProperty("*")]
public string content { get; set; }
}
public string title { get; set; }
public int pageid { get; set; }
public Text text { get; set; }
}
public Parse parse { get; set; }
}
#endregion
private HttpClient Client;
public PCGamingWikiClient()
{
Client = new HttpClient();
Client.BaseAddress = new Uri("https://www.pcgamingwiki.com/");
Client.DefaultRequestHeaders.UserAgent.ParseAdd($"{AssemblyName.GetAssemblyName(Assembly.GetExecutingAssembly().Location).Name}/{Assembly.GetExecutingAssembly().GetName().Version.ToString()}");
}
public async Task Search(string keyword)
{
var results = await Client.GetStringAsync($"w/api.php?action=opensearch&format=json&formatversion=2&search={HttpUtility.UrlEncode(keyword)}&limit=1");
string pattern = @"(https:\/\/www.pcgamingwiki.com.+)""";
RegexOptions options = RegexOptions.Multiline;
var match = Regex.Match(results, pattern, options);
if (match.Success && match.Groups.Count == 2)
{
return new Uri(match.Groups[1].Value);
}
return null;
}
public Dictionary GetMultiplayerPlayerCounts(Uri url)
{
if (url == null)
return [];
HtmlWeb web = new();
HtmlDocument dom = web.Load(url);
return GetMultiplayerPlayerCounts(dom);
}
public async Task> GetMultiplayerPlayerCounts(string keyword)
{
var data = await SearchAndParseMultiplayerPlayerData(keyword, info => info.ToDictionary(x => x.Key, y => y.Value.PlayerCount));
return data ?? [];
}
public async Task> GetMultiplayerPlayerInfo(string keyword)
{
var data = await SearchAndParseMultiplayerPlayerData(keyword, info => info.Values.ToArray());
return data ?? [];
}
protected async Task SearchAndParseMultiplayerPlayerData(string keyword, Func, T> dataPropagateFunction) where T : class
{
var pageId = await GetPageIDAsync(keyword);
if (pageId != null)
{
var pageContent = await GetPageContentAsync(pageId);
var dom = new HtmlDocument();
dom.LoadHtml(pageContent ?? string.Empty);
var data = ParseMultiplayerPlayerInfo(dom);
return dataPropagateFunction.Invoke(data);
}
return null;
}
protected Dictionary GetMultiplayerPlayerCounts(HtmlDocument dom)
{
return ParseMultiplayerPlayerInfo(dom).ToDictionary(x => x.Key, y => y.Value.PlayerCount);
}
protected Dictionary ParseMultiplayerPlayerInfo(HtmlDocument dom)
{
ArgumentNullException.ThrowIfNull(dom);
HtmlNode multiplayerTable = dom.GetElementbyId("table-network-multiplayer");
if (multiplayerTable == null)
return [];
var results = new Dictionary(StringComparer.OrdinalIgnoreCase);
var multiplayerRows = multiplayerTable.SelectNodes(".//tr[contains(@class, 'table-network-multiplayer-body-row')]");
var multiplayerAbbrs = multiplayerTable.SelectNodes(".//abbr");
var multiplayerCounts = multiplayerTable.SelectNodes(".//td[contains(@class, 'table-network-multiplayer-body-players')]");
var multiplayerNotes = multiplayerTable.SelectNodes(".//td[contains(@class, 'table-network-multiplayer-body-notes')]");
foreach (var row in multiplayerRows)
{
var abbr_node = row.SelectNodes(".//abbr");
var count_node = row.SelectNodes(".//td[contains(@class, 'table-network-multiplayer-body-players')]");
var notes_node = row.SelectNodes(".//td[contains(@class, 'table-network-multiplayer-body-notes')]");
if (abbr_node == null || count_node == null)
continue;
var type = abbr_node[0].InnerText;
var players = count_node[0].InnerText;
var notes = null as string;
if (notes_node.Count == 1 && notes_node[0]?.ChildNodes.Count > 0)
{
var notelines = notes_node.FirstOrDefault()?.ChildNodes
.Select(x => x.InnerText)
.Select(x => x.TrimEnd('.'));
notes = string.Join("\n", notelines ?? []);
notes = notes.Replace("\n\n", ". ");
notes = notes.ReplaceLineEndings("");
notes = HttpUtility.HtmlDecode(notes);
}
if (type == null)
continue;
results.TryAdd(type, new MultiplayerInfo { MultiplayerType = type });
if (Int32.TryParse(players, out int playerCount))
{
results[type].PlayerCount = playerCount;
}
if (!string.IsNullOrEmpty(notes))
{
results[type].Notes = notes;
}
}
return results;
}
public async Task GetPageIDAsync(string keyword)
{
try
{
string getPageIdUrl = $"https://www.pcgamingwiki.com/w/api.php?action=cargoquery&format=json&tables=Infobox_game&fields=Infobox_game._pageID%3DPageID%2C_pageName%3DPage%2CSteam_AppID&where=Infobox_game._pageName%3D%22{HttpUtility.UrlEncode(keyword)}%22&formatversion=1";
// HttpClient.GetFromJsonAsync<> does not seem to deserialize list properly (at least with the usedmodel)
// read json and parse via Newtonsoft lib
string getPageIdJson = await Client.GetStringAsync(getPageIdUrl);
var pageId = JsonConvert.DeserializeObject(getPageIdJson);
if (pageId == null || pageId.cargoquery == null || pageId.cargoquery.Count != 1)
return null;
var title = pageId.cargoquery[0]?.title;
return title?.PageID;
}
catch (Exception ex)
{
// TODO: add logging
//Logger?.LogError(ex, $"Could not parse page id from PCGamingWiki for retrieving page with keyword {keyword}");
}
return null;
}
public async Task GetPageContentAsync(string pageId)
{
try
{
string getPageContentUrl = $"https://www.pcgamingwiki.com/w/api.php?action=parse&format=json&prop=text&pageid={HttpUtility.UrlEncode(pageId)}";
// HttpClient.GetFromJsonAsync<> does not seem to deserialize list properly (at least with the usedmodel)
// read json and parse via Newtonsoft lib
string getPageContentJson = await Client.GetStringAsync(getPageContentUrl);
var pageContent = JsonConvert.DeserializeObject(getPageContentJson);
return pageContent?.parse?.text?.content;
}
catch (Exception ex)
{
// TODO: add logging
//Logger?.LogError(ex, $"Could not parse page content from PCGamingWiki for retrieving page with id {pageId}");
}
return null;
}
}
public record MultiplayerInfo()
{
public required string MultiplayerType { get; set; }
public int PlayerCount { get; set; }
public string? Notes { get; set; }
}
}