Skip to content

Commit

Permalink
Merge pull request #34 from bobbahbrown/yogstation
Browse files Browse the repository at this point in the history
Version 1.3.4 - YogStation
  • Loading branch information
bobbah authored Apr 22, 2021
2 parents 1fc2f53 + 17dea1b commit bb46201
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 15 deletions.
6 changes: 3 additions & 3 deletions CentCom.API/CentCom.API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Version>1.3.3</Version>
<AssemblyVersion>1.3.3.0</AssemblyVersion>
<FileVersion>1.3.3.0</FileVersion>
<Version>1.3.4</Version>
<AssemblyVersion>1.3.4.0</AssemblyVersion>
<FileVersion>1.3.4.0</FileVersion>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down
4 changes: 2 additions & 2 deletions CentCom.API/Views/Viewer/viewbans.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@

@foreach (var b in Model.Bans.OrderByDescending(x => x.BannedOn))
{
<div class="ban-container @(b.Active ? "active-ban" : "expired-ban")">
<div class="ban-container @(b.Active ? "active-ban" : (b.UnbannedBy != null ? "lifted-ban" : "expired-ban"))">
<div class="ban-title">
<span>@(b.Type) Ban / @b.SourceName / @(b.Active ? "Active" : "Expired")</span>
<span>@(b.Type) Ban / @b.SourceName / @(b.Active ? "Active" : (b.UnbannedBy != null ? "Unbanned" : "Expired"))</span>
</div>
<div class="ban-contents">
<div class="ban-reason">@b.Reason</div>
Expand Down
6 changes: 3 additions & 3 deletions CentCom.Common/CentCom.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Version>1.3.3</Version>
<AssemblyVersion>1.3.3.0</AssemblyVersion>
<FileVersion>1.3.3.0</FileVersion>
<Version>1.3.4</Version>
<AssemblyVersion>1.3.4.0</AssemblyVersion>
<FileVersion>1.3.4.0</FileVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
65 changes: 65 additions & 0 deletions CentCom.Server/BanSources/YogBanParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using CentCom.Common.Data;
using CentCom.Common.Models;
using CentCom.Server.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace CentCom.Server.BanSources
{
public class YogBanParser : BanParser
{
public override Dictionary<string, BanSource> Sources => new Dictionary<string, BanSource>()
{
{ "yogstation", new BanSource()
{
Display = "YogStation",
Name = "yogstation",
RoleplayLevel = RoleplayLevel.Medium
} }
};
public override bool SourceSupportsBanIDs => true;
private readonly YogBanService _banService;
private const int PagesPerBatch = 12;

public YogBanParser(DatabaseContext dbContext, YogBanService banService, ILogger<YogBanParser> logger) : base(dbContext, logger)
{
_banService = banService;
}

public override async Task<IEnumerable<Ban>> FetchNewBansAsync()
{
_logger.LogInformation("Getting new bans for YogStation...");
var recent = await _dbContext.Bans
.Where(x => Sources.Keys.Contains(x.SourceNavigation.Name))
.OrderByDescending(x => x.BannedOn)
.Take(5)
.Include(x => x.JobBans)
.Include(x => x.SourceNavigation)
.ToListAsync();
var foundBans = new List<Ban>();
var page = 1;

while (true)
{
var batch = (await _banService.GetBansBatchedAsync(page, PagesPerBatch)).ToArray();
foundBans.AddRange(batch);
if (!batch.Any() || batch.Any(x => recent.Any(y => y.BanID == x.BanID)))
{
break;
}
page += PagesPerBatch;
}

return foundBans;
}

public override async Task<IEnumerable<Ban>> FetchAllBansAsync()
{
_logger.LogInformation("Getting all bans for YogStation...");
return await _banService.GetBansBatchedAsync();
}
}
}
6 changes: 3 additions & 3 deletions CentCom.Server/CentCom.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<AssemblyVersion>1.3.3.0</AssemblyVersion>
<FileVersion>1.3.3.0</FileVersion>
<Version>1.3.3</Version>
<AssemblyVersion>1.3.4.0</AssemblyVersion>
<FileVersion>1.3.4.0</FileVersion>
<Version>1.3.4</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
6 changes: 4 additions & 2 deletions CentCom.Server/Services/BeeBanService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ namespace CentCom.Server.Services
{
public class BeeBanService
{
private const int _parallelRequests = 12;
private readonly IRestClient _client;
private const string BaseUrl = "https://beestation13.com/";
private readonly Regex _pagesPattern = new Regex("page [0-9]+ of (?<maxpages>[0-9]+)", RegexOptions.Compiled);
Expand Down Expand Up @@ -49,10 +50,11 @@ private async Task<IEnumerable<Ban>> GetBansAsync(int page = 1)
{
BannedOn = DateTime.SpecifyKind(DateTime.Parse(b["bantime"].GetString()), DateTimeKind.Utc),
BannedBy = b["a_ckey"].GetString(),
UnbannedBy = b["unbanned_ckey"].GetString(),
BanType = b["roles"].EnumerateArray().Select(x => x.GetString()).Contains("Server")
? BanType.Server
: BanType.Job,
Expires = expiryString == null ? (DateTime?)null : DateTime.SpecifyKind(DateTime.Parse(expiryString), DateTimeKind.Utc),
Expires = expiryString == null ? null : DateTime.SpecifyKind(DateTime.Parse(expiryString), DateTimeKind.Utc),
CKey = b["ckey"].GetString(),
Reason = b["reason"].GetString(),
BanID = b["id"].GetInt32().ToString(),
Expand Down Expand Up @@ -86,7 +88,7 @@ await range.AsyncParallelForEach(async page =>
{
toReturn.Add(b);
}
}, 12);
}, _parallelRequests);
return toReturn;
}

