From da953455ffb42cd1d7f6428bc0a8b80284f0c281 Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 21 Apr 2025 17:22:01 +0700 Subject: [PATCH 01/21] refactor: apply faithie's changes --- ...iscordBotsList.Api.Adapter.Discord.Net.csproj | 2 +- .../DiscordBotsList.Api.Tests.csproj | 14 +++++++------- DiscordBotsList.Api/AuthenticatedBotListApi.cs | 2 +- DiscordBotsList.Api/DiscordBotListApi.cs | 4 ++-- DiscordBotsList.Api/DiscordBotsList.Api.csproj | 4 ++-- DiscordBotsList.Api/Internal/Bot.cs | 9 +++++---- DiscordBotsList.Api/Internal/Entity.cs | 16 ++-------------- DiscordBotsList.Api/Internal/SelfBot.cs | 2 +- DiscordBotsList.Api/Internal/User.cs | 4 ++-- DiscordBotsList.Api/Objects/IDblBot.cs | 2 +- 10 files changed, 24 insertions(+), 35 deletions(-) diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj index d1c8d48..fa7dbe9 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj +++ b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj @@ -2,7 +2,7 @@ net8.0 - Velddev, Faith Viola + Velddev, Faith Top.gg DiscordBotsList.Api.Adapter.Discord.Net Adapter for Discord.Net diff --git a/DiscordBotsList.Api.Tests/DiscordBotsList.Api.Tests.csproj b/DiscordBotsList.Api.Tests/DiscordBotsList.Api.Tests.csproj index 462f4fc..8c2d6c6 100644 --- a/DiscordBotsList.Api.Tests/DiscordBotsList.Api.Tests.csproj +++ b/DiscordBotsList.Api.Tests/DiscordBotsList.Api.Tests.csproj @@ -8,22 +8,22 @@ - + - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + diff --git a/DiscordBotsList.Api/AuthenticatedBotListApi.cs b/DiscordBotsList.Api/AuthenticatedBotListApi.cs index 764ab5d..4d0ca53 100644 --- a/DiscordBotsList.Api/AuthenticatedBotListApi.cs +++ b/DiscordBotsList.Api/AuthenticatedBotListApi.cs @@ -30,7 +30,7 @@ public async Task GetMeAsync() { var bot = await GetBotAsync(_selfId); bot.api = this; - return bot; + return (IDblSelfBot)bot; } /// diff --git a/DiscordBotsList.Api/DiscordBotListApi.cs b/DiscordBotsList.Api/DiscordBotListApi.cs index 107bc9b..64f27ed 100644 --- a/DiscordBotsList.Api/DiscordBotListApi.cs +++ b/DiscordBotsList.Api/DiscordBotListApi.cs @@ -41,7 +41,7 @@ public async Task> GetBotsAsync(int count = 50, int page /// Bot Object public async Task GetBotAsync(ulong id) { - return await GetBotAsync(id); + return (IDblBot)await GetBotAsync(id); } /// @@ -61,7 +61,7 @@ public async Task GetBotStatsAsync(ulong id) /// User Object public async Task GetUserAsync(ulong id) { - return await GetAsync($"users/{id}"); + return (IDblUser)await GetAsync($"users/{id}"); } /// diff --git a/DiscordBotsList.Api/DiscordBotsList.Api.csproj b/DiscordBotsList.Api/DiscordBotsList.Api.csproj index e4a13d4..2b06805 100644 --- a/DiscordBotsList.Api/DiscordBotsList.Api.csproj +++ b/DiscordBotsList.Api/DiscordBotsList.Api.csproj @@ -2,7 +2,7 @@ net8.0 - Velddev, Faith Viola + Velddev, Faith Top.gg top.gg api wrapper Mike Veldsink @@ -21,7 +21,7 @@ - + diff --git a/DiscordBotsList.Api/Internal/Bot.cs b/DiscordBotsList.Api/Internal/Bot.cs index 2025737..625797f 100644 --- a/DiscordBotsList.Api/Internal/Bot.cs +++ b/DiscordBotsList.Api/Internal/Bot.cs @@ -7,7 +7,7 @@ namespace DiscordBotsList.Api.Internal { - public class Bot : Entity, IDblBot + public class Bot : Entity { internal DiscordBotListApi api; @@ -36,9 +36,10 @@ public class Bot : Entity, IDblBot [JsonPropertyName("vanity")] public string vanity { get; set; } [JsonPropertyName("points")] public int points { get; set; } - + [JsonPropertyName("monthlyPoints")] public int monthlyPoints { get; set; } + [JsonPropertyName("review")] public string VanityTag => vanity; public DateTime ApprovedAt => approvedAt; @@ -56,7 +57,7 @@ public class Bot : Entity, IDblBot public List OwnerIds => owners.ToList(); public int Points => points; - + public int MonthlyPoints => monthlyPoints; public string ShortDescription => shortDescription; @@ -64,7 +65,7 @@ public class Bot : Entity, IDblBot public List Tags => tags.ToList(); public string SupportUrl => "https://discord.gg/" + SupportInviteCode; - + public string VanityUrl => "https://top.gg/bot/" + vanity; public string WebsiteUrl => websiteUrl; diff --git a/DiscordBotsList.Api/Internal/Entity.cs b/DiscordBotsList.Api/Internal/Entity.cs index 3dc0066..5cfe8b7 100644 --- a/DiscordBotsList.Api/Internal/Entity.cs +++ b/DiscordBotsList.Api/Internal/Entity.cs @@ -1,27 +1,15 @@ -using DiscordBotsList.Api.Objects; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; namespace DiscordBotsList.Api.Internal { - public class Entity : IDblEntity + public class Entity { [JsonPropertyName("avatar")] public string Avatar { get; set; } - [JsonPropertyName("defAvatar")] public string DefaultAvatar { get; set; } - - public string AvatarUrl => !string.IsNullOrEmpty(Avatar) - ? $"https://cdn.discordapp.com/{Id}/{Avatar}.png" - : $"https://cdn.discordapp.com/{Id}/{DefaultAvatar}.png"; - [JsonPropertyName("id")] public ulong Id { get; set; } [JsonPropertyName("username")] public string Username { get; set; } [JsonPropertyName("discriminator")] public string Discriminator { get; set; } - - public override string ToString() - { - return $"{Username}#{Discriminator}"; - } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Internal/SelfBot.cs b/DiscordBotsList.Api/Internal/SelfBot.cs index b3cc608..60bf694 100644 --- a/DiscordBotsList.Api/Internal/SelfBot.cs +++ b/DiscordBotsList.Api/Internal/SelfBot.cs @@ -4,7 +4,7 @@ namespace DiscordBotsList.Api.Internal { - internal class SelfBot : Bot, IDblSelfBot + internal class SelfBot : Bot { public async Task> GetVotersAsync() { diff --git a/DiscordBotsList.Api/Internal/User.cs b/DiscordBotsList.Api/Internal/User.cs index 66ca92e..e8365c3 100644 --- a/DiscordBotsList.Api/Internal/User.cs +++ b/DiscordBotsList.Api/Internal/User.cs @@ -3,10 +3,10 @@ namespace DiscordBotsList.Api.Internal { - public class User : Entity, IDblUser + public class User : Entity { [JsonPropertyName("social")] public SocialConnections Social { get; set; } - + [JsonPropertyName("bio")] public string Biography { get; set; } [JsonPropertyName("banner")] public string BannerUrl { get; set; } diff --git a/DiscordBotsList.Api/Objects/IDblBot.cs b/DiscordBotsList.Api/Objects/IDblBot.cs index 3a32bde..23098d6 100644 --- a/DiscordBotsList.Api/Objects/IDblBot.cs +++ b/DiscordBotsList.Api/Objects/IDblBot.cs @@ -31,7 +31,7 @@ public interface IDblBot : IDblEntity string VanityUrl { get; } int Points { get; } - + int MonthlyPoints { get; } Task GetStatsAsync(); From 86ea8bae126b97442314574939f5b80e431f033f Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 21 Apr 2025 17:41:17 +0700 Subject: [PATCH 02/21] feat: remove shards and many other properties --- .../DiscordNetDiscordBotsListApi.cs | 27 ---------------- .../SubmissionAdapter.cs | 23 ------------- .../Utils/DiscordNetDblUtils.cs | 11 ------- .../AuthenticatedBotListApi.cs | 32 +------------------ DiscordBotsList.Api/Internal/Bot.cs | 8 ++--- DiscordBotsList.Api/Internal/Entity.cs | 2 -- .../Internal/GuildCountObject.cs | 26 +-------------- DiscordBotsList.Api/Internal/SelfBot.cs | 10 ------ DiscordBotsList.Api/Internal/User.cs | 4 --- DiscordBotsList.Api/Objects/IBotStats.cs | 4 --- DiscordBotsList.Api/Objects/IDblBot.cs | 8 +---- DiscordBotsList.Api/Objects/IDblEntity.cs | 5 --- DiscordBotsList.Api/Objects/IDblUser.cs | 4 --- README.md | 2 -- 14 files changed, 5 insertions(+), 161 deletions(-) diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs index 4882985..872fc0c 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs @@ -12,11 +12,6 @@ public static DiscordNetDblApi CreateDblApi(this DiscordSocketClient client, str { return new DiscordNetDblApi(client, dblToken); } - - public static ShardedDiscordNetDblApi CreateDblApi(this DiscordShardedClient client, string dblToken) - { - return new ShardedDiscordNetDblApi(client, dblToken); - } } public class DiscordNetDblApi : AuthDiscordBotListApi @@ -53,26 +48,4 @@ public virtual IAdapter CreateListener(TimeSpan? updateTime = null) return new SubmissionAdapter(this, client, updateTime ?? TimeSpan.Zero); } } - - public class ShardedDiscordNetDblApi : DiscordNetDblApi - { - public ShardedDiscordNetDblApi(DiscordShardedClient client, string dblToken) : base(client, dblToken) - { - } - - /// - /// Creates an IAdapter that updates your servercount on RunAsync(). - /// - /// Your already connected client - /// - /// Timespan for when you want to submit guildcount, leave null if you want it every JoinedGuild - /// event - /// - /// an IAdapter that updates your servercount on RunAsync(), does not automatically do it yet. - /// - public override IAdapter CreateListener(TimeSpan? updateTime = null) - { - return new ShardedSubmissionAdapter(this, client as DiscordShardedClient, updateTime ?? TimeSpan.Zero); - } - } } \ No newline at end of file diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs b/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs index e9951e4..106b815 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs @@ -50,27 +50,4 @@ protected void SendLog(string msg) Log?.Invoke(msg); } } - - internal class ShardedSubmissionAdapter : SubmissionAdapter, IAdapter - { - public ShardedSubmissionAdapter(AuthDiscordBotListApi api, DiscordShardedClient client, TimeSpan updateTime) - : base(api, client, updateTime) - { - } - - public override async Task RunAsync() - { - if (DateTime.Now > lastTimeUpdated + updateTime) - { - await api.UpdateStats( - 0, - (client as DiscordShardedClient).Shards.Count, - (client as DiscordShardedClient).Shards.Select(x => x.Guilds.Count).ToArray() - ); - - lastTimeUpdated = DateTime.Now; - SendLog("Sent stats to Top.gg!"); - } - } - } } \ No newline at end of file diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/Utils/DiscordNetDblUtils.cs b/DiscordBotsList.Api.Adapter.Discord.Net/Utils/DiscordNetDblUtils.cs index 2231dec..f694c98 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/Utils/DiscordNetDblUtils.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/Utils/DiscordNetDblUtils.cs @@ -14,16 +14,5 @@ public static DiscordNetDblApi CreateDblApi(this DiscordSocketClient client, str { return new DiscordNetDblApi(client, dblToken); } - - /// - /// Creates a DiscordBotsList Api - /// - /// your client - /// Your DiscordBotsList token - /// A new instance of a DblApi - public static ShardedDiscordNetDblApi CreateDblApi(this DiscordShardedClient client, string dblToken) - { - return new ShardedDiscordNetDblApi(client, dblToken); - } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/AuthenticatedBotListApi.cs b/DiscordBotsList.Api/AuthenticatedBotListApi.cs index 4d0ca53..5d87e47 100644 --- a/DiscordBotsList.Api/AuthenticatedBotListApi.cs +++ b/DiscordBotsList.Api/AuthenticatedBotListApi.cs @@ -44,7 +44,7 @@ public async Task> GetVotersAsync() } /// - /// Update your stats unsharded + /// Update your stats /// /// count of guilds public async Task UpdateStats(int guildCount) @@ -52,36 +52,6 @@ public async Task UpdateStats(int guildCount) await UpdateStatsAsync(new GuildCountObject(guildCount)); } - /// - /// Update your stats sharded - /// - /// Begin shard id - /// Total shards - /// Guild count per shards - public async Task UpdateStats(int shardId, int shardCount, params int[] shards) - { - await UpdateStatsAsync(new ShardedGuildCountObject - { - ShardId = shardId, - ShardCount = shardCount, - Shards = shards - }); - } - - /// - /// Update your stats sharded - /// - /// count of guilds - /// Total shards - public async Task UpdateStats(int guildCount, int shardCount) - { - await UpdateStatsAsync(new ShardedGuildCountObject - { - ShardCount = shardCount, - GuildCount = guildCount - }); - } - /// /// returns true if user have voted for the past 12 hours /// diff --git a/DiscordBotsList.Api/Internal/Bot.cs b/DiscordBotsList.Api/Internal/Bot.cs index 625797f..022c7c5 100644 --- a/DiscordBotsList.Api/Internal/Bot.cs +++ b/DiscordBotsList.Api/Internal/Bot.cs @@ -29,9 +29,7 @@ public class Bot : Entity [JsonPropertyName("invite")] public string customInvite { get; set; } - [JsonPropertyName("date")] public DateTime approvedAt { get; set; } - - [JsonPropertyName("certifiedBot")] public bool certified { get; set; } + [JsonPropertyName("date")] public DateTime submittedAt { get; set; } [JsonPropertyName("vanity")] public string vanity { get; set; } @@ -42,14 +40,12 @@ public class Bot : Entity [JsonPropertyName("review")] public string VanityTag => vanity; - public DateTime ApprovedAt => approvedAt; + public DateTime SubmittedAt => submittedAt; public string GithubUrl => githubUrl; public string InviteUrl => customInvite ?? $"https://discord.com/oauth2/authorize?&client_id={Id}&scope=bot"; - public bool IsCertified => certified; - public string LongDescription => longDescription; public string PrefixUsed => prefix; diff --git a/DiscordBotsList.Api/Internal/Entity.cs b/DiscordBotsList.Api/Internal/Entity.cs index 5cfe8b7..2c675df 100644 --- a/DiscordBotsList.Api/Internal/Entity.cs +++ b/DiscordBotsList.Api/Internal/Entity.cs @@ -9,7 +9,5 @@ public class Entity [JsonPropertyName("id")] public ulong Id { get; set; } [JsonPropertyName("username")] public string Username { get; set; } - - [JsonPropertyName("discriminator")] public string Discriminator { get; set; } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Internal/GuildCountObject.cs b/DiscordBotsList.Api/Internal/GuildCountObject.cs index 83055ac..db9d761 100644 --- a/DiscordBotsList.Api/Internal/GuildCountObject.cs +++ b/DiscordBotsList.Api/Internal/GuildCountObject.cs @@ -1,6 +1,4 @@ using DiscordBotsList.Api.Objects; -using System.Collections.Generic; -using System.Linq; using System.Text.Json.Serialization; namespace DiscordBotsList.Api.Internal @@ -15,31 +13,9 @@ public GuildCountObject(int count) } } - internal class BotStatsObject : ShardedObject, IDblBotStats + internal class BotStatsObject : IDblBotStats { [JsonPropertyName("server_count")] internal int guildCount { get; set; } public int GuildCount => guildCount; - - public IReadOnlyList Shards => shards.ToList(); - - public int ShardCount => shardCount; - } - - internal class ShardedObject - { - [JsonPropertyName("shards")] internal int[] shards { get; set; } - - [JsonPropertyName("shard_count")] internal int shardCount { get; set; } - } - - internal class ShardedGuildCountObject - { - [JsonPropertyName("shards")] public int[] Shards { get; set; } - - [JsonPropertyName("shard_id")] public int ShardId { get; set; } - - [JsonPropertyName("shard_count")] public int ShardCount { get; set; } - - [JsonPropertyName("server_count")] public int GuildCount { get; set; } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Internal/SelfBot.cs b/DiscordBotsList.Api/Internal/SelfBot.cs index 60bf694..85f9bfb 100644 --- a/DiscordBotsList.Api/Internal/SelfBot.cs +++ b/DiscordBotsList.Api/Internal/SelfBot.cs @@ -25,15 +25,5 @@ public async Task UpdateStatsAsync(int guildCount) { await ((AuthDiscordBotListApi)api).UpdateStats(guildCount); } - - public async Task UpdateStatsAsync(int[] shards) - { - await ((AuthDiscordBotListApi)api).UpdateStats(0, shards.Length, shards); - } - - public async Task UpdateStatsAsync(int shardCount, int totalShards, params int[] shards) - { - await ((AuthDiscordBotListApi)api).UpdateStats(shardCount, totalShards, shards); - } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Internal/User.cs b/DiscordBotsList.Api/Internal/User.cs index e8365c3..e15c571 100644 --- a/DiscordBotsList.Api/Internal/User.cs +++ b/DiscordBotsList.Api/Internal/User.cs @@ -9,14 +9,10 @@ public class User : Entity [JsonPropertyName("bio")] public string Biography { get; set; } - [JsonPropertyName("banner")] public string BannerUrl { get; set; } - [JsonPropertyName("color")] public string Color { get; set; } [JsonPropertyName("supporter")] public bool IsSupporter { get; set; } - [JsonPropertyName("certifiedDev")] public bool IsCertified { get; set; } - [JsonPropertyName("mod")] public bool IsModerator { get; set; } [JsonPropertyName("webMod")] public bool IsWebModerator { get; set; } diff --git a/DiscordBotsList.Api/Objects/IBotStats.cs b/DiscordBotsList.Api/Objects/IBotStats.cs index 116efb0..46dfb90 100644 --- a/DiscordBotsList.Api/Objects/IBotStats.cs +++ b/DiscordBotsList.Api/Objects/IBotStats.cs @@ -5,9 +5,5 @@ namespace DiscordBotsList.Api.Objects public interface IDblBotStats { int GuildCount { get; } - - IReadOnlyList Shards { get; } - - int ShardCount { get; } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/IDblBot.cs b/DiscordBotsList.Api/Objects/IDblBot.cs index 23098d6..07c78e5 100644 --- a/DiscordBotsList.Api/Objects/IDblBot.cs +++ b/DiscordBotsList.Api/Objects/IDblBot.cs @@ -24,9 +24,7 @@ public interface IDblBot : IDblEntity string InviteUrl { get; } - DateTime ApprovedAt { get; } - - bool IsCertified { get; } + DateTime SubmittedAt { get; } string VanityUrl { get; } @@ -46,9 +44,5 @@ public interface IDblSelfBot : IDblBot Task IsWeekendAsync(); Task UpdateStatsAsync(int guildCount); - - Task UpdateStatsAsync(int[] shards); - - Task UpdateStatsAsync(int shardCount, int totalShards, params int[] shards); } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/IDblEntity.cs b/DiscordBotsList.Api/Objects/IDblEntity.cs index 5a63b63..d0ef8ac 100644 --- a/DiscordBotsList.Api/Objects/IDblEntity.cs +++ b/DiscordBotsList.Api/Objects/IDblEntity.cs @@ -12,11 +12,6 @@ public interface IDblEntity /// string Username { get; } - /// - /// Discriminator, the XXXX#1234 part - /// - string Discriminator { get; } - /// /// Discord avatar url, or default avatar if none found. /// diff --git a/DiscordBotsList.Api/Objects/IDblUser.cs b/DiscordBotsList.Api/Objects/IDblUser.cs index 8d8073a..547590e 100644 --- a/DiscordBotsList.Api/Objects/IDblUser.cs +++ b/DiscordBotsList.Api/Objects/IDblUser.cs @@ -4,16 +4,12 @@ public interface IDblUser : IDblEntity { string Biography { get; } - string BannerUrl { get; } - SocialConnections Connections { get; } string Color { get; } bool IsSupporter { get; } - bool IsCertified { get; } - bool IsModerator { get; } bool IsWebModerator { get; } diff --git a/README.md b/README.md index 9c3d32e..c6879b2 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,6 @@ AuthDiscordBotListApi DblApi = new AuthDiscordBotListApi(BOT_DISCORD_ID, YOUR_TO #### Updating stats ```cs IDblSelfBot me = await DblApi.GetMeAsync(); -// Update stats sharded indexShard shardCount shards -await me.UpdateStatsAsync(24, 50, new[] { 12, 421, 62, 241, 524, 534 }); // Update stats guildCount await me.UpdateStatsAsync(2133); From 3cb88219c91cfa31161b6dd5cda35df7a010011b Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 21 Apr 2025 18:12:24 +0700 Subject: [PATCH 03/21] feat: remove fetching users --- .../DiscordNetDiscordBotsListApi.cs | 5 ---- DiscordBotsList.Api.Tests/UnitTests.cs | 9 +------ .../AuthenticatedBotListApi.cs | 7 +++--- DiscordBotsList.Api/Internal/User.cs | 24 ------------------- DiscordBotsList.Api/Objects/IDblUser.cs | 19 --------------- DiscordBotsList.Api/Objects/WeekendObject.cs | 8 ++++--- README.md | 6 ----- 7 files changed, 9 insertions(+), 69 deletions(-) delete mode 100644 DiscordBotsList.Api/Internal/User.cs delete mode 100644 DiscordBotsList.Api/Objects/IDblUser.cs diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs index 872fc0c..5ae7fa9 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs @@ -28,11 +28,6 @@ public async Task GetBotAsync(IUser user) return await GetBotAsync(user.Id); } - public async Task GetUserAsync(IUser user) - { - return await GetUserAsync(user.Id); - } - /// /// Creates an IAdapter that updates your servercount on RunAsync(). /// diff --git a/DiscordBotsList.Api.Tests/UnitTests.cs b/DiscordBotsList.Api.Tests/UnitTests.cs index 364a0b3..d94fdda 100644 --- a/DiscordBotsList.Api.Tests/UnitTests.cs +++ b/DiscordBotsList.Api.Tests/UnitTests.cs @@ -34,7 +34,6 @@ public UnitTests() public void GetUserTest() { Assert.NotNull(_api.GetMeAsync()); - Assert.NotNull(_api.GetUserAsync(_cred.BotId)); } [Fact] @@ -54,13 +53,7 @@ public async Task TaskGetVotersTestAsync() { Assert.NotNull(await _api.GetVotersAsync()); } - - [Fact] - public async Task GetUserTestAsync() - { - Assert.NotNull(await _api.GetUserAsync(181514288278536193)); - } - + [Fact] public async Task GetBotTestAsync() { diff --git a/DiscordBotsList.Api/AuthenticatedBotListApi.cs b/DiscordBotsList.Api/AuthenticatedBotListApi.cs index 5d87e47..7ee36af 100644 --- a/DiscordBotsList.Api/AuthenticatedBotListApi.cs +++ b/DiscordBotsList.Api/AuthenticatedBotListApi.cs @@ -34,7 +34,7 @@ public async Task GetMeAsync() } /// - /// Gets all voters that have voted on your bot + /// Gets unique voters that have voted on your bot /// Max 1000, If you have more, you MUST use WEBHOOKS instead. /// /// A list of voters @@ -64,8 +64,7 @@ public async Task HasVoted(ulong userId) protected async Task> GetVotersAsync() { - var query = $"bots/{_selfId}/votes"; - return await GetAuthorizedAsync>(Utils.CreateQuery(query)); + return await GetAuthorizedAsync>(Utils.CreateQuery("bots/votes")); } protected async Task UpdateStatsAsync(object statsObject) @@ -73,7 +72,7 @@ protected async Task UpdateStatsAsync(object statsObject) var json = JsonSerializer.Serialize(statsObject); var httpContent = new StringContent(json, Encoding.UTF8, "application/json"); await _httpClient - .PostAsync($"{baseEndpoint}/bots/{_selfId}/stats", httpContent); + .PostAsync($"{baseEndpoint}/bots/stats", httpContent); } protected async Task GetAuthorizedAsync(string url) diff --git a/DiscordBotsList.Api/Internal/User.cs b/DiscordBotsList.Api/Internal/User.cs deleted file mode 100644 index e15c571..0000000 --- a/DiscordBotsList.Api/Internal/User.cs +++ /dev/null @@ -1,24 +0,0 @@ -using DiscordBotsList.Api.Objects; -using System.Text.Json.Serialization; - -namespace DiscordBotsList.Api.Internal -{ - public class User : Entity - { - [JsonPropertyName("social")] public SocialConnections Social { get; set; } - - [JsonPropertyName("bio")] public string Biography { get; set; } - - [JsonPropertyName("color")] public string Color { get; set; } - - [JsonPropertyName("supporter")] public bool IsSupporter { get; set; } - - [JsonPropertyName("mod")] public bool IsModerator { get; set; } - - [JsonPropertyName("webMod")] public bool IsWebModerator { get; set; } - - [JsonPropertyName("admin")] public bool IsAdmin { get; set; } - - public SocialConnections Connections => Social; - } -} \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/IDblUser.cs b/DiscordBotsList.Api/Objects/IDblUser.cs deleted file mode 100644 index 547590e..0000000 --- a/DiscordBotsList.Api/Objects/IDblUser.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace DiscordBotsList.Api.Objects -{ - public interface IDblUser : IDblEntity - { - string Biography { get; } - - SocialConnections Connections { get; } - - string Color { get; } - - bool IsSupporter { get; } - - bool IsModerator { get; } - - bool IsWebModerator { get; } - - bool IsAdmin { get; } - } -} \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/WeekendObject.cs b/DiscordBotsList.Api/Objects/WeekendObject.cs index bfcabf0..c16d0d5 100644 --- a/DiscordBotsList.Api/Objects/WeekendObject.cs +++ b/DiscordBotsList.Api/Objects/WeekendObject.cs @@ -1,7 +1,9 @@ -namespace DiscordBotsList.Api.Objects +using System.Text.Json.Serialization; + +namespace DiscordBotsList.Api.Objects { - public struct WeekendObject + public class WeekendObject { - public bool Weekend; + [JsonPropertyName("is_weekend")] public bool Weekend { get; set; } } } \ No newline at end of file diff --git a/README.md b/README.md index c6879b2..3cdebcb 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,6 @@ DiscordBotListApi DblApi = new DiscordBotListApi(); IBot bot = DblApi.GetBotAsync(160105994217586689); ``` -#### Getting users -```cs -// discord id -IUser bot = DblApi.GetUserAsync(121919449996460033); -``` - ### Authorized api usage #### Setting up ```cs From 2a75daf5fc486780274d6c7c7907d667d089f87b Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 21 Apr 2025 18:17:15 +0700 Subject: [PATCH 04/21] feat: also delete SocialConnections class --- .../Objects/SocialConnections.cs | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 DiscordBotsList.Api/Objects/SocialConnections.cs diff --git a/DiscordBotsList.Api/Objects/SocialConnections.cs b/DiscordBotsList.Api/Objects/SocialConnections.cs deleted file mode 100644 index ab8a62b..0000000 --- a/DiscordBotsList.Api/Objects/SocialConnections.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Text.Json.Serialization; - -namespace DiscordBotsList.Api.Objects -{ - public class SocialConnections - { - [JsonPropertyName("youtube")] public string YouTubeChannelId { get; internal set; } - - [JsonPropertyName("reddit")] public string RedditName { get; internal set; } - - [JsonPropertyName("twitter")] public string TwitterName { get; internal set; } - - [JsonPropertyName("instagram")] public string InstagramName { get; internal set; } - - [JsonPropertyName("github")] public string GitHubName { get; internal set; } - } -} \ No newline at end of file From e7ccdfe861ec11cafec32a08c005db8a5d3962d7 Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 21 Apr 2025 19:57:38 +0700 Subject: [PATCH 05/21] feat: update url formattings and add clientid --- .../DiscordNetDiscordBotsListApi.cs | 6 ------ DiscordBotsList.Api/DiscordBotListApi.cs | 10 ---------- DiscordBotsList.Api/Internal/Bot.cs | 12 ++++++++---- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs index 5ae7fa9..61595cf 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs @@ -2,7 +2,6 @@ using Discord.WebSocket; using DiscordBotsList.Api.Objects; using System; -using System.Threading.Tasks; namespace DiscordBotsList.Api.Adapter.Discord.Net { @@ -23,11 +22,6 @@ public DiscordNetDblApi(IDiscordClient client, string dblToken) : base(client.Cu this.client = client; } - public async Task GetBotAsync(IUser user) - { - return await GetBotAsync(user.Id); - } - /// /// Creates an IAdapter that updates your servercount on RunAsync(). /// diff --git a/DiscordBotsList.Api/DiscordBotListApi.cs b/DiscordBotsList.Api/DiscordBotListApi.cs index 64f27ed..3027363 100644 --- a/DiscordBotsList.Api/DiscordBotListApi.cs +++ b/DiscordBotsList.Api/DiscordBotListApi.cs @@ -54,16 +54,6 @@ public async Task GetBotStatsAsync(ulong id) return await GetAsync($"bots/{id}/stats"); } - /// - /// Get specific user by Discord id - /// - /// Discord id - /// User Object - public async Task GetUserAsync(ulong id) - { - return (IDblUser)await GetAsync($"users/{id}"); - } - /// /// Template /// of GetBotAsync for internal usage. diff --git a/DiscordBotsList.Api/Internal/Bot.cs b/DiscordBotsList.Api/Internal/Bot.cs index 022c7c5..a71472b 100644 --- a/DiscordBotsList.Api/Internal/Bot.cs +++ b/DiscordBotsList.Api/Internal/Bot.cs @@ -11,6 +11,8 @@ public class Bot : Entity { internal DiscordBotListApi api; + [JsonPropertyName("clientid")] public ulong clientId { get; set; } + [JsonPropertyName("prefix")] public string prefix { get; set; } [JsonPropertyName("shortdesc")] public string shortDescription { get; set; } @@ -21,13 +23,13 @@ public class Bot : Entity [JsonPropertyName("website")] public string websiteUrl { get; set; } - [JsonPropertyName("support")] public string SupportInviteCode { get; set; } + [JsonPropertyName("support")] public string supportUrl { get; set; } [JsonPropertyName("github")] public string githubUrl { get; set; } [JsonPropertyName("owners")] public List owners { get; set; } - [JsonPropertyName("invite")] public string customInvite { get; set; } + [JsonPropertyName("invite")] public string inviteUrl { get; set; } [JsonPropertyName("date")] public DateTime submittedAt { get; set; } @@ -40,11 +42,13 @@ public class Bot : Entity [JsonPropertyName("review")] public string VanityTag => vanity; + public ulong ClientId => clientId; + public DateTime SubmittedAt => submittedAt; public string GithubUrl => githubUrl; - public string InviteUrl => customInvite ?? $"https://discord.com/oauth2/authorize?&client_id={Id}&scope=bot"; + public string InviteUrl => inviteUrl ?? $"https://discord.com/oauth2/authorize?&client_id={Id}&scope=bot"; public string LongDescription => longDescription; @@ -60,7 +64,7 @@ public class Bot : Entity public List Tags => tags.ToList(); - public string SupportUrl => "https://discord.gg/" + SupportInviteCode; + public string SupportUrl => supportUrl; public string VanityUrl => "https://top.gg/bot/" + vanity; From cd7b8a190746b124be3bd6d85022f8b7cdcbfb16 Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 21 Apr 2025 20:05:00 +0700 Subject: [PATCH 06/21] feat: change GET /bots/stats --- DiscordBotsList.Api/DiscordBotListApi.cs | 5 ++--- DiscordBotsList.Api/Internal/Bot.cs | 5 ----- DiscordBotsList.Api/Objects/IDblBot.cs | 2 -- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/DiscordBotsList.Api/DiscordBotListApi.cs b/DiscordBotsList.Api/DiscordBotListApi.cs index 3027363..cc9c600 100644 --- a/DiscordBotsList.Api/DiscordBotListApi.cs +++ b/DiscordBotsList.Api/DiscordBotListApi.cs @@ -47,11 +47,10 @@ public async Task GetBotAsync(ulong id) /// /// Get bot stats /// - /// Discord id /// IBotStats object related to the bot - public async Task GetBotStatsAsync(ulong id) + public async Task GetStatsAsync() { - return await GetAsync($"bots/{id}/stats"); + return await GetAsync("bots/stats"); } /// diff --git a/DiscordBotsList.Api/Internal/Bot.cs b/DiscordBotsList.Api/Internal/Bot.cs index a71472b..bf1375b 100644 --- a/DiscordBotsList.Api/Internal/Bot.cs +++ b/DiscordBotsList.Api/Internal/Bot.cs @@ -69,10 +69,5 @@ public class Bot : Entity public string VanityUrl => "https://top.gg/bot/" + vanity; public string WebsiteUrl => websiteUrl; - - public async Task GetStatsAsync() - { - return await api.GetBotStatsAsync(Id); - } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/IDblBot.cs b/DiscordBotsList.Api/Objects/IDblBot.cs index 07c78e5..b27fe5f 100644 --- a/DiscordBotsList.Api/Objects/IDblBot.cs +++ b/DiscordBotsList.Api/Objects/IDblBot.cs @@ -31,8 +31,6 @@ public interface IDblBot : IDblEntity int Points { get; } int MonthlyPoints { get; } - - Task GetStatsAsync(); } public interface IDblSelfBot : IDblBot From 5314521a860f60781d528413c60d633b7a6848d0 Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 21 Apr 2025 23:36:25 +0700 Subject: [PATCH 07/21] feat: add guild count [skip ci] --- .../AuthenticatedBotListApi.cs | 22 ++++++++++++++----- DiscordBotsList.Api/Internal/Bot.cs | 9 ++++---- DiscordBotsList.Api/Internal/SelfBot.cs | 4 ++-- DiscordBotsList.Api/Objects/IDblBot.cs | 2 +- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/DiscordBotsList.Api/AuthenticatedBotListApi.cs b/DiscordBotsList.Api/AuthenticatedBotListApi.cs index 7ee36af..17fb282 100644 --- a/DiscordBotsList.Api/AuthenticatedBotListApi.cs +++ b/DiscordBotsList.Api/AuthenticatedBotListApi.cs @@ -1,5 +1,6 @@ using DiscordBotsList.Api.Internal; using DiscordBotsList.Api.Objects; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -37,10 +38,11 @@ public async Task GetMeAsync() /// Gets unique voters that have voted on your bot /// Max 1000, If you have more, you MUST use WEBHOOKS instead. /// + /// The page number, defaults to 1 /// A list of voters - public async Task> GetVotersAsync() + public async Task> GetVotersAsync(int page = 1) { - return (await GetVotersAsync()).Cast().ToList(); + return (await GetVotersAsync(page)).Cast().ToList(); } /// @@ -49,6 +51,11 @@ public async Task> GetVotersAsync() /// count of guilds public async Task UpdateStats(int guildCount) { + if (guildCount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(guildCount), "guildCount cannot be less than 1."); + } + await UpdateStatsAsync(new GuildCountObject(guildCount)); } @@ -62,9 +69,14 @@ public async Task HasVoted(ulong userId) return await HasVotedAsync(userId); } - protected async Task> GetVotersAsync() + protected async Task> GetVotersAsync(int page) { - return await GetAuthorizedAsync>(Utils.CreateQuery("bots/votes")); + if (page < 1) + { + page = 1; + } + + return await GetAuthorizedAsync>(Utils.CreateQuery($"bots/votes?page={page}")); } protected async Task UpdateStatsAsync(object statsObject) @@ -82,7 +94,7 @@ protected async Task GetAuthorizedAsync(string url) protected async Task HasVotedAsync(ulong userId) { - var url = $"bots/{_selfId}/check?userId={userId}"; + var url = $"bots/check?userId={userId}"; return (await GetAsync(url)).HasVoted.GetValueOrDefault(0) == 1; } } diff --git a/DiscordBotsList.Api/Internal/Bot.cs b/DiscordBotsList.Api/Internal/Bot.cs index bf1375b..c36b07e 100644 --- a/DiscordBotsList.Api/Internal/Bot.cs +++ b/DiscordBotsList.Api/Internal/Bot.cs @@ -1,9 +1,7 @@ -using DiscordBotsList.Api.Objects; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; -using System.Threading.Tasks; namespace DiscordBotsList.Api.Internal { @@ -33,19 +31,22 @@ public class Bot : Entity [JsonPropertyName("date")] public DateTime submittedAt { get; set; } + [JsonPropertyName("server_count")] public int? guildCount { get; set; } + [JsonPropertyName("vanity")] public string vanity { get; set; } [JsonPropertyName("points")] public int points { get; set; } [JsonPropertyName("monthlyPoints")] public int monthlyPoints { get; set; } - [JsonPropertyName("review")] public string VanityTag => vanity; public ulong ClientId => clientId; public DateTime SubmittedAt => submittedAt; + public int? GuildCount => guildCount; + public string GithubUrl => githubUrl; public string InviteUrl => inviteUrl ?? $"https://discord.com/oauth2/authorize?&client_id={Id}&scope=bot"; diff --git a/DiscordBotsList.Api/Internal/SelfBot.cs b/DiscordBotsList.Api/Internal/SelfBot.cs index 85f9bfb..8e7d5a9 100644 --- a/DiscordBotsList.Api/Internal/SelfBot.cs +++ b/DiscordBotsList.Api/Internal/SelfBot.cs @@ -6,9 +6,9 @@ namespace DiscordBotsList.Api.Internal { internal class SelfBot : Bot { - public async Task> GetVotersAsync() + public async Task> GetVotersAsync(int page = 1) { - return await ((AuthDiscordBotListApi)api).GetVotersAsync(); + return await ((AuthDiscordBotListApi)api).GetVotersAsync(page); } public async Task HasVotedAsync(ulong userId) diff --git a/DiscordBotsList.Api/Objects/IDblBot.cs b/DiscordBotsList.Api/Objects/IDblBot.cs index b27fe5f..29e2464 100644 --- a/DiscordBotsList.Api/Objects/IDblBot.cs +++ b/DiscordBotsList.Api/Objects/IDblBot.cs @@ -35,7 +35,7 @@ public interface IDblBot : IDblEntity public interface IDblSelfBot : IDblBot { - Task> GetVotersAsync(); + Task> GetVotersAsync(int page); Task HasVotedAsync(ulong userId); From 87de67775fdfff210654fe1055f9eb46600e83b9 Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 16 Jun 2025 15:13:06 +0700 Subject: [PATCH 08/21] feat: add Reviews and implement SubmissionAdapter --- .../Adapter.cs | 68 +++++++++++++++++++ ...ordBotsList.Api.Adapter.Discord.Net.csproj | 4 +- .../DiscordNetDiscordBotsListApi.cs | 10 ++- .../SubmissionAdapter.cs | 44 +++--------- DiscordBotsList.Api.Tests/UnitTests.cs | 35 ++++++---- .../AuthenticatedBotListApi.cs | 13 +--- DiscordBotsList.Api/DiscordBotListApi.cs | 31 ++++++--- .../DiscordBotsList.Api.csproj | 4 +- DiscordBotsList.Api/Internal/Bot.cs | 7 +- DiscordBotsList.Api/Internal/Entity.cs | 5 +- .../Internal/Queries/BotListQuery.cs | 6 +- DiscordBotsList.Api/Internal/SelfBot.cs | 4 +- DiscordBotsList.Api/Objects/BotReviews.cs | 11 +++ DiscordBotsList.Api/Objects/IDblBot.cs | 2 + 14 files changed, 157 insertions(+), 87 deletions(-) create mode 100644 DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs create mode 100644 DiscordBotsList.Api/Objects/BotReviews.cs diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs b/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs new file mode 100644 index 0000000..84198af --- /dev/null +++ b/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs @@ -0,0 +1,68 @@ +using DiscordBotsList.Api.Objects; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace DiscordBotsList.Api.Adapter.Discord.Net +{ + public class Adapter : IAdapter + { + public event Action Log; + + private readonly TimeSpan updateTime; + + private CancellationTokenSource cancellationTokenSource; + + public Adapter(TimeSpan updateTime) + { + if (updateTime < TimeSpan.FromMinutes(15)) + { + throw new ArgumentException("updateTime must be at least 15 minutes.", nameof(updateTime)); + } + + this.updateTime = updateTime; + cancellationTokenSource = null; + } + + public virtual async Task RunAsync() + { + throw new NotImplementedException(); + } + + public void Start() + { + if (cancellationTokenSource != null) + { + return; + } + + cancellationTokenSource = new CancellationTokenSource(); + + Task.Run(async () => + { + while (!cancellationTokenSource.Token.IsCancellationRequested) + { + await RunAsync(); + + SendLog("Submitted stats to Top.gg!"); + + await Task.Delay(updateTime, cancellationTokenSource.Token); + } + }, cancellationTokenSource.Token); + } + + public void Stop() + { + if (cancellationTokenSource != null) + { + cancellationTokenSource.Cancel(); + cancellationTokenSource = null; + } + } + + private void SendLog(string msg) + { + Log?.Invoke(msg); + } + } +} \ No newline at end of file diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj index fa7dbe9..644d4fb 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj +++ b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj @@ -11,8 +11,8 @@ Mike Veldsink discord bots list org wrapper api discord.net git - https://github.com/DiscordBotList/DBL-dotnet-Library - https://github.com/DiscordBotList/DBL-dotnet-Library + https://github.com/Top-gg-Community/dotnet-sdk + https://github.com/Top-gg-Community/dotnet-sdk 1.5.0 LICENSE diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs index 61595cf..2755269 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs @@ -23,16 +23,14 @@ public DiscordNetDblApi(IDiscordClient client, string dblToken) : base(client.Cu } /// - /// Creates an IAdapter that updates your servercount on RunAsync(). + /// Creates a SubmissionAdapter that updates your servercount on RunAsync(). /// - /// Your already connected client /// - /// Timespan for when you want to submit guildcount, leave null if you want it every JoinedGuild - /// event + /// Timespan for when you want to submit guildcount, must be at least 15 minutes /// - /// an IAdapter that updates your servercount on RunAsync(), does not automatically do it yet. + /// A SubmissionAdapter that updates your servercount on RunAsync(). /// - public virtual IAdapter CreateListener(TimeSpan? updateTime = null) + public SubmissionAdapter CreateListener(TimeSpan? updateTime = null) { return new SubmissionAdapter(this, client, updateTime ?? TimeSpan.Zero); } diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs b/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs index 106b815..6dc5b62 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs @@ -1,53 +1,25 @@ using Discord; -using Discord.WebSocket; using DiscordBotsList.Api.Objects; using System; -using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace DiscordBotsList.Api.Adapter.Discord.Net { - internal class SubmissionAdapter : IAdapter + public class SubmissionAdapter : Adapter { - protected AuthDiscordBotListApi api; - protected IDiscordClient client; + private readonly AuthDiscordBotListApi api; + private readonly IDiscordClient client; - protected DateTime lastTimeUpdated; - protected TimeSpan updateTime; - - public SubmissionAdapter(AuthDiscordBotListApi api, IDiscordClient client, TimeSpan updateTime) + public SubmissionAdapter(AuthDiscordBotListApi api, IDiscordClient client, TimeSpan updateTime) : base(updateTime) { + this.api = api; this.client = client; - this.updateTime = updateTime; - } - - public event Action Log; - - public virtual async Task RunAsync() - { - if (DateTime.Now > lastTimeUpdated + updateTime) - { - await api.UpdateStats( - (await client.GetGuildsAsync()).Count - ); - - lastTimeUpdated = DateTime.Now; - SendLog("Submitted stats to Top.gg!"); - } - } - - public virtual void Start() - { - } - - public virtual void Stop() - { - throw new NotImplementedException(); } - protected void SendLog(string msg) + public override async Task RunAsync() { - Log?.Invoke(msg); + await api.UpdateStatsAsync((await client.GetGuildsAsync()).Count); } } } \ No newline at end of file diff --git a/DiscordBotsList.Api.Tests/UnitTests.cs b/DiscordBotsList.Api.Tests/UnitTests.cs index d94fdda..c1626a9 100644 --- a/DiscordBotsList.Api.Tests/UnitTests.cs +++ b/DiscordBotsList.Api.Tests/UnitTests.cs @@ -12,10 +12,11 @@ public class Credentials public static Credentials LoadFromEnv() { - var cred = new Credentials(); - cred.BotId = ulong.Parse(Environment.GetEnvironmentVariable("BOT_ID")); - cred.Token = Environment.GetEnvironmentVariable("API_KEY"); - return cred; + return new Credentials() + { + BotId = ulong.Parse(Environment.GetEnvironmentVariable("BOT_ID")), + Token = Environment.GetEnvironmentVariable("API_KEY"), + }; } } @@ -30,12 +31,6 @@ public UnitTests() _api = new AuthDiscordBotListApi(_cred.BotId, _cred.Token); } - [Fact] - public void GetUserTest() - { - Assert.NotNull(_api.GetMeAsync()); - } - [Fact] public async Task HasVotedTestAsync() { @@ -53,11 +48,11 @@ public async Task TaskGetVotersTestAsync() { Assert.NotNull(await _api.GetVotersAsync()); } - + [Fact] public async Task GetBotTestAsync() { - var botId = 423593006436712458U; + var botId = 264811613708746752U; var bot = await _api.GetBotAsync(botId); Assert.NotNull(bot); Assert.Equal(botId, bot.Id); @@ -70,7 +65,7 @@ public async Task GetMeTestAsync() } [Fact] - public async Task GetUsersGetStatsTest() + public async Task GetBotsTestAsync() { var bots = await _api.GetBotsAsync(); @@ -79,9 +74,19 @@ public async Task GetUsersGetStatsTest() var firstBot = bots.Items.First(); - var stats = await firstBot.GetStatsAsync(); + Assert.NotNull(firstBot); + } - Assert.NotNull(stats); + [Fact] + public async Task GetStatsTestAsync() + { + Assert.NotNull(await _api.GetStatsAsync()); + } + + [Fact] + public async Task UpdateStatsTestAsync() + { + await _api.UpdateStatsAsync(2); } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/AuthenticatedBotListApi.cs b/DiscordBotsList.Api/AuthenticatedBotListApi.cs index 17fb282..155e5c0 100644 --- a/DiscordBotsList.Api/AuthenticatedBotListApi.cs +++ b/DiscordBotsList.Api/AuthenticatedBotListApi.cs @@ -14,12 +14,10 @@ namespace DiscordBotsList.Api public class AuthDiscordBotListApi : DiscordBotListApi { private readonly ulong _selfId; - private readonly string _token; public AuthDiscordBotListApi(ulong selfId, string token) { _selfId = selfId; - _token = token; _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); } @@ -31,7 +29,7 @@ public async Task GetMeAsync() { var bot = await GetBotAsync(_selfId); bot.api = this; - return (IDblSelfBot)bot; + return bot; } /// @@ -49,7 +47,7 @@ public async Task> GetVotersAsync(int page = 1) /// Update your stats /// /// count of guilds - public async Task UpdateStats(int guildCount) + public async Task UpdateStatsAsync(int guildCount) { if (guildCount <= 0) { @@ -71,12 +69,7 @@ public async Task HasVoted(ulong userId) protected async Task> GetVotersAsync(int page) { - if (page < 1) - { - page = 1; - } - - return await GetAuthorizedAsync>(Utils.CreateQuery($"bots/votes?page={page}")); + return await GetAuthorizedAsync>(Utils.CreateQuery($"bots/{_selfId}/votes?page={Math.Max(page, 1)}")); } protected async Task UpdateStatsAsync(object statsObject) diff --git a/DiscordBotsList.Api/DiscordBotListApi.cs b/DiscordBotsList.Api/DiscordBotListApi.cs index cc9c600..a106c52 100644 --- a/DiscordBotsList.Api/DiscordBotListApi.cs +++ b/DiscordBotsList.Api/DiscordBotListApi.cs @@ -1,6 +1,7 @@ using DiscordBotsList.Api.Internal; using DiscordBotsList.Api.Internal.Queries; using DiscordBotsList.Api.Objects; +using System; using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; @@ -24,12 +25,28 @@ public DiscordBotListApi() /// /// Gets bots from botlist /// - /// amount of bots to appear per page (max: 500) - /// current page to query + /// sorts results based on their monthly vote count ("montlyPoints"), id ("id"), or their submission date ("date") + /// amount of bots to retrieve (max: 500) + /// amount of bots to skip /// List of Bot Objects - public async Task> GetBotsAsync(int count = 50, int page = 0) + public async Task> GetBotsAsync(string sort = "monthlyPoints", int count = 50, int offset = 0) { - var result = await GetAsync("bots"); + if (sort != "monthlyPoints" && sort != "id" && sort != "date") + { + throw new ArgumentException("sort must be 'monthlyPoints', 'id', or 'date'."); + } + + if (count < 0 || count > 500) + { + throw new ArgumentOutOfRangeException(nameof(count), "count mustn't be negative or exceed 500."); + } + + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset), "offset must not be negative."); + } + + var result = await GetAsync($"bots?sort={sort}&limit=${count}&offset=${offset}"); foreach (var bot in result.Items) (bot as Bot).api = this; return result; } @@ -77,11 +94,9 @@ internal async Task GetBotAsync(ulong id) where T : Bot protected async Task GetAsync(string url) { var t = await _httpClient.GetAsync(baseEndpoint + url); - var payload = await t.Content.ReadAsStringAsync(); - var o = JsonSerializer.Deserialize(payload, _serializerOptions); var result = t.IsSuccessStatusCode - ? ApiResult.FromSuccess(await t.Content.ReadFromJsonAsync(_serializerOptions)) - : ApiResult.FromHttpError(t.StatusCode); + ? ApiResult.FromSuccess(await t.Content.ReadFromJsonAsync(_serializerOptions)) + : ApiResult.FromHttpError(t.StatusCode); return result.Value; } diff --git a/DiscordBotsList.Api/DiscordBotsList.Api.csproj b/DiscordBotsList.Api/DiscordBotsList.Api.csproj index 2b06805..ef98f98 100644 --- a/DiscordBotsList.Api/DiscordBotsList.Api.csproj +++ b/DiscordBotsList.Api/DiscordBotsList.Api.csproj @@ -6,8 +6,8 @@ Top.gg top.gg api wrapper Mike Veldsink - https://github.com/DiscordBotList/DBL-dotnet-Library - https://github.com/DiscordBotList/DBL-dotnet-Library + https://github.com/Top-gg-Community/dotnet-sdk + https://github.com/Top-gg-Community/dotnet-sdk git discord bots list org wrapper api update to net 8 diff --git a/DiscordBotsList.Api/Internal/Bot.cs b/DiscordBotsList.Api/Internal/Bot.cs index c36b07e..25aa4c6 100644 --- a/DiscordBotsList.Api/Internal/Bot.cs +++ b/DiscordBotsList.Api/Internal/Bot.cs @@ -2,10 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Text.Json.Serialization; +using DiscordBotsList.Api.Objects; namespace DiscordBotsList.Api.Internal { - public class Bot : Entity + public class Bot : Entity, IDblBot { internal DiscordBotListApi api; @@ -39,6 +40,8 @@ public class Bot : Entity [JsonPropertyName("monthlyPoints")] public int monthlyPoints { get; set; } + [JsonPropertyName("reviews")] public BotReviews reviews { get; set; } + public string VanityTag => vanity; public ulong ClientId => clientId; @@ -70,5 +73,7 @@ public class Bot : Entity public string VanityUrl => "https://top.gg/bot/" + vanity; public string WebsiteUrl => websiteUrl; + + public BotReviews Reviews => reviews; } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Internal/Entity.cs b/DiscordBotsList.Api/Internal/Entity.cs index 2c675df..d776574 100644 --- a/DiscordBotsList.Api/Internal/Entity.cs +++ b/DiscordBotsList.Api/Internal/Entity.cs @@ -1,10 +1,11 @@ using System.Text.Json.Serialization; +using DiscordBotsList.Api.Objects; namespace DiscordBotsList.Api.Internal { - public class Entity + public class Entity: IDblEntity { - [JsonPropertyName("avatar")] public string Avatar { get; set; } + [JsonPropertyName("avatar")] public string AvatarUrl { get; set; } [JsonPropertyName("id")] public ulong Id { get; set; } diff --git a/DiscordBotsList.Api/Internal/Queries/BotListQuery.cs b/DiscordBotsList.Api/Internal/Queries/BotListQuery.cs index 17c43ae..6aeaca9 100644 --- a/DiscordBotsList.Api/Internal/Queries/BotListQuery.cs +++ b/DiscordBotsList.Api/Internal/Queries/BotListQuery.cs @@ -12,7 +12,7 @@ internal class BotListQuery : ISearchResult [JsonPropertyName("limit")] public int limit { get; set; } - [JsonPropertyName("offset")] public int offset { get; set; } + [JsonPropertyName("offset")] public int? offset { get; set; } [JsonPropertyName("count")] public int count { get; set; } @@ -22,12 +22,12 @@ internal class BotListQuery : ISearchResult .Cast() .ToList(); - public int CurrentPage => (int)Math.Ceiling((double)offset / limit); + public int CurrentPage => (int)Math.Ceiling((double)(offset ?? 0) / limit); public int ItemsPerPage => limit; public int TotalItems => total; - public int TotalPages => (int)Math.Ceiling((double)limit / count); + public int TotalPages => (int)Math.Ceiling((double)(offset ?? 0) / count); } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Internal/SelfBot.cs b/DiscordBotsList.Api/Internal/SelfBot.cs index 8e7d5a9..6451f32 100644 --- a/DiscordBotsList.Api/Internal/SelfBot.cs +++ b/DiscordBotsList.Api/Internal/SelfBot.cs @@ -4,7 +4,7 @@ namespace DiscordBotsList.Api.Internal { - internal class SelfBot : Bot + internal class SelfBot : Bot, IDblSelfBot { public async Task> GetVotersAsync(int page = 1) { @@ -23,7 +23,7 @@ public async Task IsWeekendAsync() public async Task UpdateStatsAsync(int guildCount) { - await ((AuthDiscordBotListApi)api).UpdateStats(guildCount); + await ((AuthDiscordBotListApi)api).UpdateStatsAsync(guildCount); } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/BotReviews.cs b/DiscordBotsList.Api/Objects/BotReviews.cs new file mode 100644 index 0000000..9539530 --- /dev/null +++ b/DiscordBotsList.Api/Objects/BotReviews.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace DiscordBotsList.Api.Objects +{ + public class BotReviews + { + [JsonPropertyName("averageScore")] public double AverageScore { get; internal set; } + + [JsonPropertyName("count")] public int Count { get; internal set; } + } +} \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/IDblBot.cs b/DiscordBotsList.Api/Objects/IDblBot.cs index 29e2464..dc0af32 100644 --- a/DiscordBotsList.Api/Objects/IDblBot.cs +++ b/DiscordBotsList.Api/Objects/IDblBot.cs @@ -31,6 +31,8 @@ public interface IDblBot : IDblEntity int Points { get; } int MonthlyPoints { get; } + + BotReviews Reviews { get; } } public interface IDblSelfBot : IDblBot From d0db2d6ad0c288b1c1744b23220c566a6320764d Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 16 Jun 2025 17:29:33 +0700 Subject: [PATCH 09/21] fix: add try-catch handling and IsRunning() method --- .../Adapter.cs | 22 +++++++++++++++---- DiscordBotsList.Api/Objects/IAdapter.cs | 2 ++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs b/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs index 84198af..ab7c15b 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs @@ -29,9 +29,14 @@ public virtual async Task RunAsync() throw new NotImplementedException(); } + public bool IsRunning() + { + return cancellationTokenSource != null; + } + public void Start() { - if (cancellationTokenSource != null) + if (IsRunning()) { return; } @@ -42,10 +47,19 @@ public void Start() { while (!cancellationTokenSource.Token.IsCancellationRequested) { - await RunAsync(); + try + { + await RunAsync(); + } + catch + { + cancellationTokenSource.Cancel(); + cancellationTokenSource = null; + break; + } SendLog("Submitted stats to Top.gg!"); - + await Task.Delay(updateTime, cancellationTokenSource.Token); } }, cancellationTokenSource.Token); @@ -53,7 +67,7 @@ public void Start() public void Stop() { - if (cancellationTokenSource != null) + if (IsRunning()) { cancellationTokenSource.Cancel(); cancellationTokenSource = null; diff --git a/DiscordBotsList.Api/Objects/IAdapter.cs b/DiscordBotsList.Api/Objects/IAdapter.cs index e58695e..9d67195 100644 --- a/DiscordBotsList.Api/Objects/IAdapter.cs +++ b/DiscordBotsList.Api/Objects/IAdapter.cs @@ -9,6 +9,8 @@ public interface IAdapter Task RunAsync(); + bool IsRunning(); + void Start(); void Stop(); From bcf216ff1f868c56e37118f8ee1363d5a62aeb76 Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 16 Jun 2025 22:00:05 +0700 Subject: [PATCH 10/21] feat: add StopAsync() --- DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs | 11 ++++++++++- .../DiscordNetDiscordBotsListApi.cs | 2 +- DiscordBotsList.Api/Objects/IAdapter.cs | 2 ++ LICENSE | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs b/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs index ab7c15b..1556dc0 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs @@ -17,7 +17,7 @@ public Adapter(TimeSpan updateTime) { if (updateTime < TimeSpan.FromMinutes(15)) { - throw new ArgumentException("updateTime must be at least 15 minutes.", nameof(updateTime)); + updateTime = TimeSpan.FromMinutes(15); } this.updateTime = updateTime; @@ -74,6 +74,15 @@ public void Stop() } } + public async Task StopAsync() + { + if (IsRunning()) + { + await cancellationTokenSource.CancelAsync(); + cancellationTokenSource = null; + } + } + private void SendLog(string msg) { Log?.Invoke(msg); diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs index 2755269..5a701a0 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs @@ -32,7 +32,7 @@ public DiscordNetDblApi(IDiscordClient client, string dblToken) : base(client.Cu /// public SubmissionAdapter CreateListener(TimeSpan? updateTime = null) { - return new SubmissionAdapter(this, client, updateTime ?? TimeSpan.Zero); + return new SubmissionAdapter(this, client, updateTime ?? TimeSpan.FromMinutes(15)); } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/IAdapter.cs b/DiscordBotsList.Api/Objects/IAdapter.cs index 9d67195..664d4bd 100644 --- a/DiscordBotsList.Api/Objects/IAdapter.cs +++ b/DiscordBotsList.Api/Objects/IAdapter.cs @@ -14,5 +14,7 @@ public interface IAdapter void Start(); void Stop(); + + Task StopAsync(); } } \ No newline at end of file diff --git a/LICENSE b/LICENSE index 9e3b10f..fe6a0b5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Discord Bots +Copyright (c) 2019-2025 Discord Bots Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 9793d6a1ed6972a2be84e478adb02b6bab7efbe9 Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 16 Jun 2025 22:06:21 +0700 Subject: [PATCH 11/21] deps: bump System.Text.Json (closes #28) --- DiscordBotsList.Api/DiscordBotsList.Api.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DiscordBotsList.Api/DiscordBotsList.Api.csproj b/DiscordBotsList.Api/DiscordBotsList.Api.csproj index ef98f98..e5ea59d 100644 --- a/DiscordBotsList.Api/DiscordBotsList.Api.csproj +++ b/DiscordBotsList.Api/DiscordBotsList.Api.csproj @@ -21,7 +21,7 @@ - + From 82f7467bee26dc20cfc7973387274185693f6c24 Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 16 Jun 2025 22:27:58 +0700 Subject: [PATCH 12/21] feat: make adapter events yield a nullable exception instead --- .../Adapter.cs | 24 +++++++++---------- DiscordBotsList.Api/Objects/IAdapter.cs | 6 +++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs b/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs index 1556dc0..de1ce42 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs @@ -1,3 +1,5 @@ +#nullable enable + using DiscordBotsList.Api.Objects; using System; using System.Threading; @@ -7,11 +9,10 @@ namespace DiscordBotsList.Api.Adapter.Discord.Net { public class Adapter : IAdapter { - public event Action Log; - + public event Action Posted = _ => { }; private readonly TimeSpan updateTime; - private CancellationTokenSource cancellationTokenSource; + private CancellationTokenSource? cancellationTokenSource; public Adapter(TimeSpan updateTime) { @@ -24,7 +25,7 @@ public Adapter(TimeSpan updateTime) cancellationTokenSource = null; } - public virtual async Task RunAsync() + public virtual Task RunAsync() { throw new NotImplementedException(); } @@ -51,14 +52,16 @@ public void Start() { await RunAsync(); } - catch + catch (Exception err) { + Posted?.Invoke(err); + cancellationTokenSource.Cancel(); cancellationTokenSource = null; break; } - SendLog("Submitted stats to Top.gg!"); + Posted?.Invoke(null); await Task.Delay(updateTime, cancellationTokenSource.Token); } @@ -69,7 +72,7 @@ public void Stop() { if (IsRunning()) { - cancellationTokenSource.Cancel(); + cancellationTokenSource!.Cancel(); cancellationTokenSource = null; } } @@ -78,14 +81,9 @@ public async Task StopAsync() { if (IsRunning()) { - await cancellationTokenSource.CancelAsync(); + await cancellationTokenSource!.CancelAsync(); cancellationTokenSource = null; } } - - private void SendLog(string msg) - { - Log?.Invoke(msg); - } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/IAdapter.cs b/DiscordBotsList.Api/Objects/IAdapter.cs index 664d4bd..e9fb685 100644 --- a/DiscordBotsList.Api/Objects/IAdapter.cs +++ b/DiscordBotsList.Api/Objects/IAdapter.cs @@ -1,11 +1,13 @@ -using System; +#nullable enable + +using System; using System.Threading.Tasks; namespace DiscordBotsList.Api.Objects { public interface IAdapter { - event Action Log; + event Action Posted; Task RunAsync(); From 4e5b0eca9e0b97381a82f43519c3eeaadddaba2b Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 16 Jun 2025 22:35:56 +0700 Subject: [PATCH 13/21] deps: bump version from 1.5.0 to 2.0.0 and Discord.Net from 2.4.0 to 3.17.4 --- .../DiscordBotsList.Api.Adapter.Discord.Net.csproj | 4 ++-- DiscordBotsList.Api/DiscordBotsList.Api.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj index 644d4fb..9b21b7d 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj +++ b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj @@ -13,12 +13,12 @@ git https://github.com/Top-gg-Community/dotnet-sdk https://github.com/Top-gg-Community/dotnet-sdk - 1.5.0 + 2.0.0 LICENSE - + diff --git a/DiscordBotsList.Api/DiscordBotsList.Api.csproj b/DiscordBotsList.Api/DiscordBotsList.Api.csproj index e5ea59d..ca1925e 100644 --- a/DiscordBotsList.Api/DiscordBotsList.Api.csproj +++ b/DiscordBotsList.Api/DiscordBotsList.Api.csproj @@ -16,7 +16,7 @@ DiscordBotsList.Api DiscordBotsList.Api DiscordBotsList.Api - 1.5.0 + 2.0.0 LICENSE From f3ab20063fc291ee3a5c4a793f1a78dbec0af726 Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 16 Jun 2025 22:39:30 +0700 Subject: [PATCH 14/21] refactor: switch event invoke order --- DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs b/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs index de1ce42..dbbf5ac 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/Adapter.cs @@ -54,10 +54,10 @@ public void Start() } catch (Exception err) { - Posted?.Invoke(err); - cancellationTokenSource.Cancel(); cancellationTokenSource = null; + + Posted?.Invoke(err); break; } From 2ab277fd7286c40d71107f26734cd6cb16595548 Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 16 Jun 2025 23:54:53 +0700 Subject: [PATCH 15/21] feat: merge unauthorized with authorized --- .../DiscordNetDiscordBotsListApi.cs | 2 +- .../SubmissionAdapter.cs | 4 +- DiscordBotsList.Api.Tests/UnitTests.cs | 4 +- .../AuthenticatedBotListApi.cs | 94 ------------------- DiscordBotsList.Api/DiscordBotListApi.cs | 72 ++++++++++++-- DiscordBotsList.Api/Internal/SelfBot.cs | 8 +- README.md | 9 +- 7 files changed, 76 insertions(+), 117 deletions(-) delete mode 100644 DiscordBotsList.Api/AuthenticatedBotListApi.cs diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs index 5a701a0..8fa4e08 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs @@ -13,7 +13,7 @@ public static DiscordNetDblApi CreateDblApi(this DiscordSocketClient client, str } } - public class DiscordNetDblApi : AuthDiscordBotListApi + public class DiscordNetDblApi : DiscordBotListApi { protected IDiscordClient client; diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs b/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs index 6dc5b62..f1a6edf 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs @@ -8,10 +8,10 @@ namespace DiscordBotsList.Api.Adapter.Discord.Net { public class SubmissionAdapter : Adapter { - private readonly AuthDiscordBotListApi api; + private readonly DiscordBotListApi api; private readonly IDiscordClient client; - public SubmissionAdapter(AuthDiscordBotListApi api, IDiscordClient client, TimeSpan updateTime) : base(updateTime) + public SubmissionAdapter(DiscordBotListApi api, IDiscordClient client, TimeSpan updateTime) : base(updateTime) { this.api = api; this.client = client; diff --git a/DiscordBotsList.Api.Tests/UnitTests.cs b/DiscordBotsList.Api.Tests/UnitTests.cs index c1626a9..c181eea 100644 --- a/DiscordBotsList.Api.Tests/UnitTests.cs +++ b/DiscordBotsList.Api.Tests/UnitTests.cs @@ -22,13 +22,13 @@ public static Credentials LoadFromEnv() public class UnitTests { - private readonly AuthDiscordBotListApi _api; + private readonly DiscordBotListApi _api; private readonly Credentials _cred; public UnitTests() { _cred = Credentials.LoadFromEnv(); - _api = new AuthDiscordBotListApi(_cred.BotId, _cred.Token); + _api = new DiscordBotListApi(_cred.BotId, _cred.Token); } [Fact] diff --git a/DiscordBotsList.Api/AuthenticatedBotListApi.cs b/DiscordBotsList.Api/AuthenticatedBotListApi.cs deleted file mode 100644 index 155e5c0..0000000 --- a/DiscordBotsList.Api/AuthenticatedBotListApi.cs +++ /dev/null @@ -1,94 +0,0 @@ -using DiscordBotsList.Api.Internal; -using DiscordBotsList.Api.Objects; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; - -namespace DiscordBotsList.Api -{ - public class AuthDiscordBotListApi : DiscordBotListApi - { - private readonly ulong _selfId; - - public AuthDiscordBotListApi(ulong selfId, string token) - { - _selfId = selfId; - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - } - - /// - /// Gets your own bot with as an ISelfBot - /// - /// your own bot with as an ISelfBot - public async Task GetMeAsync() - { - var bot = await GetBotAsync(_selfId); - bot.api = this; - return bot; - } - - /// - /// Gets unique voters that have voted on your bot - /// Max 1000, If you have more, you MUST use WEBHOOKS instead. - /// - /// The page number, defaults to 1 - /// A list of voters - public async Task> GetVotersAsync(int page = 1) - { - return (await GetVotersAsync(page)).Cast().ToList(); - } - - /// - /// Update your stats - /// - /// count of guilds - public async Task UpdateStatsAsync(int guildCount) - { - if (guildCount <= 0) - { - throw new ArgumentOutOfRangeException(nameof(guildCount), "guildCount cannot be less than 1."); - } - - await UpdateStatsAsync(new GuildCountObject(guildCount)); - } - - /// - /// returns true if user have voted for the past 12 hours - /// - /// Amount of days to filter - /// True or False - public async Task HasVoted(ulong userId) - { - return await HasVotedAsync(userId); - } - - protected async Task> GetVotersAsync(int page) - { - return await GetAuthorizedAsync>(Utils.CreateQuery($"bots/{_selfId}/votes?page={Math.Max(page, 1)}")); - } - - protected async Task UpdateStatsAsync(object statsObject) - { - var json = JsonSerializer.Serialize(statsObject); - var httpContent = new StringContent(json, Encoding.UTF8, "application/json"); - await _httpClient - .PostAsync($"{baseEndpoint}/bots/stats", httpContent); - } - - protected async Task GetAuthorizedAsync(string url) - { - return await GetAsync(url); - } - - protected async Task HasVotedAsync(ulong userId) - { - var url = $"bots/check?userId={userId}"; - return (await GetAsync(url)).HasVoted.GetValueOrDefault(0) == 1; - } - } -} \ No newline at end of file diff --git a/DiscordBotsList.Api/DiscordBotListApi.cs b/DiscordBotsList.Api/DiscordBotListApi.cs index a106c52..5f46164 100644 --- a/DiscordBotsList.Api/DiscordBotListApi.cs +++ b/DiscordBotsList.Api/DiscordBotListApi.cs @@ -2,8 +2,12 @@ using DiscordBotsList.Api.Internal.Queries; using DiscordBotsList.Api.Objects; using System; +using System.Collections.Generic; +using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Net.Http.Json; +using System.Text; using System.Text.Json; using System.Threading.Tasks; @@ -11,13 +15,18 @@ namespace DiscordBotsList.Api { public class DiscordBotListApi { - protected const string baseEndpoint = "https://top.gg/api/"; + private const string baseEndpoint = "https://top.gg/api/"; private readonly JsonSerializerOptions _serializerOptions; - protected HttpClient _httpClient; + private readonly ulong _selfId; + private readonly HttpClient _httpClient; - public DiscordBotListApi() + public DiscordBotListApi(ulong selfId, string token) { + _selfId = selfId; + _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); + _serializerOptions = new JsonSerializerOptions(); _serializerOptions.Converters.Add(new ULongToStringConverter()); } @@ -35,7 +44,7 @@ public async Task> GetBotsAsync(string sort = "monthlyPoi { throw new ArgumentException("sort must be 'monthlyPoints', 'id', or 'date'."); } - + if (count < 0 || count > 500) { throw new ArgumentOutOfRangeException(nameof(count), "count mustn't be negative or exceed 500."); @@ -58,7 +67,7 @@ public async Task> GetBotsAsync(string sort = "monthlyPoi /// Bot Object public async Task GetBotAsync(ulong id) { - return (IDblBot)await GetBotAsync(id); + return await GetBotAsync(id); } /// @@ -91,7 +100,7 @@ internal async Task GetBotAsync(ulong id) where T : Bot /// Type to parse to /// Url to get from /// Object of type T - protected async Task GetAsync(string url) + private async Task GetAsync(string url) { var t = await _httpClient.GetAsync(baseEndpoint + url); var result = t.IsSuccessStatusCode @@ -108,5 +117,56 @@ public async Task IsWeekendAsync() { return (await GetAsync("weekend")).Weekend; } + + + /// + /// Gets your own bot with as an ISelfBot + /// + /// your own bot with as an ISelfBot + public async Task GetMeAsync() + { + var bot = await GetBotAsync(_selfId); + bot.api = this; + return bot; + } + + /// + /// Gets unique voters that have voted on your bot + /// Max 1000, If you have more, you MUST use WEBHOOKS instead. + /// + /// The page number, defaults to 1 + /// A list of voters + public async Task> GetVotersAsync(int page = 1) + { + return (await GetAsync>(Utils.CreateQuery($"bots/{_selfId}/votes?page={Math.Max(page, 1)}"))).Cast().ToList(); + } + + /// + /// Update your stats + /// + /// count of guilds + public async Task UpdateStatsAsync(int guildCount) + { + if (guildCount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(guildCount), "guildCount cannot be less than 1."); + } + + var json = JsonSerializer.Serialize(new GuildCountObject(guildCount)); + var httpContent = new StringContent(json, Encoding.UTF8, "application/json"); + + await _httpClient.PostAsync($"{baseEndpoint}/bots/stats", httpContent); + } + + /// + /// returns true if user have voted for the past 12 hours + /// + /// Amount of days to filter + /// True or False + public async Task HasVoted(ulong userId) + { + var url = $"bots/check?userId={userId}"; + return (await GetAsync(url)).HasVoted.GetValueOrDefault(0) == 1; + } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Internal/SelfBot.cs b/DiscordBotsList.Api/Internal/SelfBot.cs index 6451f32..53680dd 100644 --- a/DiscordBotsList.Api/Internal/SelfBot.cs +++ b/DiscordBotsList.Api/Internal/SelfBot.cs @@ -8,22 +8,22 @@ internal class SelfBot : Bot, IDblSelfBot { public async Task> GetVotersAsync(int page = 1) { - return await ((AuthDiscordBotListApi)api).GetVotersAsync(page); + return await api.GetVotersAsync(page); } public async Task HasVotedAsync(ulong userId) { - return await ((AuthDiscordBotListApi)api).HasVoted(userId); + return await api.HasVoted(userId); } public async Task IsWeekendAsync() { - return await ((AuthDiscordBotListApi)api).IsWeekendAsync(); + return await api.IsWeekendAsync(); } public async Task UpdateStatsAsync(int guildCount) { - await ((AuthDiscordBotListApi)api).UpdateStatsAsync(guildCount); + await api.UpdateStatsAsync(guildCount); } } } \ No newline at end of file diff --git a/README.md b/README.md index 3cdebcb..a09a986 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,9 @@ top.gg botlist wrapper ## Usage -### Unauthorized api usage #### Setting up ```cs -DiscordBotListApi DblApi = new DiscordBotListApi(); +DiscordBotListApi DblApi = new DiscordBotListApi(BOT_DISCORD_ID, YOUR_TOKEN); ``` #### Getting bots @@ -14,12 +13,6 @@ DiscordBotListApi DblApi = new DiscordBotListApi(); IBot bot = DblApi.GetBotAsync(160105994217586689); ``` -### Authorized api usage -#### Setting up -```cs -AuthDiscordBotListApi DblApi = new AuthDiscordBotListApi(BOT_DISCORD_ID, YOUR_TOKEN); -``` - #### Updating stats ```cs IDblSelfBot me = await DblApi.GetMeAsync(); From 98cdbf7c2f9be39e73a32727dc5bfb40ea0b78a3 Mon Sep 17 00:00:00 2001 From: null8626 Date: Tue, 17 Jun 2025 00:18:04 +0700 Subject: [PATCH 16/21] feat: merge small with large widget --- .../DiscordNetDiscordBotsListApi.cs | 1 - DiscordBotsList.Api/DiscordBotListApi.cs | 15 +- .../Objects/SmallWidgetOptions.cs | 202 ------------------ DiscordBotsList.Api/Objects/Widget.cs | 15 ++ README.md | 7 +- 5 files changed, 24 insertions(+), 216 deletions(-) delete mode 100644 DiscordBotsList.Api/Objects/SmallWidgetOptions.cs create mode 100644 DiscordBotsList.Api/Objects/Widget.cs diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs index 8fa4e08..ddbcc03 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs @@ -1,6 +1,5 @@ using Discord; using Discord.WebSocket; -using DiscordBotsList.Api.Objects; using System; namespace DiscordBotsList.Api.Adapter.Discord.Net diff --git a/DiscordBotsList.Api/DiscordBotListApi.cs b/DiscordBotsList.Api/DiscordBotListApi.cs index 5f46164..ec70b8d 100644 --- a/DiscordBotsList.Api/DiscordBotListApi.cs +++ b/DiscordBotsList.Api/DiscordBotListApi.cs @@ -15,7 +15,7 @@ namespace DiscordBotsList.Api { public class DiscordBotListApi { - private const string baseEndpoint = "https://top.gg/api/"; + internal const string baseEndpoint = "https://top.gg/api/v1"; private readonly JsonSerializerOptions _serializerOptions; private readonly ulong _selfId; private readonly HttpClient _httpClient; @@ -55,7 +55,7 @@ public async Task> GetBotsAsync(string sort = "monthlyPoi throw new ArgumentOutOfRangeException(nameof(offset), "offset must not be negative."); } - var result = await GetAsync($"bots?sort={sort}&limit=${count}&offset=${offset}"); + var result = await GetAsync($"/bots?sort={sort}&limit=${count}&offset=${offset}"); foreach (var bot in result.Items) (bot as Bot).api = this; return result; } @@ -76,7 +76,7 @@ public async Task GetBotAsync(ulong id) /// IBotStats object related to the bot public async Task GetStatsAsync() { - return await GetAsync("bots/stats"); + return await GetAsync("/bots/stats"); } /// @@ -88,7 +88,7 @@ public async Task GetStatsAsync() /// Bot object of type T internal async Task GetBotAsync(ulong id) where T : Bot { - var t = await GetAsync($"bots/{id}"); + var t = await GetAsync($"/bots/{id}"); if (t == null) return null; t.api = this; return t; @@ -115,7 +115,7 @@ private async Task GetAsync(string url) /// True or False public async Task IsWeekendAsync() { - return (await GetAsync("weekend")).Weekend; + return (await GetAsync("/weekend")).Weekend; } @@ -138,7 +138,7 @@ public async Task GetMeAsync() /// A list of voters public async Task> GetVotersAsync(int page = 1) { - return (await GetAsync>(Utils.CreateQuery($"bots/{_selfId}/votes?page={Math.Max(page, 1)}"))).Cast().ToList(); + return (await GetAsync>($"/bots/{_selfId}/votes?page={Math.Max(page, 1)}")).Cast().ToList(); } /// @@ -165,8 +165,7 @@ public async Task UpdateStatsAsync(int guildCount) /// True or False public async Task HasVoted(ulong userId) { - var url = $"bots/check?userId={userId}"; - return (await GetAsync(url)).HasVoted.GetValueOrDefault(0) == 1; + return (await GetAsync($"/bots/check?userId={userId}")).HasVoted.GetValueOrDefault(0) == 1; } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/SmallWidgetOptions.cs b/DiscordBotsList.Api/Objects/SmallWidgetOptions.cs deleted file mode 100644 index a04effe..0000000 --- a/DiscordBotsList.Api/Objects/SmallWidgetOptions.cs +++ /dev/null @@ -1,202 +0,0 @@ -using System.Collections.Generic; - -namespace DiscordBotsList.Api.Objects -{ - public enum WidgetType - { - STATUS, - SERVERS, - LIB, - UPVOTES, - OWNER - } - - public class SmallWidgetOptions - { - private int? AvatarBackgroundColor; - private int? LeftColor; - private int? LeftTextColor; - private int? RightColor; - private int? RightTextColor; - private WidgetType Type; - - public SmallWidgetOptions SetType(WidgetType t) - { - Type = t; - return this; - } - - public SmallWidgetOptions SetAvatarBackgroundColor(int r, int g, int b) - { - AvatarBackgroundColor = Utils.FromColor(r, g, b); - return this; - } - - public SmallWidgetOptions SetLeftColor(int r, int g, int b) - { - LeftColor = Utils.FromColor(r, g, b); - return this; - } - - public SmallWidgetOptions SetRightColor(int r, int g, int b) - { - RightColor = Utils.FromColor(r, g, b); - return this; - } - - public SmallWidgetOptions SetLeftTextColor(int r, int g, int b) - { - LeftTextColor = Utils.FromColor(r, g, b); - return this; - } - - public SmallWidgetOptions SetRightTextColor(int r, int g, int b) - { - RightTextColor = Utils.FromColor(r, g, b); - return this; - } - - /// - /// Builds and returns a value. - /// - /// Id of the bot - /// Widget url - public string Build(ulong botId) - { - var query = $"https://top.gg/api/widget/{Type.ToString().ToLower()}/{botId}.svg"; - - var args = new List(); - - if (AvatarBackgroundColor != null) - args.Add($"avatarbg={AvatarBackgroundColor.Value.ToString("X")}"); - - if (LeftColor != null) - args.Add($"leftcolor={LeftColor.Value.ToString("X")}"); - - if (RightColor != null) - args.Add($"rightcolor={RightColor.Value.ToString("X")}"); - - if (LeftTextColor != null) - args.Add($"lefttextcolor={LeftTextColor.Value.ToString("X")}"); - - if (RightTextColor != null) - args.Add($"righttextcolor={RightTextColor.Value.ToString("X")}"); - - return Utils.CreateQuery(query, args.ToArray()); - } - } - - public class LargeWidgetOptions - { - private int? CertifiedColor; - private int? DataColor; - private int? HighlightColor; - private int? LabelColor; - private int? MiddleColor; - private int? TopColor; - private int? UsernameColor; - - public LargeWidgetOptions SetTopColor(int r, int g, int b) - { - TopColor = Utils.FromColor(r, g, b); - return this; - } - - public LargeWidgetOptions SetMiddleColor(int r, int g, int b) - { - MiddleColor = Utils.FromColor(r, g, b); - return this; - } - - public LargeWidgetOptions SetUsernameColor(int r, int g, int b) - { - UsernameColor = Utils.FromColor(r, g, b); - return this; - } - - public LargeWidgetOptions SetCertifiedColor(int r, int g, int b) - { - CertifiedColor = Utils.FromColor(r, g, b); - return this; - } - - public LargeWidgetOptions SetDataColor(int r, int g, int b) - { - DataColor = Utils.FromColor(r, g, b); - return this; - } - - public LargeWidgetOptions SetLabelColor(int r, int g, int b) - { - LabelColor = Utils.FromColor(r, g, b); - return this; - } - - public LargeWidgetOptions SetHighlightColor(int r, int g, int b) - { - HighlightColor = Utils.FromColor(r, g, b); - return this; - } - - /// - /// Builds and returns a value. - /// - /// Id of the bot - /// Widget url - public string Build(ulong botId) - { - var query = $"https://top.gg/api/widget/{botId}.svg"; - - var args = new List(); - - if (TopColor != null) - args.Add($"topcolor={TopColor.Value.ToString("X")}"); - - if (MiddleColor != null) - args.Add($"middlecolor={MiddleColor.Value.ToString("X")}"); - - if (UsernameColor != null) - args.Add($"usernamecolor={UsernameColor.Value.ToString("X")}"); - - if (CertifiedColor != null) - args.Add($"certifiedcolor={CertifiedColor.Value.ToString("X")}"); - - if (DataColor != null) - args.Add($"datacolor={DataColor.Value.ToString("X")}"); - - if (LabelColor != null) - args.Add($"labelcolor={LabelColor.Value.ToString("X")}"); - - if (HighlightColor != null) - args.Add($"highlightcolor={HighlightColor.Value.ToString("X")}"); - - return Utils.CreateQuery(query, args.ToArray()); - } - } - - internal static class Utils - { - public static int FromColor(float r, float g, float b) - { - return FromColor((int)(r * 255), (int)(g * 255), (int)(b * 255)); - } - - public static int FromColor(int r, int g, int b) - { - return (255 << 24) | ((byte)r << 16) | ((byte)g << 8) | ((byte)b << 0); - } - - /// - /// Creates rest parameters - /// - /// url - /// arguments - /// baseUrl?argument[0]&argument[1]&... - public static string CreateQuery(string baseUrl, params string[] args) - { - if (args.Length > 0) return $"{baseUrl}?{string.Join("&", args)}"; - - return baseUrl; - } - } -} \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/Widget.cs b/DiscordBotsList.Api/Objects/Widget.cs new file mode 100644 index 0000000..4571145 --- /dev/null +++ b/DiscordBotsList.Api/Objects/Widget.cs @@ -0,0 +1,15 @@ +namespace DiscordBotsList.Api.Objects +{ + public static class Widget + { + /// + /// Generates a large widget URL. + /// + /// The entity ID. + /// The widget URL. + public static string Large(ulong entityId) + { + return $"{DiscordBotListApi.baseEndpoint}/widgets/large/{entityId}"; + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index a09a986..7785fdb 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,10 @@ await me.UpdateStatsAsync(2133); #### Widgets ```cs -string widgetUrl = new SmallWidgetOptions() - .SetType(WidgetType.OWNER) - .SetLeftColor(255, 255, 255); - .Build(160105994217586689); +string widgetUrl = Widget.Large(1026525568344264724); ``` -Generates ![](https://top.gg/api/widget/status/160105994217586689.svg?leftcolor=FFFFFF) +Generates ![](https://top.gg/api/v1/widgets/large/1026525568344264724) ### Download #### Nuget From db82d5d8b27111544dbab468a243b4c01a135eac Mon Sep 17 00:00:00 2001 From: null8626 Date: Wed, 18 Jun 2025 20:48:40 +0700 Subject: [PATCH 17/21] feat: add small widgets --- DiscordBotsList.Api/Objects/Widget.cs | 44 +++++++++++++++++++++++++-- README.md | 23 +++++++++----- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/DiscordBotsList.Api/Objects/Widget.cs b/DiscordBotsList.Api/Objects/Widget.cs index 4571145..5eb12d1 100644 --- a/DiscordBotsList.Api/Objects/Widget.cs +++ b/DiscordBotsList.Api/Objects/Widget.cs @@ -1,15 +1,55 @@ namespace DiscordBotsList.Api.Objects { + public enum WidgetType + { + DISCORD_BOT, + DISCORD_SERVER, + } + public static class Widget { /// /// Generates a large widget URL. /// + /// The widget type. + /// The entity ID. + /// The widget URL. + public static string Large(WidgetType type, ulong entityId) + { + return $"{DiscordBotListApi.baseEndpoint}/widgets/large/{type.ToString().ToLower().Replace('_', '/')}/{entityId}"; + } + + /// + /// Generates a small widget URL for displaying votes. + /// + /// The widget type. + /// The entity ID. + /// The widget URL. + public static string Votes(WidgetType type, ulong entityId) + { + return $"{DiscordBotListApi.baseEndpoint}/widgets/small/votes/{type.ToString().ToLower().Replace('_', '/')}/{entityId}"; + } + + /// + /// Generates a small widget URL for displaying an entity's owner. + /// + /// The widget type. + /// The entity ID. + /// The widget URL. + public static string Owner(WidgetType type, ulong entityId) + { + return $"{DiscordBotListApi.baseEndpoint}/widgets/small/owner/{type.ToString().ToLower().Replace('_', '/')}/{entityId}"; + } + + /// + /// Generates a small widget URL for displaying social stats. + /// + /// The widget type. /// The entity ID. /// The widget URL. - public static string Large(ulong entityId) + public static string Social(WidgetType type, ulong entityId) { - return $"{DiscordBotListApi.baseEndpoint}/widgets/large/{entityId}"; + return $"{DiscordBotListApi.baseEndpoint}/widgets/small/social/{type.ToString().ToLower().Replace('_', '/')}/{entityId}"; } } } \ No newline at end of file diff --git a/README.md b/README.md index 7785fdb..ed66a0c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -# DBL-dotnet-Library -top.gg botlist wrapper +# Top.gg for .NET + +The community-maintained .NET library for Top.gg. ## Usage + #### Setting up ```cs DiscordBotListApi DblApi = new DiscordBotListApi(BOT_DISCORD_ID, YOUR_TOKEN); @@ -9,8 +11,8 @@ DiscordBotListApi DblApi = new DiscordBotListApi(BOT_DISCORD_ID, YOUR_TOKEN); #### Getting bots ```cs -// discord id -IBot bot = DblApi.GetBotAsync(160105994217586689); +// discord id +IBot bot = await DblApi.GetBotAsync(160105994217586689); ``` #### Updating stats @@ -23,12 +25,17 @@ await me.UpdateStatsAsync(2133); #### Widgets ```cs -string widgetUrl = Widget.Large(1026525568344264724); +string widgetUrl = Widget.Large(WidgetType.DISCORD_BOT, 1026525568344264724); ``` -Generates ![](https://top.gg/api/v1/widgets/large/1026525568344264724) +Generates ![](https://top.gg/api/v1/widgets/large/discord/bot/1026525568344264724) ### Download + #### Nuget -If you're using Nuget you can use find it with the ID `DiscordBotsList.Api` or use -> Install-Package DiscordBotsList.Api + +If you're using Nuget, you can use find it with the ID `DiscordBotsList.Api` or use + +``` +Install-Package DiscordBotsList.Api +``` From f8c54e58b654eff4459da1aebffabcf3d5c89f49 Mon Sep 17 00:00:00 2001 From: null8626 Date: Fri, 20 Jun 2025 14:32:35 +0700 Subject: [PATCH 18/21] doc: readme overhaul --- .../SubmissionAdapter.cs | 2 +- DiscordBotsList.Api.Tests/UnitTests.cs | 16 +- DiscordBotsList.Api/DiscordBotListApi.cs | 61 ++++---- DiscordBotsList.Api/Internal/Bot.cs | 4 +- .../Internal/GuildCountObject.cs | 12 +- DiscordBotsList.Api/Internal/SelfBot.cs | 4 +- DiscordBotsList.Api/Objects/IBotStats.cs | 2 +- DiscordBotsList.Api/Objects/IDblBot.cs | 2 +- DiscordBotsList.Api/Objects/Widget.cs | 18 ++- README.md | 137 +++++++++++++++--- 10 files changed, 177 insertions(+), 81 deletions(-) diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs b/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs index f1a6edf..82b6065 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs @@ -19,7 +19,7 @@ public SubmissionAdapter(DiscordBotListApi api, IDiscordClient client, TimeSpan public override async Task RunAsync() { - await api.UpdateStatsAsync((await client.GetGuildsAsync()).Count); + await api.UpdateServerCountAsync((await client.GetGuildsAsync()).Count); } } } \ No newline at end of file diff --git a/DiscordBotsList.Api.Tests/UnitTests.cs b/DiscordBotsList.Api.Tests/UnitTests.cs index c181eea..335a99b 100644 --- a/DiscordBotsList.Api.Tests/UnitTests.cs +++ b/DiscordBotsList.Api.Tests/UnitTests.cs @@ -57,13 +57,7 @@ public async Task GetBotTestAsync() Assert.NotNull(bot); Assert.Equal(botId, bot.Id); } - - [Fact] - public async Task GetMeTestAsync() - { - Assert.NotNull(await _api.GetMeAsync()); - } - + [Fact] public async Task GetBotsTestAsync() { @@ -78,15 +72,15 @@ public async Task GetBotsTestAsync() } [Fact] - public async Task GetStatsTestAsync() + public async Task GetServerCountTestAsync() { - Assert.NotNull(await _api.GetStatsAsync()); + await _api.GetServerCountAsync(); } [Fact] - public async Task UpdateStatsTestAsync() + public async Task UpdateServerCountTestAsync() { - await _api.UpdateStatsAsync(2); + await _api.UpdateServerCountAsync(2); } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/DiscordBotListApi.cs b/DiscordBotsList.Api/DiscordBotListApi.cs index ec70b8d..4154933 100644 --- a/DiscordBotsList.Api/DiscordBotListApi.cs +++ b/DiscordBotsList.Api/DiscordBotListApi.cs @@ -13,6 +13,13 @@ namespace DiscordBotsList.Api { + public enum SortBotsBy + { + MonthlyPoints, + Id, + Date, + } + public class DiscordBotListApi { internal const string baseEndpoint = "https://top.gg/api/v1"; @@ -34,34 +41,32 @@ public DiscordBotListApi(ulong selfId, string token) /// /// Gets bots from botlist /// - /// sorts results based on their monthly vote count ("montlyPoints"), id ("id"), or their submission date ("date") + /// sorts results based on their monthly vote count, id, or their submission date /// amount of bots to retrieve (max: 500) /// amount of bots to skip /// List of Bot Objects - public async Task> GetBotsAsync(string sort = "monthlyPoints", int count = 50, int offset = 0) + public async Task> GetBotsAsync(SortBotsBy sortBy = SortBotsBy.MonthlyPoints, int count = 50, int offset = 0) { - if (sort != "monthlyPoints" && sort != "id" && sort != "date") - { - throw new ArgumentException("sort must be 'monthlyPoints', 'id', or 'date'."); - } - if (count < 0 || count > 500) { - throw new ArgumentOutOfRangeException(nameof(count), "count mustn't be negative or exceed 500."); + count = 50; } if (offset < 0) { - throw new ArgumentOutOfRangeException(nameof(offset), "offset must not be negative."); + offset = 0; } - var result = await GetAsync($"/bots?sort={sort}&limit=${count}&offset=${offset}"); + var sortByString = sortBy.ToString(); + var result = await GetAsync($"/bots?sort={char.ToLowerInvariant(sortByString[0]) + sortByString.Substring(1)}&limit=${count}&offset=${offset}"); + foreach (var bot in result.Items) (bot as Bot).api = this; + return result; } /// - /// Get specific bot by Discord id + /// Gets specific bot by Discord id /// /// Discord id /// Bot Object @@ -71,12 +76,14 @@ public async Task GetBotAsync(ulong id) } /// - /// Get bot stats + /// Gets your bot's server count /// - /// IBotStats object related to the bot - public async Task GetStatsAsync() + /// Your bot's server count if available + public async Task GetServerCountAsync() { - return await GetAsync("/bots/stats"); + var result = await GetAsync("/bots/stats"); + + return result.ServerCount; } /// @@ -118,18 +125,6 @@ public async Task IsWeekendAsync() return (await GetAsync("/weekend")).Weekend; } - - /// - /// Gets your own bot with as an ISelfBot - /// - /// your own bot with as an ISelfBot - public async Task GetMeAsync() - { - var bot = await GetBotAsync(_selfId); - bot.api = this; - return bot; - } - /// /// Gets unique voters that have voted on your bot /// Max 1000, If you have more, you MUST use WEBHOOKS instead. @@ -142,17 +137,17 @@ public async Task> GetVotersAsync(int page = 1) } /// - /// Update your stats + /// Updates your bot's server count /// - /// count of guilds - public async Task UpdateStatsAsync(int guildCount) + /// Your bot's server count + public async Task UpdateServerCountAsync(int serverCount) { - if (guildCount <= 0) + if (serverCount <= 0) { - throw new ArgumentOutOfRangeException(nameof(guildCount), "guildCount cannot be less than 1."); + throw new ArgumentOutOfRangeException(nameof(serverCount), "serverCount cannot be less than 1."); } - var json = JsonSerializer.Serialize(new GuildCountObject(guildCount)); + var json = JsonSerializer.Serialize(new ServerCountObject(serverCount)); var httpContent = new StringContent(json, Encoding.UTF8, "application/json"); await _httpClient.PostAsync($"{baseEndpoint}/bots/stats", httpContent); diff --git a/DiscordBotsList.Api/Internal/Bot.cs b/DiscordBotsList.Api/Internal/Bot.cs index 25aa4c6..edd19b7 100644 --- a/DiscordBotsList.Api/Internal/Bot.cs +++ b/DiscordBotsList.Api/Internal/Bot.cs @@ -32,7 +32,7 @@ public class Bot : Entity, IDblBot [JsonPropertyName("date")] public DateTime submittedAt { get; set; } - [JsonPropertyName("server_count")] public int? guildCount { get; set; } + [JsonPropertyName("server_count")] public int? serverCount { get; set; } [JsonPropertyName("vanity")] public string vanity { get; set; } @@ -48,7 +48,7 @@ public class Bot : Entity, IDblBot public DateTime SubmittedAt => submittedAt; - public int? GuildCount => guildCount; + public int? ServerCount => serverCount; public string GithubUrl => githubUrl; diff --git a/DiscordBotsList.Api/Internal/GuildCountObject.cs b/DiscordBotsList.Api/Internal/GuildCountObject.cs index db9d761..9f72d23 100644 --- a/DiscordBotsList.Api/Internal/GuildCountObject.cs +++ b/DiscordBotsList.Api/Internal/GuildCountObject.cs @@ -3,19 +3,19 @@ namespace DiscordBotsList.Api.Internal { - internal class GuildCountObject + internal class ServerCountObject { - [JsonPropertyName("server_count")] internal int guildCount; + [JsonPropertyName("server_count")] internal int serverCount; - public GuildCountObject(int count) + public ServerCountObject(int count) { - guildCount = count; + serverCount = count; } } internal class BotStatsObject : IDblBotStats { - [JsonPropertyName("server_count")] internal int guildCount { get; set; } - public int GuildCount => guildCount; + [JsonPropertyName("server_count")] internal int serverCount { get; set; } + public int ServerCount => serverCount; } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Internal/SelfBot.cs b/DiscordBotsList.Api/Internal/SelfBot.cs index 53680dd..bc714db 100644 --- a/DiscordBotsList.Api/Internal/SelfBot.cs +++ b/DiscordBotsList.Api/Internal/SelfBot.cs @@ -21,9 +21,9 @@ public async Task IsWeekendAsync() return await api.IsWeekendAsync(); } - public async Task UpdateStatsAsync(int guildCount) + public async Task UpdateServerCountAsync(int serverCount) { - await api.UpdateStatsAsync(guildCount); + await api.UpdateServerCountAsync(serverCount); } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/IBotStats.cs b/DiscordBotsList.Api/Objects/IBotStats.cs index 46dfb90..fb2ab1b 100644 --- a/DiscordBotsList.Api/Objects/IBotStats.cs +++ b/DiscordBotsList.Api/Objects/IBotStats.cs @@ -4,6 +4,6 @@ namespace DiscordBotsList.Api.Objects { public interface IDblBotStats { - int GuildCount { get; } + int ServerCount { get; } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/IDblBot.cs b/DiscordBotsList.Api/Objects/IDblBot.cs index dc0af32..398394d 100644 --- a/DiscordBotsList.Api/Objects/IDblBot.cs +++ b/DiscordBotsList.Api/Objects/IDblBot.cs @@ -43,6 +43,6 @@ public interface IDblSelfBot : IDblBot Task IsWeekendAsync(); - Task UpdateStatsAsync(int guildCount); + Task UpdateServerCountAsync(int serverCount); } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/Widget.cs b/DiscordBotsList.Api/Objects/Widget.cs index 5eb12d1..c613a1e 100644 --- a/DiscordBotsList.Api/Objects/Widget.cs +++ b/DiscordBotsList.Api/Objects/Widget.cs @@ -1,13 +1,17 @@ -namespace DiscordBotsList.Api.Objects +using System.Text.RegularExpressions; + +namespace DiscordBotsList.Api.Objects { public enum WidgetType { - DISCORD_BOT, - DISCORD_SERVER, + DiscordBot, + DiscordServer, } public static class Widget { + private static readonly Regex typeConversionRegex = new Regex("(? /// Generates a large widget URL. /// @@ -16,7 +20,7 @@ public static class Widget /// The widget URL. public static string Large(WidgetType type, ulong entityId) { - return $"{DiscordBotListApi.baseEndpoint}/widgets/large/{type.ToString().ToLower().Replace('_', '/')}/{entityId}"; + return $"{DiscordBotListApi.baseEndpoint}/widgets/large/{typeConversionRegex.Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; } /// @@ -27,7 +31,7 @@ public static string Large(WidgetType type, ulong entityId) /// The widget URL. public static string Votes(WidgetType type, ulong entityId) { - return $"{DiscordBotListApi.baseEndpoint}/widgets/small/votes/{type.ToString().ToLower().Replace('_', '/')}/{entityId}"; + return $"{DiscordBotListApi.baseEndpoint}/widgets/small/votes/{typeConversionRegex.Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; } /// @@ -38,7 +42,7 @@ public static string Votes(WidgetType type, ulong entityId) /// The widget URL. public static string Owner(WidgetType type, ulong entityId) { - return $"{DiscordBotListApi.baseEndpoint}/widgets/small/owner/{type.ToString().ToLower().Replace('_', '/')}/{entityId}"; + return $"{DiscordBotListApi.baseEndpoint}/widgets/small/owner/{typeConversionRegex.Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; } /// @@ -49,7 +53,7 @@ public static string Owner(WidgetType type, ulong entityId) /// The widget URL. public static string Social(WidgetType type, ulong entityId) { - return $"{DiscordBotListApi.baseEndpoint}/widgets/small/social/{type.ToString().ToLower().Replace('_', '/')}/{entityId}"; + return $"{DiscordBotListApi.baseEndpoint}/widgets/small/social/{typeConversionRegex.Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; } } } \ No newline at end of file diff --git a/README.md b/README.md index ed66a0c..9ae3a93 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,144 @@ -# Top.gg for .NET +# Top.gg .NET SDK The community-maintained .NET library for Top.gg. +## Installation + +If you're using Nuget, you can use install it with the ID `DiscordBotsList.Api` like so: + +```powershell +> Install-Package DiscordBotsList.Api +``` + +## Setting up + +### Library agnostic + +```cs +var client = new DiscordBotListApi(DISCORD_ID, "TOPGG_TOKEN"); +``` + +### Discord.NET-based + +If you're using Nuget, you can use install it with the ID `DiscordBotsList.Api.Adapter.Discord.Net` like so: + +```powershell +> Install-Package DiscordBotsList.Api.Adapter.Discord.Net +``` + +Then use it in your code: + +```cs +var discordNetClient = ...; +var client = new DiscordNetDblApi(discordNetClient, "TOPGG_TOKEN"); +``` + ## Usage -#### Setting up +### Getting a bot + ```cs -DiscordBotListApi DblApi = new DiscordBotListApi(BOT_DISCORD_ID, YOUR_TOKEN); +// Discord ID +var bot = await client.GetBotAsync(264811613708746752U); ``` -#### Getting bots +### Getting several bots + +#### With defaults + ```cs -// discord id -IBot bot = await DblApi.GetBotAsync(160105994217586689); +var bots = await client.GetBotsAsync(); +var firstBot = bots.Items.First(); ``` -#### Updating stats +#### With explicit arguments + ```cs -IDblSelfBot me = await DblApi.GetMeAsync(); +// Sort by Limit Offset +var bots = await client.GetBotsAsync(SortBotsBy.MonthlyPoints, 100, 1); +var firstBot = bots.Items.First(); +``` + +### Getting your bot's voters -// Update stats guildCount -await me.UpdateStatsAsync(2133); +#### First page + +```cs +var voters = await client.GetVotersAsync(); ``` -#### Widgets +#### Subsequent pages + ```cs -string widgetUrl = Widget.Large(WidgetType.DISCORD_BOT, 1026525568344264724); +var voters = await client.GetVotersAsync(2); ``` -Generates ![](https://top.gg/api/v1/widgets/large/discord/bot/1026525568344264724) +### Check if a user has voted for your bot -### Download +```cs +// Discord ID +var voted = await client.HasVoted(661200758510977084U); +``` -#### Nuget +### Getting your bot's server count -If you're using Nuget, you can use find it with the ID `DiscordBotsList.Api` or use +```cs +var serverCount = await client.GetServerCountAsync(); +``` +### Posting your bot's server count + +```cs +// Server count +await client.UpdateServerCountAsync(bot.GetServerCount()); ``` -Install-Package DiscordBotsList.Api + +### Automatically posting your bot's server count every few minutes + +With Discord.NET: + +```cs +var submissionAdapter = client.CreateListener(); + +submissionAdapter.Start(); + +// ... + +submissionAdapter.Stop(); // Optional ``` + +### Checking if the weekend vote multiplier is active + +```cs +var isWeekend = await client.IsWeekendAsync(); +``` + +### Generating widget URLs + +#### Large + +```cs +// Widget type Discord ID +var widgetUrl = Widget.Large(WidgetType.DISCORD_BOT, 1026525568344264724U); +``` + +#### Votes + +```cs +// Widget type Discord ID +var widgetUrl = Widget.Votes(WidgetType.DISCORD_BOT, 1026525568344264724U); +``` + +#### Owner + +```cs +// Widget type Discord ID +var widgetUrl = Widget.Owner(WidgetType.DISCORD_BOT, 1026525568344264724U); +``` + +#### Social + +```cs +// Widget type Discord ID +var widgetUrl = Widget.Social(WidgetType.DISCORD_BOT, 1026525568344264724U); +``` \ No newline at end of file From 82b6a969066cf9bb6da6d15a89981ff9b475ae33 Mon Sep 17 00:00:00 2001 From: null8626 Date: Wed, 25 Jun 2025 01:24:49 +0700 Subject: [PATCH 19/21] feat: add webhooks --- ...ordBotsList.Api.Adapter.Discord.Net.csproj | 4 +- ...ordBotsList.Api.Webhooks.ASPNETCore.csproj | 28 ++++++++ ...iscordBotsList.Api.Webhooks.ASPNETCore.sln | 24 +++++++ .../IReceiver.cs | 14 ++++ .../LongToStringConverter.cs | 32 +++++++++ .../Middleware.cs | 70 +++++++++++++++++++ .../Vote.cs | 40 +++++++++++ DiscordBotsList.Api/DiscordBotListApi.cs | 10 +-- .../DiscordBotsList.Api.csproj | 4 +- DiscordBotsList.Api/Objects/Widget.cs | 25 ++----- README.md | 67 +++++++++++++++--- 11 files changed, 282 insertions(+), 36 deletions(-) create mode 100644 DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.csproj create mode 100644 DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.sln create mode 100644 DiscordBotsList.Api.Webhooks.ASPNETCore/IReceiver.cs create mode 100644 DiscordBotsList.Api.Webhooks.ASPNETCore/LongToStringConverter.cs create mode 100644 DiscordBotsList.Api.Webhooks.ASPNETCore/Middleware.cs create mode 100644 DiscordBotsList.Api.Webhooks.ASPNETCore/Vote.cs diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj index 9b21b7d..e4af33a 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj +++ b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj @@ -5,11 +5,11 @@ Velddev, Faith Top.gg DiscordBotsList.Api.Adapter.Discord.Net - Adapter for Discord.Net + Top.gg API adapter for Discord.Net Initial release true Mike Veldsink - discord bots list org wrapper api discord.net + discord bots topgg api discord.net git https://github.com/Top-gg-Community/dotnet-sdk https://github.com/Top-gg-Community/dotnet-sdk diff --git a/DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.csproj b/DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.csproj new file mode 100644 index 0000000..7ed6ed7 --- /dev/null +++ b/DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.csproj @@ -0,0 +1,28 @@ + + + net8.0 + Velddev, Faith, null8626 + Top.gg + Top.gg API webhooks wrapper for ASP.NET + Mike Veldsink + https://github.com/Top-gg-Community/dotnet-sdk + https://github.com/Top-gg-Community/dotnet-sdk + git + discord bots topgg api webhooks asp.net + 2.0.0 + LICENSE + + + + + + + + + + True + + + + + \ No newline at end of file diff --git a/DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.sln b/DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.sln new file mode 100644 index 0000000..832f91d --- /dev/null +++ b/DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotsList.Api.Webhooks.ASPNETCore", "DiscordBotsList.Api.Webhooks.ASPNETCore.csproj", "{1D925476-7407-E766-5044-48D0FCF35938}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1D925476-7407-E766-5044-48D0FCF35938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D925476-7407-E766-5044-48D0FCF35938}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D925476-7407-E766-5044-48D0FCF35938}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D925476-7407-E766-5044-48D0FCF35938}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2888D49C-0344-4012-8648-7B2959A50321} + EndGlobalSection +EndGlobal diff --git a/DiscordBotsList.Api.Webhooks.ASPNETCore/IReceiver.cs b/DiscordBotsList.Api.Webhooks.ASPNETCore/IReceiver.cs new file mode 100644 index 0000000..31af51d --- /dev/null +++ b/DiscordBotsList.Api.Webhooks.ASPNETCore/IReceiver.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; + +namespace DiscordBotsList.Api.Webhooks.ASPNETCore +{ + public interface IReceiver + { + /// + /// Receives webhook data. + /// + /// The webhook data. + /// Return type can be anything as it's ignored. + Task Callback(T data); + } +} \ No newline at end of file diff --git a/DiscordBotsList.Api.Webhooks.ASPNETCore/LongToStringConverter.cs b/DiscordBotsList.Api.Webhooks.ASPNETCore/LongToStringConverter.cs new file mode 100644 index 0000000..53b1412 --- /dev/null +++ b/DiscordBotsList.Api.Webhooks.ASPNETCore/LongToStringConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DiscordBotsList.Api.Webhooks.ASPNETCore +{ + /// + /// Converts API responses from strings to longs and vice versa. + /// + internal class ULongToStringConverter : JsonConverter + { + public override ulong Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + var stringValue = reader.GetString(); + + if (ulong.TryParse(stringValue, out var value)) return value; + + throw new InvalidOperationException(); + } + + public override void Write( + Utf8JsonWriter writer, + ulong value, + JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } + } +} \ No newline at end of file diff --git a/DiscordBotsList.Api.Webhooks.ASPNETCore/Middleware.cs b/DiscordBotsList.Api.Webhooks.ASPNETCore/Middleware.cs new file mode 100644 index 0000000..f32dda6 --- /dev/null +++ b/DiscordBotsList.Api.Webhooks.ASPNETCore/Middleware.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.Http; +using System.Text.Json; +using System.Threading.Tasks; + +namespace DiscordBotsList.Api.Webhooks.ASPNETCore +{ + public class Middleware where Receiver : IReceiver + { + private readonly JsonSerializerOptions _serializerOptions; + private readonly RequestDelegate _next; + private readonly string _path; + private readonly string _auth; + private readonly Receiver _receiver; + + public Middleware(RequestDelegate next, string path, string auth, Receiver receiver) + { + _next = next; + _path = path; + _auth = auth; + _receiver = receiver; + + _serializerOptions = new JsonSerializerOptions(); + _serializerOptions.Converters.Add(new ULongToStringConverter()); + } + + public async Task InvokeAsync(HttpContext context) + { + if (context.Request.Path.StartsWithSegments(_path) && context.Request.Method == "POST") + { + if (!context.Request.Headers.TryGetValue("Authorization", out var authorizationInput) || !authorizationInput.Equals(_auth)) + { + if (!context.Response.HasStarted) + { + context.Response.StatusCode = 401; + await context.Response.WriteAsync("Unauthorized"); + } + + return; + } + + var data = await JsonSerializer.DeserializeAsync(context.Request.Body, _serializerOptions); + + if (data != null) + { + await _receiver.Callback(data); + + if (!context.Response.HasStarted) + { + context.Response.StatusCode = 204; + await context.Response.WriteAsync(""); + } + } + + if (!context.Response.HasStarted) + { + context.Response.StatusCode = 400; + await context.Response.WriteAsync("Bad request"); + } + } + else + { + await _next(context); + } + } + } + + public class VoteMiddleware(RequestDelegate next, string path, string auth, Receiver receiver) : Middleware(next, path, auth, receiver) where Receiver : IReceiver + { + } +} \ No newline at end of file diff --git a/DiscordBotsList.Api.Webhooks.ASPNETCore/Vote.cs b/DiscordBotsList.Api.Webhooks.ASPNETCore/Vote.cs new file mode 100644 index 0000000..95a7915 --- /dev/null +++ b/DiscordBotsList.Api.Webhooks.ASPNETCore/Vote.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using System.Web; + +namespace DiscordBotsList.Api.Webhooks.ASPNETCore +{ + public class Vote + { + [JsonPropertyName("bot")] internal ulong botId { get; init; } + + [JsonPropertyName("guild")] internal ulong serverId { get; init; } + + [JsonPropertyName("user")] internal ulong voterId { get; init; } + + [JsonPropertyName("isWeekend")] internal bool isWeekend { get; init; } + + [JsonPropertyName("type")] internal string type { get; init; } + + [JsonPropertyName("query")] internal string query { get; init; } + + public ulong ReceiverId => botId == 0 ? serverId : botId; + public ulong VoterId => voterId; + public bool IsWeekend => isWeekend; + public bool IsTest => type == "test"; + public Dictionary Query + { + get + { + if (query == null) + { + return null; + } + + var parsedQuery = HttpUtility.ParseQueryString(query); + return parsedQuery.AllKeys.ToDictionary(key => key, key => parsedQuery[key]); + } + } + } +} \ No newline at end of file diff --git a/DiscordBotsList.Api/DiscordBotListApi.cs b/DiscordBotsList.Api/DiscordBotListApi.cs index 4154933..70221ed 100644 --- a/DiscordBotsList.Api/DiscordBotListApi.cs +++ b/DiscordBotsList.Api/DiscordBotListApi.cs @@ -39,7 +39,7 @@ public DiscordBotListApi(ulong selfId, string token) } /// - /// Gets bots from botlist + /// Fetches bots from Top.gg /// /// sorts results based on their monthly vote count, id, or their submission date /// amount of bots to retrieve (max: 500) @@ -66,7 +66,7 @@ public async Task> GetBotsAsync(SortBotsBy sortBy = SortB } /// - /// Gets specific bot by Discord id + /// Fetches specific bot by Discord ID /// /// Discord id /// Bot Object @@ -76,7 +76,7 @@ public async Task GetBotAsync(ulong id) } /// - /// Gets your bot's server count + /// Fetches your bot's server count /// /// Your bot's server count if available public async Task GetServerCountAsync() @@ -102,7 +102,7 @@ internal async Task GetBotAsync(ulong id) where T : Bot } /// - /// Gets and parses objects + /// Fetches and parses objects /// /// Type to parse to /// Url to get from @@ -126,7 +126,7 @@ public async Task IsWeekendAsync() } /// - /// Gets unique voters that have voted on your bot + /// Fetches unique voters that have voted on your bot /// Max 1000, If you have more, you MUST use WEBHOOKS instead. /// /// The page number, defaults to 1 diff --git a/DiscordBotsList.Api/DiscordBotsList.Api.csproj b/DiscordBotsList.Api/DiscordBotsList.Api.csproj index ca1925e..208c31c 100644 --- a/DiscordBotsList.Api/DiscordBotsList.Api.csproj +++ b/DiscordBotsList.Api/DiscordBotsList.Api.csproj @@ -4,12 +4,12 @@ net8.0 Velddev, Faith Top.gg - top.gg api wrapper + The community-maintained .NET library for Top.gg Mike Veldsink https://github.com/Top-gg-Community/dotnet-sdk https://github.com/Top-gg-Community/dotnet-sdk git - discord bots list org wrapper api + discord bots topgg api update to net 8 false true diff --git a/DiscordBotsList.Api/Objects/Widget.cs b/DiscordBotsList.Api/Objects/Widget.cs index c613a1e..fa6db4e 100644 --- a/DiscordBotsList.Api/Objects/Widget.cs +++ b/DiscordBotsList.Api/Objects/Widget.cs @@ -8,9 +8,10 @@ public enum WidgetType DiscordServer, } - public static class Widget + public static partial class Widget { - private static readonly Regex typeConversionRegex = new Regex("(? /// Generates a large widget URL. @@ -18,10 +19,7 @@ public static class Widget /// The widget type. /// The entity ID. /// The widget URL. - public static string Large(WidgetType type, ulong entityId) - { - return $"{DiscordBotListApi.baseEndpoint}/widgets/large/{typeConversionRegex.Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; - } + public static string Large(WidgetType type, ulong entityId) => $"{DiscordBotListApi.baseEndpoint}/widgets/large/{typeConversionRegex().Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; /// /// Generates a small widget URL for displaying votes. @@ -29,10 +27,7 @@ public static string Large(WidgetType type, ulong entityId) /// The widget type. /// The entity ID. /// The widget URL. - public static string Votes(WidgetType type, ulong entityId) - { - return $"{DiscordBotListApi.baseEndpoint}/widgets/small/votes/{typeConversionRegex.Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; - } + public static string Votes(WidgetType type, ulong entityId) => $"{DiscordBotListApi.baseEndpoint}/widgets/small/votes/{typeConversionRegex().Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; /// /// Generates a small widget URL for displaying an entity's owner. @@ -40,10 +35,7 @@ public static string Votes(WidgetType type, ulong entityId) /// The widget type. /// The entity ID. /// The widget URL. - public static string Owner(WidgetType type, ulong entityId) - { - return $"{DiscordBotListApi.baseEndpoint}/widgets/small/owner/{typeConversionRegex.Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; - } + public static string Owner(WidgetType type, ulong entityId) => $"{DiscordBotListApi.baseEndpoint}/widgets/small/owner/{typeConversionRegex().Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; /// /// Generates a small widget URL for displaying social stats. @@ -51,9 +43,6 @@ public static string Owner(WidgetType type, ulong entityId) /// The widget type. /// The entity ID. /// The widget URL. - public static string Social(WidgetType type, ulong entityId) - { - return $"{DiscordBotListApi.baseEndpoint}/widgets/small/social/{typeConversionRegex.Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; - } + public static string Social(WidgetType type, ulong entityId) => $"{DiscordBotListApi.baseEndpoint}/widgets/small/social/{typeConversionRegex().Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; } } \ No newline at end of file diff --git a/README.md b/README.md index 9ae3a93..a393830 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,28 @@ The community-maintained .NET library for Top.gg. ## Installation -If you're using Nuget, you can use install it with the ID `DiscordBotsList.Api` like so: +### Main API wrapper + +#### Library agnostic ```powershell > Install-Package DiscordBotsList.Api ``` +#### Discord.NET-based + +```powershell +> Install-Package DiscordBotsList.Api.Adapter.Discord.Net +``` + +### Webhooks only + +If you're using ASP.NET Core: + +```powershell +> Install-Package DiscordBotsList.Api.Webhooks.ASPNETCore +``` + ## Setting up ### Library agnostic @@ -20,14 +36,6 @@ var client = new DiscordBotListApi(DISCORD_ID, "TOPGG_TOKEN"); ### Discord.NET-based -If you're using Nuget, you can use install it with the ID `DiscordBotsList.Api.Adapter.Discord.Net` like so: - -```powershell -> Install-Package DiscordBotsList.Api.Adapter.Discord.Net -``` - -Then use it in your code: - ```cs var discordNetClient = ...; var client = new DiscordNetDblApi(discordNetClient, "TOPGG_TOKEN"); @@ -141,4 +149,45 @@ var widgetUrl = Widget.Owner(WidgetType.DISCORD_BOT, 1026525568344264724U); ```cs // Widget type Discord ID var widgetUrl = Widget.Social(WidgetType.DISCORD_BOT, 1026525568344264724U); +``` + +### Webhooks + +#### Being notified whenever someone voted for your bot + +With ASP.NET Core: + +```cs +using DiscordBotsList.Api.Webhooks.ASPNETCore; + +namespace MyServer +{ + internal class MyVoteListener : IReceiver + { + public Task Callback(Vote vote) + { + Console.WriteLine($"A user with the ID of {vote.VoterId} has voted us on Top.gg!"); + + return Task.CompletedTask; + } + } + + public class Program + { + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + var app = builder.Build(); + + app.UseMiddleware>("/votes", Environment.GetEnvironmentVariable("MY_TOPGG_WEBHOOK_SECRET"), new MyVoteListener()); + + app.Map("/", async context => + { + await context.Response.WriteAsync("Hello, World!"); + }); + + app.Run("http://localhost:8080"); + } + } +} ``` \ No newline at end of file From 31c4ae47166276f64324d46c56252d5e64a3bec1 Mon Sep 17 00:00:00 2001 From: null8626 Date: Fri, 27 Jun 2025 18:40:25 +0700 Subject: [PATCH 20/21] feat: remove .ASPNETCore to make it more general --- .../LongToStringConverter.cs | 32 ------------ .../DiscordBotsList.Api.Webhooks.csproj | 0 .../DiscordBotsList.Api.Webhooks.sln | 2 +- .../IReceiver.cs | 2 +- .../Middleware.cs | 6 +-- .../ULongToStringConverter.cs | 27 ++++++++++ .../Vote.cs | 23 ++++++--- DiscordBotsList.Api/Internal/Bot.cs | 49 +++++++++++++------ DiscordBotsList.Api/Internal/Entity.cs | 10 ++-- .../Internal/GuildCountObject.cs | 6 ++- .../Internal/HasVotedObject.cs | 3 +- .../Internal/Queries/BotListQuery.cs | 15 ++++-- DiscordBotsList.Api/Objects/BotReviews.cs | 6 ++- DiscordBotsList.Api/Objects/WeekendObject.cs | 3 +- ...Converter.cs => ULongToStringConverter.cs} | 16 +++--- README.md | 15 ++---- 16 files changed, 118 insertions(+), 97 deletions(-) delete mode 100644 DiscordBotsList.Api.Webhooks.ASPNETCore/LongToStringConverter.cs rename DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.csproj => DiscordBotsList.Api.Webhooks/DiscordBotsList.Api.Webhooks.csproj (100%) rename DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.sln => DiscordBotsList.Api.Webhooks/DiscordBotsList.Api.Webhooks.sln (89%) rename {DiscordBotsList.Api.Webhooks.ASPNETCore => DiscordBotsList.Api.Webhooks}/IReceiver.cs (86%) rename {DiscordBotsList.Api.Webhooks.ASPNETCore => DiscordBotsList.Api.Webhooks}/Middleware.cs (93%) create mode 100644 DiscordBotsList.Api.Webhooks/ULongToStringConverter.cs rename {DiscordBotsList.Api.Webhooks.ASPNETCore => DiscordBotsList.Api.Webhooks}/Vote.cs (51%) rename DiscordBotsList.Api/Serialization/{LongToStringConverter.cs => ULongToStringConverter.cs} (52%) diff --git a/DiscordBotsList.Api.Webhooks.ASPNETCore/LongToStringConverter.cs b/DiscordBotsList.Api.Webhooks.ASPNETCore/LongToStringConverter.cs deleted file mode 100644 index 53b1412..0000000 --- a/DiscordBotsList.Api.Webhooks.ASPNETCore/LongToStringConverter.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace DiscordBotsList.Api.Webhooks.ASPNETCore -{ - /// - /// Converts API responses from strings to longs and vice versa. - /// - internal class ULongToStringConverter : JsonConverter - { - public override ulong Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - var stringValue = reader.GetString(); - - if (ulong.TryParse(stringValue, out var value)) return value; - - throw new InvalidOperationException(); - } - - public override void Write( - Utf8JsonWriter writer, - ulong value, - JsonSerializerOptions options) - { - writer.WriteStringValue(value.ToString()); - } - } -} \ No newline at end of file diff --git a/DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.csproj b/DiscordBotsList.Api.Webhooks/DiscordBotsList.Api.Webhooks.csproj similarity index 100% rename from DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.csproj rename to DiscordBotsList.Api.Webhooks/DiscordBotsList.Api.Webhooks.csproj diff --git a/DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.sln b/DiscordBotsList.Api.Webhooks/DiscordBotsList.Api.Webhooks.sln similarity index 89% rename from DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.sln rename to DiscordBotsList.Api.Webhooks/DiscordBotsList.Api.Webhooks.sln index 832f91d..f37b2b1 100644 --- a/DiscordBotsList.Api.Webhooks.ASPNETCore/DiscordBotsList.Api.Webhooks.ASPNETCore.sln +++ b/DiscordBotsList.Api.Webhooks/DiscordBotsList.Api.Webhooks.sln @@ -2,7 +2,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.2.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotsList.Api.Webhooks.ASPNETCore", "DiscordBotsList.Api.Webhooks.ASPNETCore.csproj", "{1D925476-7407-E766-5044-48D0FCF35938}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotsList.Api.Webhooks", "DiscordBotsList.Api.Webhooks.csproj", "{1D925476-7407-E766-5044-48D0FCF35938}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/DiscordBotsList.Api.Webhooks.ASPNETCore/IReceiver.cs b/DiscordBotsList.Api.Webhooks/IReceiver.cs similarity index 86% rename from DiscordBotsList.Api.Webhooks.ASPNETCore/IReceiver.cs rename to DiscordBotsList.Api.Webhooks/IReceiver.cs index 31af51d..8ce84b8 100644 --- a/DiscordBotsList.Api.Webhooks.ASPNETCore/IReceiver.cs +++ b/DiscordBotsList.Api.Webhooks/IReceiver.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace DiscordBotsList.Api.Webhooks.ASPNETCore +namespace DiscordBotsList.Api.Webhooks { public interface IReceiver { diff --git a/DiscordBotsList.Api.Webhooks.ASPNETCore/Middleware.cs b/DiscordBotsList.Api.Webhooks/Middleware.cs similarity index 93% rename from DiscordBotsList.Api.Webhooks.ASPNETCore/Middleware.cs rename to DiscordBotsList.Api.Webhooks/Middleware.cs index f32dda6..a481886 100644 --- a/DiscordBotsList.Api.Webhooks.ASPNETCore/Middleware.cs +++ b/DiscordBotsList.Api.Webhooks/Middleware.cs @@ -2,7 +2,7 @@ using System.Text.Json; using System.Threading.Tasks; -namespace DiscordBotsList.Api.Webhooks.ASPNETCore +namespace DiscordBotsList.Api.Webhooks { public class Middleware where Receiver : IReceiver { @@ -47,11 +47,9 @@ public async Task InvokeAsync(HttpContext context) if (!context.Response.HasStarted) { context.Response.StatusCode = 204; - await context.Response.WriteAsync(""); } } - - if (!context.Response.HasStarted) + else if (!context.Response.HasStarted) { context.Response.StatusCode = 400; await context.Response.WriteAsync("Bad request"); diff --git a/DiscordBotsList.Api.Webhooks/ULongToStringConverter.cs b/DiscordBotsList.Api.Webhooks/ULongToStringConverter.cs new file mode 100644 index 0000000..58b92d4 --- /dev/null +++ b/DiscordBotsList.Api.Webhooks/ULongToStringConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DiscordBotsList.Api.Webhooks +{ + /// + /// Converts API responses from strings to longs and vice versa. + /// + internal class ULongToStringConverter : JsonConverter + { + public override ulong Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String && ulong.TryParse(reader.GetString(), out var value)) + { + return value; + } + + throw new InvalidOperationException(); + } + + public override void Write(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } + } +} \ No newline at end of file diff --git a/DiscordBotsList.Api.Webhooks.ASPNETCore/Vote.cs b/DiscordBotsList.Api.Webhooks/Vote.cs similarity index 51% rename from DiscordBotsList.Api.Webhooks.ASPNETCore/Vote.cs rename to DiscordBotsList.Api.Webhooks/Vote.cs index 95a7915..4057931 100644 --- a/DiscordBotsList.Api.Webhooks.ASPNETCore/Vote.cs +++ b/DiscordBotsList.Api.Webhooks/Vote.cs @@ -3,21 +3,30 @@ using System.Text.Json.Serialization; using System.Web; -namespace DiscordBotsList.Api.Webhooks.ASPNETCore +namespace DiscordBotsList.Api.Webhooks { public class Vote { - [JsonPropertyName("bot")] internal ulong botId { get; init; } + [JsonPropertyName("bot")] + [JsonConverter(typeof(ULongToStringConverter))] + public ulong botId { get; init; } - [JsonPropertyName("guild")] internal ulong serverId { get; init; } + [JsonPropertyName("guild")] + [JsonConverter(typeof(ULongToStringConverter))] + public ulong serverId { get; init; } - [JsonPropertyName("user")] internal ulong voterId { get; init; } + [JsonPropertyName("user")] + [JsonConverter(typeof(ULongToStringConverter))] + public ulong voterId { get; init; } - [JsonPropertyName("isWeekend")] internal bool isWeekend { get; init; } + [JsonPropertyName("isWeekend")] + public bool isWeekend { get; init; } - [JsonPropertyName("type")] internal string type { get; init; } + [JsonPropertyName("type")] + public string type { get; init; } - [JsonPropertyName("query")] internal string query { get; init; } + [JsonPropertyName("query")] + public string query { get; init; } public ulong ReceiverId => botId == 0 ? serverId : botId; public ulong VoterId => voterId; diff --git a/DiscordBotsList.Api/Internal/Bot.cs b/DiscordBotsList.Api/Internal/Bot.cs index edd19b7..225b3cb 100644 --- a/DiscordBotsList.Api/Internal/Bot.cs +++ b/DiscordBotsList.Api/Internal/Bot.cs @@ -10,37 +10,54 @@ public class Bot : Entity, IDblBot { internal DiscordBotListApi api; - [JsonPropertyName("clientid")] public ulong clientId { get; set; } + [JsonPropertyName("clientid")] + [JsonConverter(typeof(ULongToStringConverter))] + public ulong clientId { get; set; } - [JsonPropertyName("prefix")] public string prefix { get; set; } + [JsonPropertyName("prefix")] + public string prefix { get; set; } - [JsonPropertyName("shortdesc")] public string shortDescription { get; set; } + [JsonPropertyName("shortdesc")] + public string shortDescription { get; set; } - [JsonPropertyName("longdesc")] public string longDescription { get; set; } + [JsonPropertyName("longdesc")] + public string longDescription { get; set; } - [JsonPropertyName("tags")] public List tags { get; set; } + [JsonPropertyName("tags")] + public List tags { get; set; } - [JsonPropertyName("website")] public string websiteUrl { get; set; } + [JsonPropertyName("website")] + public string websiteUrl { get; set; } - [JsonPropertyName("support")] public string supportUrl { get; set; } + [JsonPropertyName("support")] + public string supportUrl { get; set; } - [JsonPropertyName("github")] public string githubUrl { get; set; } + [JsonPropertyName("github")] + public string githubUrl { get; set; } - [JsonPropertyName("owners")] public List owners { get; set; } + [JsonPropertyName("owners")] + public List owners { get; set; } - [JsonPropertyName("invite")] public string inviteUrl { get; set; } + [JsonPropertyName("invite")] + public string inviteUrl { get; set; } - [JsonPropertyName("date")] public DateTime submittedAt { get; set; } + [JsonPropertyName("date")] + public DateTime submittedAt { get; set; } - [JsonPropertyName("server_count")] public int? serverCount { get; set; } + [JsonPropertyName("server_count")] + public int? serverCount { get; set; } - [JsonPropertyName("vanity")] public string vanity { get; set; } + [JsonPropertyName("vanity")] + public string vanity { get; set; } - [JsonPropertyName("points")] public int points { get; set; } + [JsonPropertyName("points")] + public int points { get; set; } - [JsonPropertyName("monthlyPoints")] public int monthlyPoints { get; set; } + [JsonPropertyName("monthlyPoints")] + public int monthlyPoints { get; set; } - [JsonPropertyName("reviews")] public BotReviews reviews { get; set; } + [JsonPropertyName("reviews")] + public BotReviews reviews { get; set; } public string VanityTag => vanity; diff --git a/DiscordBotsList.Api/Internal/Entity.cs b/DiscordBotsList.Api/Internal/Entity.cs index d776574..12f0756 100644 --- a/DiscordBotsList.Api/Internal/Entity.cs +++ b/DiscordBotsList.Api/Internal/Entity.cs @@ -5,10 +5,14 @@ namespace DiscordBotsList.Api.Internal { public class Entity: IDblEntity { - [JsonPropertyName("avatar")] public string AvatarUrl { get; set; } + [JsonPropertyName("avatar")] + public string AvatarUrl { get; set; } - [JsonPropertyName("id")] public ulong Id { get; set; } + [JsonPropertyName("id")] + [JsonConverter(typeof(ULongToStringConverter))] + public ulong Id { get; set; } - [JsonPropertyName("username")] public string Username { get; set; } + [JsonPropertyName("username")] + public string Username { get; set; } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Internal/GuildCountObject.cs b/DiscordBotsList.Api/Internal/GuildCountObject.cs index 9f72d23..e2eddb4 100644 --- a/DiscordBotsList.Api/Internal/GuildCountObject.cs +++ b/DiscordBotsList.Api/Internal/GuildCountObject.cs @@ -5,7 +5,8 @@ namespace DiscordBotsList.Api.Internal { internal class ServerCountObject { - [JsonPropertyName("server_count")] internal int serverCount; + [JsonPropertyName("server_count")] + internal int serverCount; public ServerCountObject(int count) { @@ -15,7 +16,8 @@ public ServerCountObject(int count) internal class BotStatsObject : IDblBotStats { - [JsonPropertyName("server_count")] internal int serverCount { get; set; } + [JsonPropertyName("server_count")] + internal int serverCount { get; set; } public int ServerCount => serverCount; } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Internal/HasVotedObject.cs b/DiscordBotsList.Api/Internal/HasVotedObject.cs index 668c965..5185fe8 100644 --- a/DiscordBotsList.Api/Internal/HasVotedObject.cs +++ b/DiscordBotsList.Api/Internal/HasVotedObject.cs @@ -4,6 +4,7 @@ namespace DiscordBotsList.Api.Internal { internal class HasVotedObject { - [JsonPropertyName("voted")] public int? HasVoted { get; set; } + [JsonPropertyName("voted")] + public int? HasVoted { get; init; } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Internal/Queries/BotListQuery.cs b/DiscordBotsList.Api/Internal/Queries/BotListQuery.cs index 6aeaca9..c548a95 100644 --- a/DiscordBotsList.Api/Internal/Queries/BotListQuery.cs +++ b/DiscordBotsList.Api/Internal/Queries/BotListQuery.cs @@ -8,15 +8,20 @@ namespace DiscordBotsList.Api.Internal.Queries { internal class BotListQuery : ISearchResult { - [JsonPropertyName("results")] public List results { get; set; } + [JsonPropertyName("results")] + public List results { get; set; } - [JsonPropertyName("limit")] public int limit { get; set; } + [JsonPropertyName("limit")] + public int limit { get; set; } - [JsonPropertyName("offset")] public int? offset { get; set; } + [JsonPropertyName("offset")] + public int? offset { get; set; } - [JsonPropertyName("count")] public int count { get; set; } + [JsonPropertyName("count")] + public int count { get; set; } - [JsonPropertyName("total")] public int total { get; set; } + [JsonPropertyName("total")] + public int total { get; set; } public List Items => results .Cast() diff --git a/DiscordBotsList.Api/Objects/BotReviews.cs b/DiscordBotsList.Api/Objects/BotReviews.cs index 9539530..5e060ee 100644 --- a/DiscordBotsList.Api/Objects/BotReviews.cs +++ b/DiscordBotsList.Api/Objects/BotReviews.cs @@ -4,8 +4,10 @@ namespace DiscordBotsList.Api.Objects { public class BotReviews { - [JsonPropertyName("averageScore")] public double AverageScore { get; internal set; } + [JsonPropertyName("averageScore")] + public double AverageScore { get; internal set; } - [JsonPropertyName("count")] public int Count { get; internal set; } + [JsonPropertyName("count")] + public int Count { get; internal set; } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/WeekendObject.cs b/DiscordBotsList.Api/Objects/WeekendObject.cs index c16d0d5..e24b116 100644 --- a/DiscordBotsList.Api/Objects/WeekendObject.cs +++ b/DiscordBotsList.Api/Objects/WeekendObject.cs @@ -4,6 +4,7 @@ namespace DiscordBotsList.Api.Objects { public class WeekendObject { - [JsonPropertyName("is_weekend")] public bool Weekend { get; set; } + [JsonPropertyName("is_weekend")] + public bool Weekend { get; set; } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Serialization/LongToStringConverter.cs b/DiscordBotsList.Api/Serialization/ULongToStringConverter.cs similarity index 52% rename from DiscordBotsList.Api/Serialization/LongToStringConverter.cs rename to DiscordBotsList.Api/Serialization/ULongToStringConverter.cs index 5cbc7c4..1cd39f8 100644 --- a/DiscordBotsList.Api/Serialization/LongToStringConverter.cs +++ b/DiscordBotsList.Api/Serialization/ULongToStringConverter.cs @@ -9,21 +9,17 @@ namespace DiscordBotsList.Api.Internal /// internal class ULongToStringConverter : JsonConverter { - public override ulong Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) + public override ulong Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var stringValue = reader.GetString(); - if (ulong.TryParse(stringValue, out var value)) return value; + if (reader.TokenType == JsonTokenType.String && ulong.TryParse(reader.GetString(), out var value)) + { + return value; + } throw new InvalidOperationException(); } - public override void Write( - Utf8JsonWriter writer, - ulong value, - JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToString()); } diff --git a/README.md b/README.md index a393830..8784148 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,8 @@ The community-maintained .NET library for Top.gg. ### Webhooks only -If you're using ASP.NET Core: - ```powershell -> Install-Package DiscordBotsList.Api.Webhooks.ASPNETCore +> Install-Package DiscordBotsList.Api.Webhooks ``` ## Setting up @@ -46,7 +44,6 @@ var client = new DiscordNetDblApi(discordNetClient, "TOPGG_TOKEN"); ### Getting a bot ```cs -// Discord ID var bot = await client.GetBotAsync(264811613708746752U); ``` @@ -84,7 +81,6 @@ var voters = await client.GetVotersAsync(2); ### Check if a user has voted for your bot ```cs -// Discord ID var voted = await client.HasVoted(661200758510977084U); ``` @@ -97,7 +93,6 @@ var serverCount = await client.GetServerCountAsync(); ### Posting your bot's server count ```cs -// Server count await client.UpdateServerCountAsync(bot.GetServerCount()); ``` @@ -126,28 +121,24 @@ var isWeekend = await client.IsWeekendAsync(); #### Large ```cs -// Widget type Discord ID var widgetUrl = Widget.Large(WidgetType.DISCORD_BOT, 1026525568344264724U); ``` #### Votes ```cs -// Widget type Discord ID var widgetUrl = Widget.Votes(WidgetType.DISCORD_BOT, 1026525568344264724U); ``` #### Owner ```cs -// Widget type Discord ID var widgetUrl = Widget.Owner(WidgetType.DISCORD_BOT, 1026525568344264724U); ``` #### Social ```cs -// Widget type Discord ID var widgetUrl = Widget.Social(WidgetType.DISCORD_BOT, 1026525568344264724U); ``` @@ -155,10 +146,10 @@ var widgetUrl = Widget.Social(WidgetType.DISCORD_BOT, 1026525568344264724U); #### Being notified whenever someone voted for your bot -With ASP.NET Core: +With ASP.NET Core or Blazor: ```cs -using DiscordBotsList.Api.Webhooks.ASPNETCore; +using DiscordBotsList.Api.Webhooks; namespace MyServer { From 0dddd6e35c8f6761e82af8241cd3e118841b3b27 Mon Sep 17 00:00:00 2001 From: null8626 Date: Tue, 16 Sep 2025 22:04:48 +0700 Subject: [PATCH 21/21] feat: update in anticipation of v1 --- .../SubmissionAdapter.cs | 2 +- DiscordBotsList.Api.Tests/UnitTests.cs | 8 ++++---- DiscordBotsList.Api/DiscordBotListApi.cs | 15 +++++++-------- DiscordBotsList.Api/Internal/Bot.cs | 2 +- .../Internal/{Entity.cs => Project.cs} | 2 +- DiscordBotsList.Api/Internal/SelfBot.cs | 4 ++-- DiscordBotsList.Api/Objects/IDblBot.cs | 2 +- DiscordBotsList.Api/Objects/IDblEntity.cs | 6 +++--- DiscordBotsList.Api/Objects/Widget.cs | 18 +++++++++--------- 9 files changed, 29 insertions(+), 30 deletions(-) rename DiscordBotsList.Api/Internal/{Entity.cs => Project.cs} (92%) diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs b/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs index 82b6065..5c5bd14 100644 --- a/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs +++ b/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs @@ -19,7 +19,7 @@ public SubmissionAdapter(DiscordBotListApi api, IDiscordClient client, TimeSpan public override async Task RunAsync() { - await api.UpdateServerCountAsync((await client.GetGuildsAsync()).Count); + await api.UpdateBotServerCountAsync((await client.GetGuildsAsync()).Count); } } } \ No newline at end of file diff --git a/DiscordBotsList.Api.Tests/UnitTests.cs b/DiscordBotsList.Api.Tests/UnitTests.cs index 335a99b..2df812e 100644 --- a/DiscordBotsList.Api.Tests/UnitTests.cs +++ b/DiscordBotsList.Api.Tests/UnitTests.cs @@ -72,15 +72,15 @@ public async Task GetBotsTestAsync() } [Fact] - public async Task GetServerCountTestAsync() + public async Task GetBotServerCountTestAsync() { - await _api.GetServerCountAsync(); + await _api.GetBotServerCountAsync(); } [Fact] - public async Task UpdateServerCountTestAsync() + public async Task UpdateBotServerCountTestAsync() { - await _api.UpdateServerCountAsync(2); + await _api.UpdateBotServerCountAsync(2); } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/DiscordBotListApi.cs b/DiscordBotsList.Api/DiscordBotListApi.cs index 70221ed..9a5fb60 100644 --- a/DiscordBotsList.Api/DiscordBotListApi.cs +++ b/DiscordBotsList.Api/DiscordBotListApi.cs @@ -22,7 +22,7 @@ public enum SortBotsBy public class DiscordBotListApi { - internal const string baseEndpoint = "https://top.gg/api/v1"; + internal const string baseEndpoint = "https://top.gg/api"; private readonly JsonSerializerOptions _serializerOptions; private readonly ulong _selfId; private readonly HttpClient _httpClient; @@ -79,7 +79,7 @@ public async Task GetBotAsync(ulong id) /// Fetches your bot's server count /// /// Your bot's server count if available - public async Task GetServerCountAsync() + public async Task GetBotServerCountAsync() { var result = await GetAsync("/bots/stats"); @@ -126,21 +126,20 @@ public async Task IsWeekendAsync() } /// - /// Fetches unique voters that have voted on your bot - /// Max 1000, If you have more, you MUST use WEBHOOKS instead. + /// Fetches unique voters that have voted for your project /// /// The page number, defaults to 1 /// A list of voters public async Task> GetVotersAsync(int page = 1) { - return (await GetAsync>($"/bots/{_selfId}/votes?page={Math.Max(page, 1)}")).Cast().ToList(); + return (await GetAsync>($"/bots/{_selfId}/votes?page={Math.Max(page, 1)}")).Cast().ToList(); } /// /// Updates your bot's server count /// /// Your bot's server count - public async Task UpdateServerCountAsync(int serverCount) + public async Task UpdateBotServerCountAsync(int serverCount) { if (serverCount <= 0) { @@ -154,9 +153,9 @@ public async Task UpdateServerCountAsync(int serverCount) } /// - /// returns true if user have voted for the past 12 hours + /// returns true if user have voted for your project in the past 12 hours /// - /// Amount of days to filter + /// The specified user's ID /// True or False public async Task HasVoted(ulong userId) { diff --git a/DiscordBotsList.Api/Internal/Bot.cs b/DiscordBotsList.Api/Internal/Bot.cs index 225b3cb..7141bf3 100644 --- a/DiscordBotsList.Api/Internal/Bot.cs +++ b/DiscordBotsList.Api/Internal/Bot.cs @@ -6,7 +6,7 @@ namespace DiscordBotsList.Api.Internal { - public class Bot : Entity, IDblBot + public class Bot : Project, IDblBot { internal DiscordBotListApi api; diff --git a/DiscordBotsList.Api/Internal/Entity.cs b/DiscordBotsList.Api/Internal/Project.cs similarity index 92% rename from DiscordBotsList.Api/Internal/Entity.cs rename to DiscordBotsList.Api/Internal/Project.cs index 12f0756..6d939f2 100644 --- a/DiscordBotsList.Api/Internal/Entity.cs +++ b/DiscordBotsList.Api/Internal/Project.cs @@ -3,7 +3,7 @@ namespace DiscordBotsList.Api.Internal { - public class Entity: IDblEntity + public class Project: IDblEntity { [JsonPropertyName("avatar")] public string AvatarUrl { get; set; } diff --git a/DiscordBotsList.Api/Internal/SelfBot.cs b/DiscordBotsList.Api/Internal/SelfBot.cs index bc714db..bbf6d37 100644 --- a/DiscordBotsList.Api/Internal/SelfBot.cs +++ b/DiscordBotsList.Api/Internal/SelfBot.cs @@ -21,9 +21,9 @@ public async Task IsWeekendAsync() return await api.IsWeekendAsync(); } - public async Task UpdateServerCountAsync(int serverCount) + public async Task UpdateBotServerCountAsync(int serverCount) { - await api.UpdateServerCountAsync(serverCount); + await api.UpdateBotServerCountAsync(serverCount); } } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/IDblBot.cs b/DiscordBotsList.Api/Objects/IDblBot.cs index 398394d..eb4ceb6 100644 --- a/DiscordBotsList.Api/Objects/IDblBot.cs +++ b/DiscordBotsList.Api/Objects/IDblBot.cs @@ -43,6 +43,6 @@ public interface IDblSelfBot : IDblBot Task IsWeekendAsync(); - Task UpdateServerCountAsync(int serverCount); + Task UpdateBotServerCountAsync(int serverCount); } } \ No newline at end of file diff --git a/DiscordBotsList.Api/Objects/IDblEntity.cs b/DiscordBotsList.Api/Objects/IDblEntity.cs index d0ef8ac..c6ca1b0 100644 --- a/DiscordBotsList.Api/Objects/IDblEntity.cs +++ b/DiscordBotsList.Api/Objects/IDblEntity.cs @@ -3,17 +3,17 @@ public interface IDblEntity { /// - /// Discord Id + /// ID /// ulong Id { get; } /// - /// Username of the entity + /// Username /// string Username { get; } /// - /// Discord avatar url, or default avatar if none found. + /// Discord avatar URL, or default avatar if none found. /// string AvatarUrl { get; } } diff --git a/DiscordBotsList.Api/Objects/Widget.cs b/DiscordBotsList.Api/Objects/Widget.cs index fa6db4e..c349d29 100644 --- a/DiscordBotsList.Api/Objects/Widget.cs +++ b/DiscordBotsList.Api/Objects/Widget.cs @@ -17,32 +17,32 @@ public static partial class Widget /// Generates a large widget URL. /// /// The widget type. - /// The entity ID. + /// The project ID. /// The widget URL. - public static string Large(WidgetType type, ulong entityId) => $"{DiscordBotListApi.baseEndpoint}/widgets/large/{typeConversionRegex().Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; + public static string Large(WidgetType type, ulong projectId) => $"{DiscordBotListApi.baseEndpoint}/v1/widgets/large/{typeConversionRegex().Replace(type.ToString(), "/$1").ToLower()}/{projectId}"; /// /// Generates a small widget URL for displaying votes. /// /// The widget type. - /// The entity ID. + /// The project ID. /// The widget URL. - public static string Votes(WidgetType type, ulong entityId) => $"{DiscordBotListApi.baseEndpoint}/widgets/small/votes/{typeConversionRegex().Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; + public static string Votes(WidgetType type, ulong projectId) => $"{DiscordBotListApi.baseEndpoint}/v1/widgets/small/votes/{typeConversionRegex().Replace(type.ToString(), "/$1").ToLower()}/{projectId}"; /// - /// Generates a small widget URL for displaying an entity's owner. + /// Generates a small widget URL for displaying a project's owner. /// /// The widget type. - /// The entity ID. + /// The project ID. /// The widget URL. - public static string Owner(WidgetType type, ulong entityId) => $"{DiscordBotListApi.baseEndpoint}/widgets/small/owner/{typeConversionRegex().Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; + public static string Owner(WidgetType type, ulong projectId) => $"{DiscordBotListApi.baseEndpoint}/v1/widgets/small/owner/{typeConversionRegex().Replace(type.ToString(), "/$1").ToLower()}/{projectId}"; /// /// Generates a small widget URL for displaying social stats. /// /// The widget type. - /// The entity ID. + /// The project ID. /// The widget URL. - public static string Social(WidgetType type, ulong entityId) => $"{DiscordBotListApi.baseEndpoint}/widgets/small/social/{typeConversionRegex().Replace(type.ToString(), "/$1").ToLower()}/{entityId}"; + public static string Social(WidgetType type, ulong projectId) => $"{DiscordBotListApi.baseEndpoint}/v1/widgets/small/social/{typeConversionRegex().Replace(type.ToString(), "/$1").ToLower()}/{projectId}"; } } \ No newline at end of file