Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion CentCom.Server/BanSources/TgBanParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@ public override async Task<IEnumerable<Ban>> FetchNewBansAsync()
.Include(x => x.JobBans)
.Include(x => x.SourceNavigation)
.ToListAsync();
return await _banService.GetBansBatchedAsync(searchFor: recent.Select(x => x.Id));

var foundBans = new List<Ban>();
var page = 1;
while (true)
{
var bans = await _banService.GetBansAsync(page);
if (bans.Count == 0)
break;

foundBans.AddRange(bans.Select(x => x.AsBan(Sources["tgstation"])));

// Check for existing bans
if (foundBans.Any(x => recent.Any(y => y.BanID == x.BanID)))
break;

page++;
}

return foundBans.DistinctBy(x => x.BanID);
}
}
164 changes: 85 additions & 79 deletions CentCom.Server/External/Raw/TgRawBan.cs
Original file line number Diff line number Diff line change
@@ -1,106 +1,112 @@
using System;
using System.Globalization;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using CentCom.Common.Extensions;
using CentCom.Common.Models;

namespace CentCom.Server.External.Raw;

public class TgRawBan : IRawBan
public class TgUser
{
private string _bannedAt;
private string _expirationTime;
private string _unbannedAt;

[JsonPropertyName("id")]
public int Id { get; set; }

[JsonPropertyName("expiration_time")]
public string ExpirationTimeRaw
{
get => _expirationTime; set
{
_expirationTime = value;
ExpirationTime = ParseTgDateTime(value);
}
}

public DateTime? ExpirationTime { get; private set; }

[JsonPropertyName("role")]
public string Role { get; set; }

[JsonPropertyName("ckey")]
public string CKey { get; set; }

[JsonPropertyName("userIdentifier")]
public string UserIdentifier { get; set; }
}

[JsonPropertyName("a_ckey")]
public string AdminCKey { get; set; }
public class TgServer
{
[JsonPropertyName("name")]
public string Name { get; set; }

[JsonPropertyName("identifier")]
public string Identifier { get; set; }

[JsonPropertyName("port")]
public int Port { get; set; }

[JsonPropertyName("address")]
public string Address { get; set; }

[JsonPropertyName("url")]
public string Url { get; set; }

[JsonPropertyName("publicLogs")]
public string PublicLogsUrl { get; set; }

[JsonPropertyName("rawLogs")]
public string RawLogsUrl { get; set; }

[JsonPropertyName("round")]
public int Round { get; set; }
}

public class TgRawBan : IRawBan
{
[JsonPropertyName("id")]
public int Id { get; set; }

[JsonPropertyName("admin")]
public TgUser Admin { get; set; }

[JsonPropertyName("target")]
public TgUser Target { get; set; }

[JsonPropertyName("unbanner")]
public TgUser? Unbanner { get; set; }

Check warning on line 58 in CentCom.Server/External/Raw/TgRawBan.cs

View workflow job for this annotation

GitHub Actions / ubuntu

The annotation for nullable reference types should only be used in code within a '#nullable' annotations context.

[JsonPropertyName("roles")]
public List<string> Roles { get; set; }

[JsonPropertyName("bantime")]
public DateTimeOffset BanTime { get; set; }

[JsonPropertyName("unbannedTime")]
public DateTimeOffset? UnbannedTime { get; set; }

[JsonPropertyName("round")]
public int Round { get; set; }

[JsonPropertyName("status")]
public string Status { get; set; }

[JsonPropertyName("reason")]
public string Reason { get; set; }

[JsonPropertyName("bantime")]
public string BannedAtRaw
{
get => _bannedAt; set
{
_bannedAt = value;
BannedAt = ParseTgDateTime(value).Value;
}
}

public DateTime BannedAt { get; private set; }

[JsonPropertyName("unbanned_datetime")]
public string UnbannedAtRaw
{
get => _unbannedAt; set
{
_unbannedAt = value;
UnbannedAt = ParseTgDateTime(value);
}
}

public DateTime? UnbannedAt { get; private set; }

[JsonPropertyName("unbanned_Ckey")]
public string UnbannedBy { get; set; }

public BanType GetBanType() => Role.ToLower() == "server" ? BanType.Server : BanType.Job;

[JsonPropertyName("server")]
public TgServer Server { get; set; }

[JsonPropertyName("expiration")]
public DateTimeOffset? Expiration { get; set; }

[JsonPropertyName("banIds")]
public List<int> BanIds { get; set; }

public BanType GetBanType() => Roles.Count == 1 && Roles[0] == "Server" ? BanType.Server : BanType.Job;

public Ban AsBan(BanSource source)
{
var toReturn = new Ban
{
BanID = Id.ToString(),
BannedBy = AdminCKey,
BannedOn = BannedAt,
SourceNavigation = source,
BanType = GetBanType(),
CKey = CKey,
UnbannedBy = UnbannedBy,
CKey = Target.CKey,
BannedOn = BanTime.UtcDateTime,
BannedBy = Admin.CKey,
Reason = Reason,
Expires = UnbannedAt ?? ExpirationTime,
SourceNavigation = source
Expires = Expiration?.UtcDateTime,
UnbannedBy = Unbanner?.CKey,
BanID = string.Join(";", BanIds),
JobBans = null,
BanAttributes = (BanAttribute)0
};

// Add job bans if relevant
if (toReturn.BanType == BanType.Job)
{
toReturn.AddJob(Role);
}
toReturn.AddJobRange(Roles);

return toReturn;
}

private static DateTime? ParseTgDateTime(string value)
{
if (DateTime.TryParse(value,
CultureInfo.InvariantCulture.DateTimeFormat,
DateTimeStyles.AllowWhiteSpaces,
out var expiration))
{
return expiration;
}

return null;
}
}
24 changes: 18 additions & 6 deletions CentCom.Server/External/TgApiResponse.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using CentCom.Server.External.Raw;