Expand Down
140 changes: 138 additions & 2 deletions CentCom.Server/Services/YogBanService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,143 @@
namespace CentCom.Server.Services
using CentCom.Common.Models;
using CentCom.Server.Exceptions;
using CentCom.Server.Extensions;
using Microsoft.Extensions.Logging;
using RestSharp;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;

namespace CentCom.Server.Services
{
public class YogBanService
{
// TBD
private const int _parallelRequests = 12;
private const int _requestsPerMinute = 60;
private readonly IRestClient _client;
private const string BaseUrl = "https://yogstation.net/";
private readonly Regex _pagesPattern = new Regex(@"<a class=""pagination-link[^>]+>(?<pagenum>[0-9]+)<\/a>", RegexOptions.Compiled | RegexOptions.Multiline);
private static readonly BanSource _source = new BanSource() { Name = "yogstation" };
private readonly ILogger _logger;

public YogBanService(ILogger<YogBanService> logger)
{
_client = new RestClient(BaseUrl);
_logger = logger;
}

private async Task<IEnumerable<Ban>> GetBansAsync(int page = 1)
{
var request = new RestRequest("bans", Method.GET, DataFormat.Json)
.AddQueryParameter("json", "1")
.AddQueryParameter("page", page.ToString())
.AddQueryParameter("amount", "1000");
var response = await _client.ExecuteAsync(request);

if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
_logger.LogError("Yogstation website returned a non-200 HTTP response code: {StatusCode}, aborting parse", response.StatusCode);
throw new BanSourceUnavailableException($"Yogstation website returned a non-200 HTTP response code: {response.StatusCode}, aborting parse");
}

var toReturn = new List<Ban>();
var content = JsonSerializer.Deserialize<IEnumerable<Dictionary<string, JsonElement>>>(response.Content);
foreach (var b in content)
{
var expiryString = b["unbanned_datetime"].GetString() ?? b["expiration_time"].GetString();
var toAdd = new Ban()
{
BannedOn = DateTime.SpecifyKind(DateTime.Parse(b["bantime"].GetString()), DateTimeKind.Utc),
BannedBy = b["a_ckey"].GetString(),
UnbannedBy = b["unbanned_ckey"].GetString(),
BanType = b["roles"].EnumerateArray().Select(x => x.GetString()).Contains("Server")
? BanType.Server
: BanType.Job,
Expires = expiryString == null ? null : DateTime.SpecifyKind(DateTime.Parse(expiryString), DateTimeKind.Utc),
CKey = b["ckey"].GetString(),
Reason = b["reason"].GetString(),
BanID = b["id"].GetInt32().ToString(),
SourceNavigation = _source
};

if (toAdd.BanType == BanType.Job)
{
toAdd.AddJobRange(b["roles"].EnumerateArray().Select(x => x.GetString()));
}

toReturn.Add(toAdd);
}

return toReturn;
}

public async Task<IEnumerable<Ban>> GetBansBatchedAsync(int startpage = 1, int pages = -1)
{
var maxPages = await GetNumberOfPagesAsync();
var range = Enumerable.Range(startpage, pages != -1 ? pages : maxPages + 1); // pad with a page for safety
var toReturn = new ConcurrentBag<Ban>();
var allTasks = new List<Task>();
var throttle = new SemaphoreSlim(_parallelRequests);
var requestsInPeriod = 0;
var periodReset = DateTime.Now.AddMinutes(1);

foreach (var page in range)
{
await throttle.WaitAsync();

// Handle rate limiting
if (requestsInPeriod >= _requestsPerMinute)
{
await Task.Delay(periodReset - DateTime.Now);
periodReset = periodReset.AddMinutes(1);
requestsInPeriod = 0;
}
requestsInPeriod++;

// Fetch page
allTasks.Add(Task.Run(async () =>
{
try
{
foreach (var b in await GetBansAsync(page))
{
toReturn.Add(b);
}
}
finally
{
throttle.Release();
}
}));
}

await Task.WhenAll(allTasks);

return toReturn;
}

private async Task<int> GetNumberOfPagesAsync()
{
var request = new RestRequest("bans", Method.GET, DataFormat.None);
var result = await _client.ExecuteAsync(request);

if (result.StatusCode != System.Net.HttpStatusCode.OK)
{
throw new Exception($"Unexpected non-200 status code [{result.StatusCode}] when trying to retrieve number of ban pages on yogstation.net");
}

var match = _pagesPattern.Matches(result.Content);
if (!match.Any())
{
throw new Exception("Failed to find page numbers on yogstation.net bans page");
}

// Yog has 20 bans per page by default, and we retrieve pages of 1000
return (int)Math.Ceiling(int.Parse(match.Last().Groups["pagenum"].Value) * 20 / 1000.0);
}
}
}

0 comments on commit bb46201

Please sign in to comment.