diff --git a/.gitignore b/.gitignore
index 7a1146b..a31f4be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
*.user
-*.json
*/.vs
.vs/
*/obj
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
deleted file mode 100644
index f9c182c..0000000
--- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordBotsList.Api.Adapter.Discord.Net.csproj
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
- net8.0
- Velddev, Faith
- Top.gg
- DiscordBotsList.Api.Adapter.Discord.Net
- Top.gg API adapter for Discord.Net
- Initial release
- true
- Mike Veldsink
- discord bots topgg api discord.net
- git
- https://github.com/Top-gg-Community/dotnet-sdk
- https://github.com/Top-gg-Community/dotnet-sdk
- 1.6.0
- LICENSE
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
diff --git a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs b/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs
deleted file mode 100644
index 4882985..0000000
--- a/DiscordBotsList.Api.Adapter.Discord.Net/DiscordNetDiscordBotsListApi.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-using Discord;
-using Discord.WebSocket;
-using DiscordBotsList.Api.Objects;
-using System;
-using System.Threading.Tasks;
-
-namespace DiscordBotsList.Api.Adapter.Discord.Net
-{
- public static class DiscordNetDblUtils
- {
- public static DiscordNetDblApi CreateDblApi(this DiscordSocketClient client, string dblToken)
- {
- return new DiscordNetDblApi(client, dblToken);
- }
-
- public static ShardedDiscordNetDblApi CreateDblApi(this DiscordShardedClient client, string dblToken)
- {
- return new ShardedDiscordNetDblApi(client, dblToken);
- }
- }
-
- public class DiscordNetDblApi : AuthDiscordBotListApi
- {
- protected IDiscordClient client;
-
- public DiscordNetDblApi(IDiscordClient client, string dblToken) : base(client.CurrentUser.Id, dblToken)
- {
- this.client = client;
- }
-
- 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().
- ///
- /// 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 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
deleted file mode 100644
index e9951e4..0000000
--- a/DiscordBotsList.Api.Adapter.Discord.Net/SubmissionAdapter.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using Discord;
-using Discord.WebSocket;
-using DiscordBotsList.Api.Objects;
-using System;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace DiscordBotsList.Api.Adapter.Discord.Net
-{
- internal class SubmissionAdapter : IAdapter
- {
- protected AuthDiscordBotListApi api;
- protected IDiscordClient client;
-
- protected DateTime lastTimeUpdated;
- protected TimeSpan updateTime;
-
- public SubmissionAdapter(AuthDiscordBotListApi api, IDiscordClient client, TimeSpan updateTime)
- {
- 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)
- {
- 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
deleted file mode 100644
index 2231dec..0000000
--- a/DiscordBotsList.Api.Adapter.Discord.Net/Utils/DiscordNetDblUtils.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using Discord.WebSocket;
-
-namespace DiscordBotsList.Api.Adapter.Discord.Net.Utils
-{
- public static class DiscordNetDblUtils
- {
- ///
- /// Creates a DiscordBotsList Api
- ///
- /// your client
- /// Your DiscordBotsList token
- /// A new instance of a DblApi
- public static DiscordNetDblApi CreateDblApi(this DiscordSocketClient client, string dblToken)
- {
- 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.Tests/.runsettings.template b/DiscordBotsList.Api.Tests/.runsettings.template
deleted file mode 100644
index 7657d98..0000000
--- a/DiscordBotsList.Api.Tests/.runsettings.template
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
- MY_API_KEY
- 123456
-
-
-
\ No newline at end of file
diff --git a/DiscordBotsList.Api.Tests/DiscordBotsList.Api.Tests.csproj b/DiscordBotsList.Api.Tests/DiscordBotsList.Api.Tests.csproj
deleted file mode 100644
index 8c2d6c6..0000000
--- a/DiscordBotsList.Api.Tests/DiscordBotsList.Api.Tests.csproj
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
- net8.0
- 8.0.0
-
- false
-
-
-
-
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
diff --git a/DiscordBotsList.Api.Tests/UnitTests.cs b/DiscordBotsList.Api.Tests/UnitTests.cs
deleted file mode 100644
index b22cbd2..0000000
--- a/DiscordBotsList.Api.Tests/UnitTests.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using System;
-using System.Threading.Tasks;
-using Xunit;
-
-namespace DiscordBotsList.Api.Tests
-{
- public class Credentials
- {
- public ulong BotId { get; set; }
- public string Token { get; set; }
-
- public static Credentials LoadFromEnv()
- {
- return new Credentials()
- {
- BotId = ulong.Parse(Environment.GetEnvironmentVariable("BOT_ID")),
- Token = Environment.GetEnvironmentVariable("API_KEY")
- };
- }
- }
-
- public class UnitTests
- {
- private readonly AuthDiscordBotListApi _api;
- private readonly Credentials _cred;
-
- public UnitTests()
- {
- _cred = Credentials.LoadFromEnv();
- _api = new AuthDiscordBotListApi(_cred.BotId, _cred.Token);
- }
-
- [Fact]
- public async Task HasVotedTestAsync()
- {
- Assert.False(await _api.HasVoted(0));
- }
-
- [Fact]
- public async Task TaskIsWeekendTestAsync()
- {
- await _api.IsWeekendAsync();
- }
-
- [Fact]
- public async Task TaskGetVotersTestAsync()
- {
- Assert.NotNull(await _api.GetVotersAsync());
- }
-
- [Fact]
- public async Task GetBotTestAsync()
- {
- var botId = 1026525568344264724U;
- var bot = await _api.GetBotAsync(botId);
- Assert.NotNull(bot);
- Assert.Equal(botId, bot.Id);
- }
-
- [Fact]
- public async Task GetMeTestAsync()
- {
- Assert.NotNull(await _api.GetMeAsync());
- }
-
- [Fact]
- public async Task GetUsersGetStatsTest()
- {
- var bots = await _api.GetBotsAsync();
-
- Assert.NotNull(bots);
- Assert.NotEmpty(bots.Items);
- }
- }
-}
\ No newline at end of file
diff --git a/DiscordBotsList.Api/AuthenticatedBotListApi.cs b/DiscordBotsList.Api/AuthenticatedBotListApi.cs
deleted file mode 100644
index 80828ca..0000000
--- a/DiscordBotsList.Api/AuthenticatedBotListApi.cs
+++ /dev/null
@@ -1,190 +0,0 @@
-using DiscordBotsList.Api.Internal;
-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.Text;
-using System.Text.Json;
-using System.Threading.Tasks;
-
-namespace DiscordBotsList.Api
-{
- public enum SortBotsBy
- {
- MonthlyPoints,
- Id,
- Date,
- }
-
- public class AuthDiscordBotListApi : DiscordBotListApi
- {
- private readonly ulong _selfId;
-
- public AuthDiscordBotListApi(ulong selfId, string token)
- {
- _selfId = selfId;
- _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
- }
-
- ///
- /// Fetches bots from Top.gg
- ///
- /// amount of bots to retrieve (max: 500)
- /// amount of bots to skip
- /// sorts results based on their monthly vote count, id, or their submission date
- /// List of Bot Objects
- public async Task> GetBotsAsync(int count = 50, int offset = 0, SortBotsBy sortBy = SortBotsBy.MonthlyPoints)
- {
- var sortByString = sortBy.ToString();
- sortByString = char.ToLowerInvariant(sortByString[0]) + sortByString[1..];
-
- var result = await GetAsync($"bots?limit={count}&offset={offset}&sort={sortByString}");
-
- foreach (var bot in result.Items) (bot as Bot).api = this;
- return result;
- }
-
- ///
- /// Template
- /// of GetBotAsync for internal usage.
- ///
- /// Type of Bot
- /// Discord id
- /// Bot object of type T
- internal async Task GetBotAsync(ulong id) where T : Bot
- {
- var t = await GetAsync($"bots/{id}");
- if (t == null) return null;
- t.api = this;
- return t;
- }
-
- ///
- /// Get specific bot by Discord id
- ///
- /// Discord id
- /// Bot Object
- public new async Task GetBotAsync(ulong id)
- {
- return await GetBotAsync(id);
- }
-
- ///
- /// Get bot stats
- ///
- /// Discord id, no longer needed
- /// IBotStats object related to the bot
- public new async Task GetBotStatsAsync(ulong id = 0)
- {
- return await GetAsync($"bots/{_selfId}/stats");
- }
-
- ///
- /// Get specific user by Discord id
- ///
- /// Discord id
- /// User Object
- public new async Task GetUserAsync(ulong id)
- {
- return await GetAsync($"users/{id}");
- }
-
- ///
- /// 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;
- }
-
- ///
- /// 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();
- }
-
- ///
- /// Update your stats unsharded
- ///
- /// count of guilds
- 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 the user has voted for your project in the past 12 hours
- ///
- /// the user ID
- /// True or False
- public async Task HasVoted(ulong userId)
- {
- return await HasVotedAsync(userId);
- }
-
- protected async Task> GetVotersAsync()
- {
- var query = $"bots/{_selfId}/votes";
- return await GetAuthorizedAsync>(Utils.CreateQuery(query));
- }
-
- 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);
- }
-
- protected Task GetAuthorizedAsync(string url)
- {
- return GetAsync(url);
- }
-
- protected async Task HasVotedAsync(ulong userId)
- {
- var url = $"bots/{_selfId}/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
deleted file mode 100644
index 1989805..0000000
--- a/DiscordBotsList.Api/DiscordBotListApi.cs
+++ /dev/null
@@ -1,95 +0,0 @@
-using DiscordBotsList.Api.Internal;
-using DiscordBotsList.Api.Objects;
-using System;
-using System.Net.Http;
-using System.Net.Http.Json;
-using System.Text.Json;
-using System.Threading.Tasks;
-
-namespace DiscordBotsList.Api
-{
- public class DiscordBotListApi
- {
- protected const string baseEndpoint = "https://top.gg/api/";
- private readonly JsonSerializerOptions _serializerOptions;
- protected readonly HttpClient _httpClient;
-
- public DiscordBotListApi()
- {
- _httpClient = new HttpClient();
- _serializerOptions = new JsonSerializerOptions();
- _serializerOptions.Converters.Add(new ULongToStringConverter());
- }
-
- ///
- /// Gets bots from botlist
- ///
- /// amount of bots to appear per page (max: 500)
- /// current page to query
- /// List of Bot Objects
- [Obsolete("This method requires a token to work. Please use the AuthenticatedBotListApi class instead.", true)]
- public Task> GetBotsAsync(int count = 50, int page = 0)
- {
- return null;
- }
-
- ///
- /// Get specific bot by Discord id
- ///
- /// Discord id
- /// Bot Object
- [Obsolete("This method requires a token to work. Please use the AuthenticatedBotListApi class instead.", true)]
- public Task GetBotAsync(ulong id)
- {
- return null;
- }
-
- ///
- /// Get bot stats
- ///
- /// Discord id
- /// IBotStats object related to the bot
- [Obsolete("This method requires a token to work. Please use the AuthenticatedBotListApi class instead.", true)]
- public Task GetBotStatsAsync(ulong id)
- {
- return null;
- }
-
- ///
- /// Get specific user by Discord id
- ///
- /// Discord id
- /// User Object
- [Obsolete("This method requires a token to work. Please use the AuthenticatedBotListApi class instead.", true)]
- public Task GetUserAsync(ulong id)
- {
- return null;
- }
-
- ///
- /// Gets and parses objects
- ///
- /// Type to parse to
- /// Url to get from
- /// Object of type T
- 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);
- return result.Value;
- }
-
- ///
- /// returns true if voting multiplier = x2
- ///
- /// True or False
- public async Task IsWeekendAsync()
- {
- return (await GetAsync("weekend")).Weekend;
- }
- }
-}
\ No newline at end of file
diff --git a/DiscordBotsList.Api/DiscordBotsList.Api.csproj b/DiscordBotsList.Api/DiscordBotsList.Api.csproj
deleted file mode 100644
index 9bc63ff..0000000
--- a/DiscordBotsList.Api/DiscordBotsList.Api.csproj
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- net8.0
- Velddev, Faith
- Top.gg
- top.gg api wrapper
- Mike Veldsink
- https://github.com/DiscordBotList/DBL-dotnet-Library
- https://github.com/DiscordBotList/DBL-dotnet-Library
- git
- discord bots list org wrapper api
- update to net 8
- false
- true
- DiscordBotsList.Api
- DiscordBotsList.Api
- DiscordBotsList.Api
- 1.6.0
- LICENSE
-
-
-
-
-
-
-
-
- True
-
-
-
-
-
diff --git a/DiscordBotsList.Api/Internal/Bot.cs b/DiscordBotsList.Api/Internal/Bot.cs
deleted file mode 100644
index 204bbe4..0000000
--- a/DiscordBotsList.Api/Internal/Bot.cs
+++ /dev/null
@@ -1,97 +0,0 @@
-using DiscordBotsList.Api.Objects;
-using System;
-using System.Collections.Generic;
-using System.Text.Json.Serialization;
-using System.Threading.Tasks;
-
-namespace DiscordBotsList.Api.Internal
-{
- public class Bot : Entity, IDblBot
- {
- internal DiscordBotListApi api;
-
- [JsonPropertyName("clientid")]
- [JsonConverter(typeof(ULongToStringConverter))]
- public ulong clientId { get; set; }
-
- [JsonPropertyName("prefix")] public string prefix { get; set; }
-
- [JsonPropertyName("shortdesc")] public string shortDescription { get; set; }
-
- [JsonPropertyName("longdesc")] public string longDescription { get; set; }
-
- [JsonPropertyName("tags")] public List tags { get; set; }
-
- [JsonPropertyName("website")] public string websiteUrl { get; set; }
-
- [JsonPropertyName("support")]
- public string supportUrl { get; set; }
-
- [Obsolete("Actually refers to the entire support invite URL, not just its invite code. Use SupportUrl instead.")]
- public string SupportInviteCode => supportUrl;
-
- [JsonPropertyName("github")] public string githubUrl { get; set; }
-
- [JsonPropertyName("owners")] public List owners { get; set; }
-
- [JsonPropertyName("invite")] public string customInvite { get; set; }
-
- [Obsolete("Actually refers to when the bot was submitted. Use submittedAt instead.")]
- public DateTime approvedAt => submittedAt;
-
- [JsonPropertyName("date")] public DateTime submittedAt { get; set; }
-
- [JsonPropertyName("certifiedBot")] public bool certified { get; set; }
-
- [JsonPropertyName("vanity")] public string vanity { get; set; }
-
- [JsonPropertyName("points")] public int points { get; set; }
-
- [JsonPropertyName("monthlyPoints")] public int monthlyPoints { get; set; }
-
- [JsonPropertyName("reviews")]
- public BotReviews reviews { get; set; }
-
- public ulong ClientId => clientId;
-
- public string VanityTag => vanity;
-
- [Obsolete("Actually refers to when the bot was submitted. Use SubmittedAt instead.")]
- public DateTime ApprovedAt => submittedAt;
-
- 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;
-
- public List OwnerIds => owners;
-
- public int Points => points;
-
- public int MonthlyPoints => monthlyPoints;
-
- public string ShortDescription => shortDescription;
-
- public List Tags => tags;
-
- public string SupportUrl => supportUrl;
-
- public string VanityUrl => "https://top.gg/bot/" + vanity;
-
- public string WebsiteUrl => websiteUrl;
-
- public BotReviews Reviews => reviews;
-
- public async Task GetStatsAsync()
- {
- return await ((AuthDiscordBotListApi)api).GetBotStatsAsync(Id);
- }
- }
-}
\ No newline at end of file
diff --git a/DiscordBotsList.Api/Internal/BotReviews.cs b/DiscordBotsList.Api/Internal/BotReviews.cs
deleted file mode 100644
index 61fd342..0000000
--- a/DiscordBotsList.Api/Internal/BotReviews.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace DiscordBotsList.Api.Internal
-{
- 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/Internal/Entity.cs b/DiscordBotsList.Api/Internal/Entity.cs
deleted file mode 100644
index 739e836..0000000
--- a/DiscordBotsList.Api/Internal/Entity.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using DiscordBotsList.Api.Objects;
-using System.Text.Json.Serialization;
-
-namespace DiscordBotsList.Api.Internal
-{
- public class Entity : IDblEntity
- {
- [JsonPropertyName("avatar")] public string Avatar { get; set; }
-
- [JsonPropertyName("defAvatar")] public string DefaultAvatar { get; set; }
-
- public string AvatarUrl => Avatar;
-
- [JsonPropertyName("id")]
- [JsonConverter(typeof(ULongToStringConverter))]
- 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/GuildCountObject.cs b/DiscordBotsList.Api/Internal/GuildCountObject.cs
deleted file mode 100644
index 83055ac..0000000
--- a/DiscordBotsList.Api/Internal/GuildCountObject.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using DiscordBotsList.Api.Objects;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Json.Serialization;
-
-namespace DiscordBotsList.Api.Internal
-{
- internal class GuildCountObject
- {
- [JsonPropertyName("server_count")] internal int guildCount;
-
- public GuildCountObject(int count)
- {
- guildCount = count;
- }
- }
-
- internal class BotStatsObject : ShardedObject, 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/HasVotedObject.cs b/DiscordBotsList.Api/Internal/HasVotedObject.cs
deleted file mode 100644
index 668c965..0000000
--- a/DiscordBotsList.Api/Internal/HasVotedObject.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace DiscordBotsList.Api.Internal
-{
- internal class HasVotedObject
- {
- [JsonPropertyName("voted")] public int? HasVoted { get; set; }
- }
-}
\ No newline at end of file
diff --git a/DiscordBotsList.Api/Internal/Queries/BotListQuery.cs b/DiscordBotsList.Api/Internal/Queries/BotListQuery.cs
deleted file mode 100644
index 656fb80..0000000
--- a/DiscordBotsList.Api/Internal/Queries/BotListQuery.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using DiscordBotsList.Api.Objects;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Json.Serialization;
-
-namespace DiscordBotsList.Api.Internal.Queries
-{
- internal class BotListQuery : ISearchResult
- {
- [JsonPropertyName("results")] public List results { get; set; }
-
- [JsonPropertyName("limit")] public int limit { get; set; }
-
- [JsonPropertyName("offset")] public int? offset { get; set; }
-
- [JsonPropertyName("count")] public int count { get; set; }
-
- [JsonPropertyName("total")] public int total { get; set; }
-
- public List Items => results
- .Cast()
- .ToList();
-
- 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);
- }
-}
\ No newline at end of file
diff --git a/DiscordBotsList.Api/Internal/SelfBot.cs b/DiscordBotsList.Api/Internal/SelfBot.cs
deleted file mode 100644
index 946ba01..0000000
--- a/DiscordBotsList.Api/Internal/SelfBot.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using DiscordBotsList.Api.Objects;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-
-namespace DiscordBotsList.Api.Internal
-{
- internal class SelfBot : Bot, IDblSelfBot
- {
- public async Task> GetVotersAsync(int page = 1)
- {
- return await ((AuthDiscordBotListApi)api).GetVotersAsync(page);
- }
-
- public async Task HasVotedAsync(ulong userId)
- {
- return await ((AuthDiscordBotListApi)api).HasVoted(userId);
- }
-
- public async Task IsWeekendAsync()
- {
- return await ((AuthDiscordBotListApi)api).IsWeekendAsync();
- }
-
- 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
deleted file mode 100644
index 66ca92e..0000000
--- a/DiscordBotsList.Api/Internal/User.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using DiscordBotsList.Api.Objects;
-using System.Text.Json.Serialization;
-
-namespace DiscordBotsList.Api.Internal
-{
- public class User : Entity, IDblUser
- {
- [JsonPropertyName("social")] public SocialConnections Social { get; set; }
-
- [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; }
-
- [JsonPropertyName("admin")] public bool IsAdmin { get; set; }
-
- public SocialConnections Connections => Social;
- }
-}
\ No newline at end of file
diff --git a/DiscordBotsList.Api/Objects/ApiRequest.cs b/DiscordBotsList.Api/Objects/ApiRequest.cs
deleted file mode 100644
index 9aaecb1..0000000
--- a/DiscordBotsList.Api/Objects/ApiRequest.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System;
-using System.Net;
-
-namespace DiscordBotsList.Api.Objects
-{
- public class ApiResult
- {
- private ApiResult()
- {
- }
-
- public T Value { get; private set; }
-
- ///
- /// The error reason for this API request, if any.
- ///
- public string ErrorReason { get; private set; }
-
- public bool IsSuccess { get; private set; }
-
- internal static ApiResult FromSuccess(T value)
- {
- return new ApiResult { Value = value, IsSuccess = true };
- }
-
- internal static ApiResult FromError(Exception ex)
- {
- return new ApiResult { Value = default, ErrorReason = ex.Message, IsSuccess = false };
- }
-
- internal static ApiResult
- FromHttpError(
- HttpStatusCode statusCode) // This could be altered to collect an object that provides more information
- {
- return new ApiResult { Value = default, ErrorReason = statusCode.ToString(), IsSuccess = false };
- }
- }
-}
\ No newline at end of file
diff --git a/DiscordBotsList.Api/Objects/IAdapter.cs b/DiscordBotsList.Api/Objects/IAdapter.cs
deleted file mode 100644
index e58695e..0000000
--- a/DiscordBotsList.Api/Objects/IAdapter.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-namespace DiscordBotsList.Api.Objects
-{
- public interface IAdapter
- {
- event Action Log;
-
- Task RunAsync();
-
- void Start();
-
- void Stop();
- }
-}
\ No newline at end of file
diff --git a/DiscordBotsList.Api/Objects/IBotStats.cs b/DiscordBotsList.Api/Objects/IBotStats.cs
deleted file mode 100644
index 116efb0..0000000
--- a/DiscordBotsList.Api/Objects/IBotStats.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Collections.Generic;
-
-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
deleted file mode 100644
index e69ce9e..0000000
--- a/DiscordBotsList.Api/Objects/IDblBot.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
-using DiscordBotsList.Api.Internal;
-
-namespace DiscordBotsList.Api.Objects
-{
- public interface IDblBot : IDblEntity
- {
- ulong ClientId { get; }
-
- string VanityTag { get; }
-
- string PrefixUsed { get; }
-
- string ShortDescription { get; }
-
- string LongDescription { get; }
-
- List Tags { get; }
-
- string WebsiteUrl { get; }
-
- string SupportUrl { get; }
-
- string GithubUrl { get; }
-
- List OwnerIds { get; }
-
- string InviteUrl { get; }
-
- DateTime SubmittedAt { get; }
-
- bool IsCertified { get; }
-
- string VanityUrl { get; }
-
- int Points { get; }
-
- int MonthlyPoints { get; }
-
- BotReviews Reviews { get; }
-
- Task GetStatsAsync();
- }
-
- public interface IDblSelfBot : IDblBot
- {
- Task> GetVotersAsync(int page = 1);
-
- Task HasVotedAsync(ulong userId);
-
- 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
deleted file mode 100644
index d952521..0000000
--- a/DiscordBotsList.Api/Objects/IDblEntity.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-namespace DiscordBotsList.Api.Objects
-{
- public interface IDblEntity
- {
- ///
- /// Id
- ///
- ulong Id { get; }
-
- ///
- /// Username
- ///
- string Username { get; }
-
- ///
- /// Discriminator, the XXXX#1234 part
- ///
- string Discriminator { get; }
-
- ///
- /// Avatar url, or default avatar if none found.
- ///
- string AvatarUrl { get; }
- }
-}
\ 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 8d8073a..0000000
--- a/DiscordBotsList.Api/Objects/IDblUser.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-namespace DiscordBotsList.Api.Objects
-{
- 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; }
-
- bool IsAdmin { get; }
- }
-}
\ No newline at end of file
diff --git a/DiscordBotsList.Api/Objects/ISearchResult.cs b/DiscordBotsList.Api/Objects/ISearchResult.cs
deleted file mode 100644
index 4eb5c45..0000000
--- a/DiscordBotsList.Api/Objects/ISearchResult.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-using System.Collections.Generic;
-
-namespace DiscordBotsList.Api.Objects
-{
- public interface ISearchResult
- {
- ///
- /// Items returned from search
- ///
- List Items { get; }
-
- ///
- /// The current page you've navigated
- ///
- int CurrentPage { get; }
-
- ///
- /// Set items per page
- ///
- int ItemsPerPage { get; }
-
- ///
- /// Total amount of items found in the search
- ///
- int TotalItems { get; }
-
- ///
- /// Total amount of pages found in the search
- ///
- int TotalPages { get; }
- }
-}
\ 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/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
diff --git a/DiscordBotsList.Api/Objects/WeekendObject.cs b/DiscordBotsList.Api/Objects/WeekendObject.cs
deleted file mode 100644
index e24b116..0000000
--- a/DiscordBotsList.Api/Objects/WeekendObject.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Text.Json.Serialization;
-
-namespace DiscordBotsList.Api.Objects
-{
- public class WeekendObject
- {
- [JsonPropertyName("is_weekend")]
- public bool Weekend { get; set; }
- }
-}
\ No newline at end of file
diff --git a/DiscordBotsList.Api/Serialization/ULongToStringConverter.cs b/DiscordBotsList.Api/Serialization/ULongToStringConverter.cs
deleted file mode 100644
index 1cd39f8..0000000
--- a/DiscordBotsList.Api/Serialization/ULongToStringConverter.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace DiscordBotsList.Api.Internal
-{
- ///
- /// 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/LICENSE b/LICENSE
index fe6a0b5..c4c27be 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019-2025 Discord Bots
+Copyright (c) 2019-2026 Top.gg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 9c3d32e..a972e35 100644
--- a/README.md
+++ b/README.md
@@ -1,52 +1,201 @@
-# DBL-dotnet-Library
-top.gg botlist wrapper
+# Top.gg .NET SDK
+
+The community-maintained .NET SDK for Top.gg.
+
+## Chapters
+
+- [Installation](#installation)
+- [Setting up](#setting-up)
+- [Usage](#usage)
+ - [Getting your project's information](#getting-your-projects-information)
+ - [Getting your project's vote information of a user](#getting-your-projects-vote-information-of-a-user)
+ - [Getting a cursor-based paginated list of votes for your project](#getting-a-cursor-based-paginated-list-of-votes-for-your-project)
+ - [Posting your bot's application commands list](#posting-your-bots-application-commands-list)
+ - [Generating widget URLs](#generating-widget-urls)
+ - [Webhooks](#webhooks)
+
+## Installation
+
+### Main API wrapper
+
+```console
+$ dotnet add package Topgg.Sdk.Api --version 1.0.0
+```
+
+### Webhooks only
+
+```console
+$ dotnet add package Topgg.Sdk.Webhooks --version 1.0.0
+```
+
+## Setting up
+
+```cs
+using Topgg.Sdk.Api;
+
+var client = new TopggApi(Environment.GetEnvironmentVariable("TOPGG_TOKEN"));
+```
## Usage
-### Unauthorized api usage
-#### Setting up
+
+### Getting your project's information
+
+```cs
+var project = await client.GetSelfAsync();
+```
+
+### Getting your project's vote information of a user
+
+#### Discord ID
+
+```cs
+using Topgg.Sdk.Api.Data;
+
+var vote = await client.GetVoteAsync(661200758510977084);
+```
+
+#### Top.gg ID
+
+```cs
+using Topgg.Sdk.Api.Data;
+
+var vote = await client.GetVoteAsync(8226924471638491136, UserSource.Topgg);
+```
+
+### Getting a cursor-based paginated list of votes for your project
+
+```cs
+var since = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+var firstPage = await client.GetVotesAsync(since);
+
+foreach (var vote in firstPage.Votes)
+{
+ // ...
+}
+
+var secondPage = await firstPage.Next();
+
+foreach (var vote in secondPage.Votes)
+{
+ // ...
+}
+```
+
+### Posting your bot's application commands list
+
+#### Discord.Net
+
```cs
-DiscordBotListApi DblApi = new DiscordBotListApi();
+var commands = $"[{string.Join(",", (await bot.GetGlobalApplicationCommandsAsync()).Select(command => command.ToJson()))}]";
+
+await client.PostCommandsAsync(commands);
```
-#### Getting bots
+#### Raw
+
```cs
-// discord id
-IBot bot = DblApi.GetBotAsync(160105994217586689);
+// Array of application commands that
+// can be serialized to Discord API's raw JSON format.
+var commands = @"[
+ {
+ ""options"": [],
+ ""name"": ""test"",
+ ""name_localizations"": null,
+ ""description"": ""command description"",
+ ""description_localizations"": null,
+ ""contexts"": [],
+ ""default_permission"": null,
+ ""default_member_permissions"": null,
+ ""dm_permission"": false,
+ ""integration_types"": [],
+ ""nsfw"": false
+ }
+]";
+
+await client.PostCommandsAsync(commands);
```
-#### Getting users
+### Generating widget URLs
+
+#### Large
+
```cs
-// discord id
-IUser bot = DblApi.GetUserAsync(121919449996460033);
+using Topgg.Sdk.Api.Data;
+using Topgg.Sdk.Api;
+
+var widgetUrl = Widget.Large(Platform.Discord, ProjectType.Bot, 1026525568344264724);
```
-### Authorized api usage
-#### Setting up
+#### Votes
+
```cs
-AuthDiscordBotListApi DblApi = new AuthDiscordBotListApi(BOT_DISCORD_ID, YOUR_TOKEN);
+using Topgg.Sdk.Api.Data;
+using Topgg.Sdk.Api;
+
+var widgetUrl = Widget.Votes(Platform.Discord, ProjectType.Bot, 1026525568344264724);
```
-#### Updating stats
+#### Owner
+
```cs
-IDblSelfBot me = await DblApi.GetMeAsync();
-// Update stats sharded indexShard shardCount shards
-await me.UpdateStatsAsync(24, 50, new[] { 12, 421, 62, 241, 524, 534 });
+using Topgg.Sdk.Api.Data;
+using Topgg.Sdk.Api;
-// Update stats guildCount
-await me.UpdateStatsAsync(2133);
+var widgetUrl = Widget.Owner(Platform.Discord, ProjectType.Bot, 1026525568344264724);
```
-#### Widgets
+#### Social
+
+```cs
+using Topgg.Sdk.Api.Data;
+using Topgg.Sdk.Api;
+
+var widgetUrl = Widget.Social(Platform.Discord, ProjectType.Bot, 1026525568344264724);
+```
+
+### Webhooks
+
+With ASP.NET Core:
+
```cs
-string widgetUrl = new SmallWidgetOptions()
- .SetType(WidgetType.OWNER)
- .SetLeftColor(255, 255, 255);
- .Build(160105994217586689);
+using Topgg.Sdk.Webhooks.Payloads;
+using Topgg.Sdk.Webhooks;
+
+public class Webhooks() : WebhookEventListener(Environment.GetEnvironmentVariable("TOPGG_WEBHOOK_SECRET"))
+{
+ // Optional
+ public override Task OnIntegrationCreate(HttpContext context, IntegrationCreatePayload payload, string trace) => DefaultResponse(context);
+
+ // Optional
+ public override Task OnIntegrationDelete(HttpContext context, IntegrationDeletePayload payload, string trace) => DefaultResponse(context);
+
+ // Optional
+ public override Task OnTest(HttpContext context, TestPayload payload, string trace) => DefaultResponse(context);
+
+ // Optional
+ public override Task OnVoteCreate(HttpContext context, VoteCreatePayload payload, string trace) => DefaultResponse(context);
+
+ private static async Task DefaultResponse(HttpContext context)
+ {
+ if (!context.Response.HasStarted)
+ {
+ context.Response.StatusCode = 204;
+ }
+ }
+}
```
-Generates 
+Later, in your server's setup:
+
+```cs
+var builder = WebApplication.CreateBuilder(args);
+var app = builder.Build();
+
+var webhooks = new CustomWebhooks();
+
+// POST /webhook
+app.MapPost("/webhook", webhooks.Handler);
-### Download
-#### Nuget
-If you're using Nuget you can use find it with the ID `DiscordBotsList.Api` or use
-> Install-Package DiscordBotsList.Api
+app.Run();
+```
\ No newline at end of file
diff --git a/Topgg.Sdk.Api/Data/Project.cs b/Topgg.Sdk.Api/Data/Project.cs
new file mode 100644
index 0000000..4304b17
--- /dev/null
+++ b/Topgg.Sdk.Api/Data/Project.cs
@@ -0,0 +1,77 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Topgg.Sdk.Api.Serialization;
+
+namespace Topgg.Sdk.Api.Data;
+
+/// A project's platform.
+public enum Platform
+{
+ Discord
+}
+
+/// A project's type.
+public enum ProjectType
+{
+ Bot,
+ Server
+}
+
+/// A project listed on Top.gg.
+public class Project
+{
+ /// The project's ID.
+ [JsonConverter(typeof(ULongToStringConverter))]
+ public ulong Id { get; internal init; }
+
+ /// The project's name sourced from the external platform.
+ public string Name { get; internal init; }
+
+ /// The project's platform.
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public Platform Platform { get; internal init; }
+
+ /// The project's type.
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public ProjectType Type { get; internal init; }
+
+ /// The project's short description.
+ public string Headline { get; internal init; }
+
+ /// The project's tag IDs.
+ public List Tags { get; internal init; }
+
+ /// The project's current vote count that affects the project's ranking.
+ public int Votes { get; internal init; }
+
+ /// The project's total vote count.
+ [JsonPropertyName("votes_total")]
+ public int TotalVotes { get; internal init; }
+
+ /// The project's review score out of 5.
+ public float ReviewScore { get; internal init; }
+
+ /// The project's total review count.
+ public int ReviewCount { get; internal init; }
+}
+
+/// A brief information on a project listed on Top.gg.
+public class PartialProject
+{
+ /// The project's ID.
+ [JsonConverter(typeof(ULongToStringConverter))]
+ public ulong Id { get; internal init; }
+
+ /// The project's type.
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public ProjectType Type { get; internal init; }
+
+ /// The project's platform.
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public Platform Platform { get; internal init; }
+
+ /// The project's platform ID.
+ [JsonConverter(typeof(ULongToStringConverter))]
+ public ulong PlatformId { get; internal init; }
+
+}
diff --git a/Topgg.Sdk.Api/Data/User.cs b/Topgg.Sdk.Api/Data/User.cs
new file mode 100644
index 0000000..b44d269
--- /dev/null
+++ b/Topgg.Sdk.Api/Data/User.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+using Topgg.Sdk.Api.Serialization;
+
+namespace Topgg.Sdk.Api.Data;
+
+/// A user account from an external platform that is linked to a Top.gg user account.
+public enum UserSource
+{
+ Discord,
+ Topgg,
+}
+
+/// A brief information of a project's vote.
+public class PartialVote
+{
+ /// When the vote was cast.
+ [JsonPropertyName("created_at")]
+ public DateTime VotedAt { get; internal init; }
+
+ /// When the vote expires and the user is required to vote again.
+ public DateTime ExpiresAt { get; internal init; }
+
+ /// The number of votes this vote counted for. This is a rounded integer value which determines how many points this individual vote was worth.
+ public int Weight { get; internal init; }
+}
+
+/// A project's vote information.
+public class Vote : PartialVote
+{
+ /// The voter's ID.
+ [JsonPropertyName("user_id")]
+ [JsonConverter(typeof(ULongToStringConverter))]
+ public ulong VoterId { get; internal init; }
+
+ /// The voter's ID on the project's platform.
+ [JsonConverter(typeof(ULongToStringConverter))]
+ public ulong PlatformId { get; internal init; }
+}
+
+/// A paginated list of a project's vote information.
+public class PaginatedVotes
+{
+ /// The votes in this page.
+ [JsonPropertyName("data")]
+ public List Votes { get; internal init; }
+
+ [JsonInclude]
+ internal string Cursor { get; init; }
+
+ internal TopggApi Client;
+
+ /// Tries to advance to the next page.
+ /// The next page of votes.
+ public async Task Next() => await Client.GetVotesAsync(Cursor);
+}
\ No newline at end of file
diff --git a/Topgg.Sdk.Api/Serialization/ULongToStringConverter.cs b/Topgg.Sdk.Api/Serialization/ULongToStringConverter.cs
new file mode 100644
index 0000000..c53f7a6
--- /dev/null
+++ b/Topgg.Sdk.Api/Serialization/ULongToStringConverter.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Topgg.Sdk.Api.Serialization;
+
+/// 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/Topgg.Sdk.Api/Tests/Mock.cs b/Topgg.Sdk.Api/Tests/Mock.cs
new file mode 100644
index 0000000..d40c138
--- /dev/null
+++ b/Topgg.Sdk.Api/Tests/Mock.cs
@@ -0,0 +1,64 @@
+#nullable enable
+
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Topgg.Sdk.Api.Tests;
+
+internal class Mock : HttpMessageHandler
+{
+#pragma warning disable SYSLIB1045
+ private static readonly (HttpMethod Method, Regex Endpoint, string? Name)[] Routes = {
+ (HttpMethod.Get, new Regex(@"^\/projects\/@me$", RegexOptions.Compiled), "GetSelf"),
+ (HttpMethod.Get, new Regex(@"^\/projects\/@me\/votes\/\d+$", RegexOptions.Compiled), "GetVote"),
+ (HttpMethod.Get, new Regex(@"^\/projects\/@me\/votes$", RegexOptions.Compiled), "GetVotes"),
+ (HttpMethod.Post, new Regex(@"^\/projects\/@me\/commands$", RegexOptions.Compiled), null)
+ };
+#pragma warning restore SYSLIB1045
+
+ private static Stream StreamJson(string name) => typeof(Mock).Assembly.GetManifestResourceStream($"Topgg.Sdk.Api.Tests.Mocks.{name}.json")!;
+
+ internal static string ReadJson(string name)
+ {
+ using var stream = StreamJson(name);
+ using var reader = new StreamReader(stream);
+
+ return reader.ReadToEnd();
+ }
+
+ protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ if (request.RequestUri!.Host == "top.gg" && request.RequestUri.AbsolutePath.StartsWith("/api/v1/"))
+ {
+ var endpoint = request.RequestUri.AbsolutePath[7..];
+
+ foreach (var (Method, Endpoint, Name) in Routes)
+ {
+ if (request.Method == Method && Endpoint.IsMatch(endpoint))
+ {
+ if (Name == null)
+ {
+ return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NoContent));
+ }
+ else
+ {
+ var content = new StreamContent(StreamJson(Name));
+ content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
+
+ return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
+ {
+ Content = content,
+ });
+ }
+ }
+ }
+ }
+
+ return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound));
+ }
+}
\ No newline at end of file
diff --git a/Topgg.Sdk.Api/Tests/Mocks/GetSelf.json b/Topgg.Sdk.Api/Tests/Mocks/GetSelf.json
new file mode 100644
index 0000000..e74997b
--- /dev/null
+++ b/Topgg.Sdk.Api/Tests/Mocks/GetSelf.json
@@ -0,0 +1,16 @@
+{
+ "id": "364806029876555776",
+ "name": "Top.gg Lib Dev API Access",
+ "type": "bot",
+ "platform": "discord",
+ "headline": "API access for Top.gg Library Developers",
+ "tags": [
+ "api",
+ "library",
+ "topgg"
+ ],
+ "votes": 4,
+ "votes_total": 34,
+ "review_score": 5,
+ "review_count": 2
+}
\ No newline at end of file
diff --git a/Topgg.Sdk.Api/Tests/Mocks/GetVote.json b/Topgg.Sdk.Api/Tests/Mocks/GetVote.json
new file mode 100644
index 0000000..c6a0227
--- /dev/null
+++ b/Topgg.Sdk.Api/Tests/Mocks/GetVote.json
@@ -0,0 +1,5 @@
+{
+ "created_at": "2026-02-25T22:35:36.978392+00:00",
+ "expires_at": "2026-02-26T10:35:36.978392+00:00",
+ "weight": 1
+}
\ No newline at end of file
diff --git a/Topgg.Sdk.Api/Tests/Mocks/GetVotes.json b/Topgg.Sdk.Api/Tests/Mocks/GetVotes.json
new file mode 100644
index 0000000..5eab378
--- /dev/null
+++ b/Topgg.Sdk.Api/Tests/Mocks/GetVotes.json
@@ -0,0 +1,33 @@
+{
+ "cursor": "",
+ "data": [
+ {
+ "user_id": "800506814562787328",
+ "platform_id": "1461830808796139662",
+ "weight": 2,
+ "created_at": "2026-01-17T23:36:06.34732Z",
+ "expires_at": "2026-01-18T11:36:06.34732Z"
+ },
+ {
+ "user_id": "316026718115037184",
+ "platform_id": "481068576363773972",
+ "weight": 2,
+ "created_at": "2026-02-20T05:43:58.392411Z",
+ "expires_at": "2026-02-20T17:43:58.392411Z"
+ },
+ {
+ "user_id": "794153497215045632",
+ "platform_id": "1425259851600101457",
+ "weight": 2,
+ "created_at": "2026-02-21T18:59:20.660734Z",
+ "expires_at": "2026-02-22T06:59:20.660734Z"
+ },
+ {
+ "user_id": "8226924471638491136",
+ "platform_id": "661200758510977084",
+ "weight": 1,
+ "created_at": "2026-02-25T22:35:36.978392Z",
+ "expires_at": "2026-02-26T10:35:36.978392Z"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Topgg.Sdk.Api/Tests/Mocks/PostCommands.json b/Topgg.Sdk.Api/Tests/Mocks/PostCommands.json
new file mode 100644
index 0000000..7284384
--- /dev/null
+++ b/Topgg.Sdk.Api/Tests/Mocks/PostCommands.json
@@ -0,0 +1,15 @@
+[
+ {
+ "options": [],
+ "name": "test",
+ "name_localizations": null,
+ "description": "command description",
+ "description_localizations": null,
+ "contexts": [],
+ "default_permission": null,
+ "default_member_permissions": null,
+ "dm_permission": false,
+ "integration_types": [],
+ "nsfw": false
+ }
+]
\ No newline at end of file
diff --git a/Topgg.Sdk.Api/Tests/Tests.cs b/Topgg.Sdk.Api/Tests/Tests.cs
new file mode 100644
index 0000000..f14f9bd
--- /dev/null
+++ b/Topgg.Sdk.Api/Tests/Tests.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Topgg.Sdk.Api.Data;
+using Xunit;
+
+namespace Topgg.Sdk.Api.Tests;
+
+public class Tests
+{
+ public static IEnumerable> UserSources => Enum.GetValues().Select(source => new TheoryDataRow(source));
+ public static IEnumerable> PlatformsAndProjectTypes => Enum.GetValues().SelectMany(platform => Enum.GetValues(), (platform, projectType) => new TheoryDataRow(platform, projectType));
+
+ private readonly TopggApi Client = new(new HttpClient(new Mock()));
+
+ [Fact]
+ public async Task GetSelfAsync() => await Client.GetSelfAsync();
+
+ [Fact]
+ public async Task PostCommandsAsync() => await Client.PostCommandsAsync(Mock.ReadJson("PostCommands"));
+
+ [Theory]
+ [MemberData(nameof(UserSources))]
+ public async Task GetVoteAsync(UserSource source)
+ {
+ await Client.GetVoteAsync(661200758510977084, source);
+ }
+
+ [Fact]
+ public async Task GetVotesAsync()
+ {
+ var since = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ var firstPage = await Client.GetVotesAsync(since);
+ await firstPage.Next();
+ }
+
+ [Theory]
+ [MemberData(nameof(PlatformsAndProjectTypes))]
+ public void Widgets(Platform platform, ProjectType projectType)
+ {
+ Widget.Large(platform, projectType, 123456);
+ Widget.Votes(platform, projectType, 123456);
+ Widget.Owner(platform, projectType, 123456);
+ Widget.Social(platform, projectType, 123456);
+ }
+}
\ No newline at end of file
diff --git a/Topgg.Sdk.Api/Topgg.Sdk.Api.csproj b/Topgg.Sdk.Api/Topgg.Sdk.Api.csproj
new file mode 100644
index 0000000..eccf4ec
--- /dev/null
+++ b/Topgg.Sdk.Api/Topgg.Sdk.Api.csproj
@@ -0,0 +1,41 @@
+
+
+ net8.0
+ Velddev, Faith, null8626
+ Top.gg
+ The community-maintained .NET SDK for the Top.gg API.
+ Mike Veldsink
+ https://github.com/Top-gg-Community/dotnet-sdk
+ https://github.com/Top-gg-Community/dotnet-sdk
+ git
+ discord bots topgg api
+ Adapt to Top.gg API v1
+ false
+ true
+ Topgg.Sdk.Api
+ Topgg.Sdk.Api
+ The .NET SDK for the Top.gg API
+ 1.0.0
+ LICENSE
+
+
+
+
+ True
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/Topgg.Sdk.Api/TopggApi.cs b/Topgg.Sdk.Api/TopggApi.cs
new file mode 100644
index 0000000..03d2cec
--- /dev/null
+++ b/Topgg.Sdk.Api/TopggApi.cs
@@ -0,0 +1,120 @@
+#nullable enable
+
+using Topgg.Sdk.Api.Data;
+using Topgg.Sdk.Api.Serialization;
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Json;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System.Text.Json.Serialization;
+
+namespace Topgg.Sdk.Api;
+
+/// Interact with Top.gg API v1's endpoints.
+public class TopggApi(HttpClient httpClient)
+{
+ internal static readonly string BaseURL = "https://top.gg/api/v1";
+ private readonly JsonSerializerOptions SerializerOptions = new()
+ {
+ Converters = { new ULongToStringConverter(), new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) },
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
+ };
+ private readonly HttpClient Http = httpClient;
+
+ /// Creates a new client instance.
+ /// The API token to use.
+ public TopggApi(string token) : this(new HttpClient()
+ {
+ DefaultRequestHeaders = { { "Authorization", $"Bearer {token}" } }
+ })
+ { }
+
+ /// Tries to get your project's information.
+ /// Your project's information.
+ public async Task GetSelfAsync() => (await GetAsync("/projects/@me"))!;
+
+ /// Tries to update the application commands list in your Discord bot's Top.gg page.
+ /// Serializable list of Discord application commands.
+ /// A list of your Discord bot's application commands in the form of Discord API's raw JSON format.
+ public async Task PostCommandsAsync(T commands) => await PostAsync("/projects/@me/commands", commands);
+
+ /// Tries to get the latest vote information of a user on your project. Returns null if the user has not voted.
+ /// The user's ID.
+ /// The user's source.
+ /// The latest vote information of a user on your project or null if the user has not voted.
+ public async Task GetVoteAsync(ulong id, UserSource source = UserSource.Discord)
+ {
+ try
+ {
+ return await GetAsync($"/projects/@me/votes/{id}?source={source.ToString().ToLower()}");
+ }
+ catch (HttpRequestException error)
+ {
+ if (error.StatusCode == HttpStatusCode.NotFound)
+ {
+ return null;
+ }
+
+ throw;
+ }
+ }
+
+
+ /// Tries to get a cursor-based paginated list of votes for your project, ordered by creation date.
+ /// The earliest possible date for all votes.
+ /// A cursor-based paginated list of votes for your project, ordered by creation date.
+ public async Task GetVotesAsync(DateTime since)
+ {
+ var votes = (await GetAsync($"/projects/@me/votes?startDate={Uri.EscapeDataString(since.ToString("yyyy-MM-ddTHH:mm:ss.fffK"))}"))!;
+
+ votes.Client = this;
+
+ return votes;
+ }
+
+ internal async Task GetVotesAsync(string cursor)
+ {
+ var votes = (await GetAsync($"/projects/@me/votes?cursor={cursor}"))!;
+
+ votes.Client = this;
+
+ return votes;
+ }
+
+ private async Task ProcessResponse(HttpResponseMessage response)
+ {
+ response.EnsureSuccessStatusCode();
+
+ if (typeof(T) == typeof(string))
+ {
+ return (T)(object)await response.Content.ReadAsStringAsync();
+ }
+ else
+ {
+ return await response.Content.ReadFromJsonAsync(SerializerOptions);
+ }
+ }
+
+ private async Task GetAsync(string url) => await ProcessResponse(await Http.GetAsync(BaseURL + url));
+
+ private async Task PostAsync(string url, B body)
+ {
+ StringContent httpContent;
+
+ if (typeof(B) == typeof(string))
+ {
+ httpContent = new StringContent((string)(object)body!, Encoding.UTF8, "application/json");
+ }
+ else
+ {
+ var json = JsonSerializer.Serialize(body);
+
+ httpContent = new StringContent(json, Encoding.UTF8, "application/json");
+ }
+
+ return await ProcessResponse(await Http.PostAsync(BaseURL + url, httpContent));
+ }
+}
\ No newline at end of file
diff --git a/Topgg.Sdk.Api/Widget.cs b/Topgg.Sdk.Api/Widget.cs
new file mode 100644
index 0000000..fb4fc00
--- /dev/null
+++ b/Topgg.Sdk.Api/Widget.cs
@@ -0,0 +1,34 @@
+using Topgg.Sdk.Api.Data;
+
+namespace Topgg.Sdk.Api;
+
+public static class Widget
+{
+ /// Generates a large widget URL.
+ /// The project's platform.
+ /// The project's type.
+ /// The project ID.
+ /// The widget URL.
+ public static string Large(Platform platform, ProjectType projectType, ulong id) => $"{TopggApi.BaseURL}/widgets/large/{platform.ToString().ToLower()}/{projectType.ToString().ToLower()}/{id}";
+
+ /// Generates a small widget URL for displaying votes.
+ /// The project's platform.
+ /// The project's type.
+ /// The project ID.
+ /// The widget URL.
+ public static string Votes(Platform platform, ProjectType projectType, ulong id) => $"{TopggApi.BaseURL}/widgets/small/votes/{platform.ToString().ToLower()}/{projectType.ToString().ToLower()}/{id}";
+
+ /// Generates a small widget URL for displaying a project's owner.
+ /// The project's platform.
+ /// The project's type.
+ /// The project ID.
+ /// The widget URL.
+ public static string Owner(Platform platform, ProjectType projectType, ulong id) => $"{TopggApi.BaseURL}/widgets/small/owner/{platform.ToString().ToLower()}/{projectType.ToString().ToLower()}/{id}";
+
+ /// Generates a small widget URL for displaying social stats.
+ /// The project's platform.
+ /// The project's type.
+ /// The project ID.
+ /// The widget URL.
+ public static string Social(Platform platform, ProjectType projectType, ulong id) => $"{TopggApi.BaseURL}/widgets/small/social/{platform.ToString().ToLower()}/{projectType.ToString().ToLower()}/{id}";
+}
\ No newline at end of file
diff --git a/DiscordBotsList.Api.sln b/Topgg.Sdk.sln
similarity index 58%
rename from DiscordBotsList.Api.sln
rename to Topgg.Sdk.sln
index 7209a51..1f9a667 100644
--- a/DiscordBotsList.Api.sln
+++ b/Topgg.Sdk.sln
@@ -1,37 +1,29 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2020
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordBotsList.Api", "DiscordBotsList.Api\DiscordBotsList.Api.csproj", "{B20E0634-86B7-4392-A40D-93B368A68158}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordBotsList.Api.Tests", "DiscordBotsList.Api.Tests\DiscordBotsList.Api.Tests.csproj", "{4A37AC0E-B9C5-49DB-B8C2-D701A37CA771}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiscordBotsList.Api.Adapter.Discord.Net", "DiscordBotsList.Api.Adapter.Discord.Net\DiscordBotsList.Api.Adapter.Discord.Net.csproj", "{0E4A5566-863E-402A-9359-49FC7900D4E8}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {B20E0634-86B7-4392-A40D-93B368A68158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B20E0634-86B7-4392-A40D-93B368A68158}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B20E0634-86B7-4392-A40D-93B368A68158}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B20E0634-86B7-4392-A40D-93B368A68158}.Release|Any CPU.Build.0 = Release|Any CPU
- {4A37AC0E-B9C5-49DB-B8C2-D701A37CA771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4A37AC0E-B9C5-49DB-B8C2-D701A37CA771}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4A37AC0E-B9C5-49DB-B8C2-D701A37CA771}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4A37AC0E-B9C5-49DB-B8C2-D701A37CA771}.Release|Any CPU.Build.0 = Release|Any CPU
- {0E4A5566-863E-402A-9359-49FC7900D4E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {0E4A5566-863E-402A-9359-49FC7900D4E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {0E4A5566-863E-402A-9359-49FC7900D4E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {0E4A5566-863E-402A-9359-49FC7900D4E8}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {B3317C2A-4530-4F3A-9A45-8D8DA228E8B5}
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27130.2020
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Topgg.Sdk.Api", "Topgg.Sdk.Api\Topgg.Sdk.Api.csproj", "{B20E0634-86B7-4392-A40D-93B368A68158}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B20E0634-86B7-4392-A40D-93B368A68158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B20E0634-86B7-4392-A40D-93B368A68158}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B20E0634-86B7-4392-A40D-93B368A68158}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B20E0634-86B7-4392-A40D-93B368A68158}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4A37AC0E-B9C5-49DB-B8C2-D701A37CA771}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4A37AC0E-B9C5-49DB-B8C2-D701A37CA771}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4A37AC0E-B9C5-49DB-B8C2-D701A37CA771}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4A37AC0E-B9C5-49DB-B8C2-D701A37CA771}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {B3317C2A-4530-4F3A-9A45-8D8DA228E8B5}
+ EndGlobalSection
+EndGlobal