Add /Api/WWW/GitHub/Wiki/History endpoint

This commit is contained in:
JustArchi
2021-03-08 22:27:03 +01:00
parent d7c7b3813e
commit a4310d28f7
2 changed files with 123 additions and 25 deletions

View File

@@ -20,10 +20,13 @@
// limitations under the License.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using AngleSharp.Dom;
using Markdig;
@@ -54,6 +57,79 @@ namespace ArchiSteamFarm {
return await GetReleaseFromURL(SharedInfo.GithubReleaseURL + "/tags/" + version).ConfigureAwait(false);
}
internal static async Task<Dictionary<string, DateTime>?> GetWikiHistory(string page) {
if (string.IsNullOrEmpty(page)) {
throw new ArgumentNullException(nameof(page));
}
if (ASF.WebBrowser == null) {
throw new InvalidOperationException(nameof(ASF.WebBrowser));
}
string url = SharedInfo.ProjectURL + "/wiki/" + page + "/_history";
using WebBrowser.HtmlDocumentResponse? response = await ASF.WebBrowser.UrlGetToHtmlDocument(url, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
if (response == null) {
return null;
}
if (response.StatusCode.IsClientErrorCode()) {
return response.StatusCode switch {
HttpStatusCode.NotFound => new Dictionary<string, DateTime>(0),
_ => null
};
}
List<IElement> revisionNodes = response.Content.SelectNodes("//li[contains(@class, 'wiki-history-revision')]");
Dictionary<string, DateTime> result = new(revisionNodes.Count);
foreach (IElement revisionNode in revisionNodes) {
IElement? versionNode = revisionNode.SelectSingleElementNode(".//input/@value");
if (versionNode == null) {
ASF.ArchiLogger.LogNullError(nameof(versionNode));
return null;
}
string versionText = versionNode.GetAttribute("value");
if (string.IsNullOrEmpty(versionText)) {
ASF.ArchiLogger.LogNullError(nameof(versionText));
return null;
}
IElement? dateTimeNode = revisionNode.SelectSingleElementNode(".//relative-time/@datetime");
if (dateTimeNode == null) {
ASF.ArchiLogger.LogNullError(nameof(dateTimeNode));
return null;
}
string dateTimeText = dateTimeNode.GetAttribute("datetime");
if (string.IsNullOrEmpty(dateTimeText)) {
ASF.ArchiLogger.LogNullError(nameof(dateTimeText));
return null;
}
if (!DateTime.TryParse(dateTimeText, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out DateTime dateTime)) {
ASF.ArchiLogger.LogNullError(nameof(dateTime));
return null;
}
result[versionText] = dateTime.ToUniversalTime();
}
return result;
}
internal static async Task<string?> GetWikiPage(string page, string? revision = null) {
if (string.IsNullOrEmpty(page)) {
throw new ArgumentNullException(nameof(page));

View File

@@ -20,6 +20,8 @@
// limitations under the License.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Net;
using System.Threading.Tasks;
@@ -31,6 +33,51 @@ using Microsoft.AspNetCore.Mvc;
namespace ArchiSteamFarm.IPC.Controllers.Api {
[Route("Api/WWW")]
public sealed class WWWController : ArchiController {
/// <summary>
/// Fetches history of specific GitHub page from ASF project.
/// </summary>
/// <remarks>
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime.
/// </remarks>
[HttpGet("GitHub/Wiki/History/{page:required}")]
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
public async Task<ActionResult<GenericResponse>> GitHubWikiHistoryGet(string page) {
if (string.IsNullOrEmpty(page)) {
throw new ArgumentNullException(nameof(page));
}
Dictionary<string, DateTime>? revisions = await GitHub.GetWikiHistory(page).ConfigureAwait(false);
return revisions != null ? revisions.Count > 0 ? Ok(new GenericResponse<ImmutableDictionary<string, DateTime>>(revisions.ToImmutableDictionary())) : BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
}
/// <summary>
/// Fetches specific GitHub page of ASF project.
/// </summary>
/// <remarks>
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime.
/// Specifying revision is optional - when not specified, will fetch latest available. If specified revision is invalid, GitHub will automatically fetch the latest revision as well.
/// </remarks>
[HttpGet("GitHub/Wiki/Page/{page:required}")]
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
public async Task<ActionResult<GenericResponse>> GitHubWikiPageGet(string page, [FromQuery] string? revision = null) {
if (string.IsNullOrEmpty(page)) {
throw new ArgumentNullException(nameof(page));
}
string? html = await GitHub.GetWikiPage(page, revision).ConfigureAwait(false);
return html switch {
null => StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries))),
"" => BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))),
_ => Ok(new GenericResponse<string>(html))
};
}
/// <summary>
/// Fetches the most recent GitHub release of ASF project.
/// </summary>
@@ -81,31 +128,6 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return releaseResponse != null ? Ok(new GenericResponse<GitHubReleaseResponse>(new GitHubReleaseResponse(releaseResponse))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
}
/// <summary>
/// Fetches specific GitHub page of ASF project.
/// </summary>
/// <remarks>
/// This is internal API being utilizied by our ASF-ui IPC frontend. You should not depend on existence of any /Api/WWW endpoints as they can disappear and change anytime.
/// Specifying revision is optional - when not specified, will fetch latest available. If specified revision is invalid, GitHub will automatically fetch the latest revision as well.
/// </remarks>
[HttpGet("GitHub/Wiki/Page/{page:required}")]
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
public async Task<ActionResult<GenericResponse>> GitHubWikiPageGet(string page, [FromQuery] string? revision = null) {
if (string.IsNullOrEmpty(page)) {
throw new ArgumentNullException(nameof(page));
}
string? html = await GitHub.GetWikiPage(page, revision).ConfigureAwait(false);
return html switch {
null => StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries))),
"" => BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(page)))),
_ => Ok(new GenericResponse<string>(html))
};
}
/// <summary>
/// Sends a HTTPS request through ASF's built-in HttpClient.
/// </summary>