namespace CentCom.Server.External;

public class TgApiResponse
public class TgApiPagination
{
[JsonPropertyName("beforeid")]
public int BeforeId { get; set; }
[JsonPropertyName("items")]
public int Items { get; set; }

[JsonPropertyName("page")]
public int Page { get; set; }

[JsonPropertyName("per_page")]
public int PerPage { get; set; }
}

[JsonPropertyName("bans")]
public IEnumerable<TgRawBan> Bans { get; set; }
public class TgApiResponse
{
[JsonPropertyName("data")]
public IEnumerable<TgRawBan> Data { get; set; }

[JsonPropertyName("pagination")]
public TgApiPagination Pagination { get; set; }
}
54 changes: 15 additions & 39 deletions CentCom.Server/Services/TgBanService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using CentCom.Common.Extensions;
using CentCom.Common.Models;
using CentCom.Server.External;
using CentCom.Server.External.Raw;
Expand All @@ -28,57 +27,34 @@ public TgBanService(ILogger<TgBanService> logger) : base(logger)
}));
}

protected override string BaseUrl => "https://tgstation13.org/";
protected override string BaseUrl => "https://statbus.space/";

private async Task<List<TgRawBan>> GetBansAsync(int? startingId = null)
public async Task<List<TgRawBan>> GetBansAsync(int? page = null)
{
var request = new RestRequest("tgdb/publicbans.php")
.AddQueryParameter("format", "json");
if (startingId.HasValue)
request.AddQueryParameter("beforeid", startingId.ToString());
var request = new RestRequest($"bans/public/v1/{page}")
.AddQueryParameter("json", "true");
var response = await Client.ExecuteAsync<TgApiResponse>(request);

if (response.StatusCode != HttpStatusCode.OK)
FailedRequest(response);

return response.Data.Bans.ToList();
return response.Data.Data.ToList();
}

public async Task<IEnumerable<Ban>> GetBansBatchedAsync(int? startingId = null, IEnumerable<int> searchFor = null)
public async Task<IEnumerable<Ban>> GetBansBatchedAsync()
{
// Get bans, must use a sequential approach here due to the last ban
// ID only being available once we finish a page
var dirtyBans = new List<TgRawBan>();
var lastRequested = startingId;
List<TgRawBan> lastResponse;
do
var allBans = new List<TgRawBan>();
var page = 1;
while (true)
{
lastResponse = await GetBansAsync(lastRequested);
if (!lastResponse.Any())
var bans = await GetBansAsync(page);
if (bans.Count == 0)
break;

// If the last ban on the page is a job ban, get the next page to ensure we have the full ban
if (lastResponse[^1].GetBanType() == BanType.Job)
{
var nextPage = await GetBansAsync(lastResponse[^1].Id);
lastResponse.AddRange(nextPage.Where(x => x.CKey == lastResponse[^1].CKey && x.BannedAt == lastResponse[^1].BannedAt));
}

lastRequested = lastResponse.Min(x => x.Id);
dirtyBans.AddRange(lastResponse);
}
while (lastResponse.Any() && (searchFor == null || lastResponse.Any(x => searchFor.Contains(x.Id))));

// Flatten any jobbans
var intermediateBans = dirtyBans.Select(x => x.AsBan(BanSource));
var cleanBans = intermediateBans.Where(x => x.BanType == BanType.Server).ToList();
foreach (var group in intermediateBans.Where(x => x.BanType == BanType.Job).GroupBy(x => new { x.CKey, x.BannedOn }))
{
var firstBan = group.OrderBy(x => x.BanID).First();
firstBan.AddJobRange(group.SelectMany(x => x.JobBans).Select(x => x.Job));
cleanBans.Add(firstBan);

allBans.AddRange(bans);
page++;
}

return cleanBans;
return allBans.Select(x => x.AsBan(BanSource)).DistinctBy(x => x.BanID);
}
}
Loading