diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
new file mode 100644
index 0000000..075a828
--- /dev/null
+++ b/.github/workflows/dotnet.yml
@@ -0,0 +1,23 @@
+name: .NET Tests
+
+on:
+ push:
+ branches: [ "*" ]
+ pull_request:
+ branches: [ "*" ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9.0.x
+ - name: Restore dependencies
+ run: dotnet restore
+ - name: Build
+ run: dotnet build --no-restore /p:TreatWarningsAsErrors=false
+ - name: Test
+ run: dotnet test --no-build --verbosity normal
\ No newline at end of file
diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml
new file mode 100644
index 0000000..af2b97d
--- /dev/null
+++ b/.github/workflows/nuget.yml
@@ -0,0 +1,48 @@
+name: CI/CD NuGet Package
+
+on:
+ release:
+ types: [created]
+
+env:
+ NUGET_SOURCE: https://api.nuget.org/v3/index.json
+ GITHUB_SOURCE: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json
+
+jobs:
+ build-and-publish:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: 9.0.x
+
+ - name: Extract Package Version
+ id: get_version
+ run: |
+ VERSION=${GITHUB_REF#refs/tags/v}
+ echo "PACKAGE_VERSION=$VERSION" >> $GITHUB_ENV
+ echo "Using version: $VERSION"
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build --no-restore --configuration Release
+
+ - name: Test
+ run: dotnet test --no-build --verbosity normal --configuration Release -e telegram_test_token=${{secrets.TELEGRAM_TEST_TOKEN}}
+
+ - name: Pack
+ run: dotnet pack --no-build --configuration Release -p:PackageVersion=${{ env.PACKAGE_VERSION }}
+ - name: Add GitHub source
+ run: dotnet nuget add source --username ${{ github.repository_owner }} --password ${{ secrets.GHCR_TOKEN }} --store-password-in-clear-text --name github ${{ env.GITHUB_SOURCE }}
+
+ - name: Publish to GitHub Packages
+ run: dotnet nuget push "**/*.nupkg" --source "github"
+
+ - name: Publish to NuGet
+ run: dotnet nuget push "**/*.nupkg" --source ${{ env.NUGET_SOURCE }} --api-key ${{ secrets.NUGET_API_KEY }}
\ No newline at end of file
diff --git a/DiscordConfig.cs b/DiscordConfig.cs
new file mode 100644
index 0000000..e317174
--- /dev/null
+++ b/DiscordConfig.cs
@@ -0,0 +1,11 @@
+namespace yawaflua.Discord.Net;
+
+public sealed class DiscordConfig
+{
+ public ulong ClientId { get; set; }
+ public string ClientSecret { get; set; } = string.Empty;
+ public string RedirectUri { get; set; } = string.Empty;
+ public ScopesBuilder Scope { get; set; }
+ public bool Prompt { get; set; } = true;
+ public string Token { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/DiscordOAuth.cs b/DiscordOAuth.cs
index 9a88f43..7c5c82d 100644
--- a/DiscordOAuth.cs
+++ b/DiscordOAuth.cs
@@ -1,36 +1,70 @@
using System.Collections.Specialized;
using System.Net.Http.Headers;
using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
using System.Web;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
-using Newtonsoft.Json;
-using x3rt.DiscordOAuth2.Entities;
-using x3rt.DiscordOAuth2.Options;
+using yawaflua.Discord.Net.Entities;
+using yawaflua.Discord.Net.Entities.Enums;
+using yawaflua.Discord.Net.Interfaces;
+using yawaflua.Discord.Net.Interfaces.Models;
+using ISession = yawaflua.Discord.Net.Interfaces.Models.ISession;
-namespace x3rt.DiscordOAuth2;
+namespace yawaflua.Discord.Net;
-public class DiscordOAuth
+public class DiscordOAuth : IDiscord
{
- private static ulong ClientId { get; set; }
- private static string ClientSecret { get; set; } = string.Empty;
- private static string? BotToken { get; set; }
+ private ulong ClientId { get; set; }
+ private string ClientSecret { get; set; } = string.Empty;
+ private string? BotToken { get; set; }
private string RedirectUri { get; set; }
private bool Prompt { get; set; }
private ScopesBuilder Scopes { get; set; }
private string? AccessToken { get; set; }
-
- public static void Configure(ulong clientId, string clientSecret, string? botToken = null)
+ private IToken? token { get; set; }
+
+ private readonly HttpClient _httpClient = new HttpClient();
+
+ ///
+ /// Now deprecated, use DiscordConfig instead.
+ ///
+ ///
+ ///
+ ///
+ [Obsolete]
+ public static DiscordOAuth Configure(ulong clientId, string clientSecret, string? botToken = null)
+ {
+ return new DiscordOAuth(clientId, clientSecret, string.Empty, new ScopesBuilder(OAuthScope.Identify))
+ {
+ BotToken = botToken
+ };
+ }
+
+ public DiscordOAuth(ulong clientId, string clientSecret, string redirectUri, ScopesBuilder scopes, string? botToken = null, bool prompt = true)
{
ClientId = clientId;
ClientSecret = clientSecret;
+ RedirectUri = redirectUri;
+ Scopes = scopes;
+ Prompt = prompt;
BotToken = botToken;
}
-
- private readonly HttpClient _httpClient = new HttpClient();
-
+
+ public DiscordOAuth(DiscordConfig config)
+ {
+ ClientId = config.ClientId;
+ ClientSecret = config.ClientSecret;
+ RedirectUri = config.RedirectUri;
+ Scopes = config.Scope;
+ Prompt = config.Prompt;
+ BotToken = config.Token;
+ }
+
+ [Obsolete]
public DiscordOAuth(string redirectUri, ScopesBuilder scopes, bool prompt = true)
{
RedirectUri = redirectUri;
@@ -38,30 +72,12 @@ public class DiscordOAuth
Prompt = prompt;
}
- public string GetAuthorizationUrl(string state)
- {
- NameValueCollection query = HttpUtility.ParseQueryString(string.Empty);
- query["client_id"] = ClientId.ToString();
- query["redirect_uri"] = RedirectUri;
- query["response_type"] = "code";
- query["scope"] = Scopes.ToString();
- query["state"] = state;
- query["prompt"] = Prompt ? "consent" : "none";
-
- var uriBuilder = new UriBuilder("https://discord.com/api/oauth2/authorize")
- {
- Query = query.ToString()
- };
-
- return uriBuilder.ToString();
- }
-
public static bool TryGetCode(HttpRequest request, out string? code)
{
code = null;
- if (request.Query.TryGetValue("code", out StringValues codeValues))
+ if (request.Query.TryGetValue("code", out var codeQuery))
{
- code = codeValues;
+ code = codeQuery;
return true;
}
@@ -70,12 +86,10 @@ public class DiscordOAuth
public static bool TryGetCode(HttpContext context, out string? code)
{
- var b = TryGetCode(context.Request, out var a);
- code = a;
- return b;
+ return TryGetCode(context.Request, out code);
}
- public async Task GetTokenAsync(string code)
+ public async Task GetTokenAsync(string code)
{
var content = new FormUrlEncodedContent(new Dictionary
{
@@ -89,135 +103,19 @@ public class DiscordOAuth
var response = await _httpClient.PostAsync("https://discord.com/api/oauth2/token", content);
var responseString = await response.Content.ReadAsStringAsync();
- var authToken = JsonConvert.DeserializeObject(responseString);
+ var authToken = JsonSerializer.Deserialize(responseString);
AccessToken = authToken?.AccessToken;
+ token = authToken;
return authToken;
}
- private async Task GetInformationAsync(string accessToken, string endpoint) where T : class
+ public ISession CreateSession()
{
- _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
- var response = await _httpClient.GetAsync($"https://discord.com/api/{endpoint}");
- if (!response.IsSuccessStatusCode)
- {
- return null;
- }
-
- var responseString = await response.Content.ReadAsStringAsync();
- return JsonConvert.DeserializeObject(responseString) ?? null;
+ if (token != null)
+ return new DiscordSession(token, _httpClient, Scopes, ClientId, ClientSecret, RedirectUri, Prompt);
+ else
+ throw new InvalidOperationException("Token is not set. Please call GetTokenAsync first.");
}
- private async Task GetInformationAsync(HttpContext context, string endpoint) where T : class
- {
- if (AccessToken is null)
- {
- if (!TryGetCode(context, out var code)) return null;
- var accessToken = await GetTokenAsync(code!);
- if (accessToken is null) return null;
- return await GetInformationAsync(accessToken.AccessToken, endpoint);
- }
- else
- {
- return await GetInformationAsync(AccessToken, endpoint);
- }
- }
-
- private async Task GetInformationAsync(OAuthToken token, string endpoint) where T : class
- {
- return await GetInformationAsync(token.AccessToken, endpoint);
- }
-
- public async Task GetUserAsync(string accessToken)
- {
- return await GetInformationAsync(accessToken, "users/@me");
- }
-
- public async Task GetUserAsync(HttpContext context)
- {
- return await GetInformationAsync(context, "users/@me");
- }
-
- public async Task GetUserAsync(OAuthToken token)
- {
- return await GetInformationAsync(token, "users/@me");
- }
-
- public async Task GetGuildsAsync(string accessToken)
- {
- return await GetInformationAsync(accessToken, "users/@me/guilds");
- }
-
- public async Task GetGuildsAsync(HttpContext context)
- {
- return await GetInformationAsync(context, "users/@me/guilds");
- }
-
- public async Task GetGuildsAsync(OAuthToken token)
- {
- return await GetInformationAsync(token, "users/@me/guilds");
- }
-
- public async Task GetConnectionsAsync(string accessToken)
- {
- return await GetInformationAsync(accessToken, "users/@me/connections");
- }
-
- public async Task GetConnectionsAsync(HttpContext context)
- {
- return await GetInformationAsync(context, "users/@me/connections");
- }
-
- public async Task GetConnectionsAsync(OAuthToken token)
- {
- return await GetInformationAsync(token, "users/@me/connections");
- }
-
- public async Task JoinGuildAsync(string accessToken, ulong userId, GuildOptions options)
- {
- if (BotToken is null) throw new InvalidOperationException("Bot token is not set");
- var request =
- new HttpRequestMessage(HttpMethod.Put,
- $"https://discord.com/api/guilds/{options.GuildId}/members/{userId}");
- request.Headers.Authorization = new AuthenticationHeaderValue("Bot", BotToken);
-
- var content = new Dictionary
- {
- ["access_token"] = accessToken
- };
- if (options.Nickname is not null) content["nick"] = options.Nickname;
- if (options.RoleIds is not null) content["roles"] = options.RoleIds;
- if (options.Muted is not null) content["mute"] = options.Muted;
- if (options.Deafened is not null) content["deaf"] = options.Deafened;
-
- var json = JsonConvert.SerializeObject(content);
- request.Content = new StringContent(json, Encoding.UTF8, "application/json");
- var response = await _httpClient.SendAsync(request);
- return response.IsSuccessStatusCode;
- }
-
- public async Task JoinGuildAsync(HttpContext context, GuildOptions options)
- {
- if (AccessToken is null)
- {
- if (!TryGetCode(context, out var code)) return false;
- var accessToken = await GetTokenAsync(code!);
- if (accessToken is null) return false;
- var user = await GetUserAsync(accessToken.AccessToken);
- if (user is null) return false;
- return await JoinGuildAsync(accessToken.AccessToken, user.Id, options);
- }
- else
- {
- var user = await GetUserAsync(AccessToken);
- if (user is null) return false;
- return await JoinGuildAsync(AccessToken, user.Id, options);
- }
- }
-
- public async Task JoinGuildAsync(OAuthToken token, GuildOptions options)
- {
- var user = await GetUserAsync(token.AccessToken);
- if (user is null) return false;
- return await JoinGuildAsync(token.AccessToken, user.Id, options);
- }
+
}
\ No newline at end of file
diff --git a/Entities/AvatarDecoration.cs b/Entities/AvatarDecoration.cs
new file mode 100644
index 0000000..7b88e96
--- /dev/null
+++ b/Entities/AvatarDecoration.cs
@@ -0,0 +1,12 @@
+using System.Text.Json.Serialization;
+using yawaflua.Discord.Net.Interfaces.Models;
+
+namespace yawaflua.Discord.Net.Entities;
+
+internal class AvatarDecoration : IAvatarDecoration
+{
+ [JsonPropertyName("asset")]
+ public string AssetHash { get; set; }
+ [JsonPropertyName("sku_id")]
+ public ulong AssetArticular { get; set; }
+}
\ No newline at end of file
diff --git a/Entities/DiscordConnection.cs b/Entities/DiscordConnection.cs
index 47e80ce..497fe02 100644
--- a/Entities/DiscordConnection.cs
+++ b/Entities/DiscordConnection.cs
@@ -1,48 +1,19 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using yawaflua.Discord.Net.Entities.Enums;
+using yawaflua.Discord.Net.Interfaces.Models;
-namespace x3rt.DiscordOAuth2.Entities;
+namespace yawaflua.Discord.Net.Entities;
-public class DiscordConnection
+internal class DiscordConnection : IConnection
{
- [JsonProperty("id")] public string Id { get; set; }
- [JsonProperty("name")] public string Name { get; set; }
- [JsonProperty("type")] public ConnectionType Type { get; set; }
- [JsonProperty("revoked")] public bool? Revoked { get; set; }
- [JsonProperty("integrations")] public object[] Integrations { get; set; }
- [JsonProperty("verified")] public bool? Verified { get; set; }
- [JsonProperty("friend_sync")] public bool? FriendSync { get; set; }
- [JsonProperty("show_activity")] public bool? ShowActivity { get; set; }
- [JsonProperty("two_way_link")] public bool? TwoWayLink { get; set; }
- [JsonProperty("visibility")] public ConnectionVisibility? Visibility { get; set; }
-
-
- public enum ConnectionVisibility
- {
- None,
- Everyone
- }
-
-
- public enum ConnectionType
- {
- BattleNet,
- Ebay,
- EpicGames,
- Facebook,
- GitHub,
- Instagram,
- LeagueOfLegends,
- PayPal,
- PlayStation,
- Reddit,
- RiotGames,
- Spotify,
- Skype,
- Steam,
- TikTok,
- Twitch,
- Twitter,
- Xbox,
- YouTube
- }
+ [JsonPropertyName("id")] public string Id { get; set; }
+ [JsonPropertyName("name")] public string Name { get; set; }
+ [JsonPropertyName("type")] public ConnectionType Type { get; set; }
+ [JsonPropertyName("revoked")] public bool? Revoked { get; set; }
+ [JsonPropertyName("integrations")] public object[] Integrations { get; set; }
+ [JsonPropertyName("verified")] public bool? Verified { get; set; }
+ [JsonPropertyName("friend_sync")] public bool? FriendSync { get; set; }
+ [JsonPropertyName("show_activity")] public bool? ShowActivity { get; set; }
+ [JsonPropertyName("two_way_link")] public bool? TwoWayLink { get; set; }
+ [JsonPropertyName("visibility")] public ConnectionVisibility? Visibility { get; set; }
}
\ No newline at end of file
diff --git a/Entities/DiscordGuild.cs b/Entities/DiscordGuild.cs
index 0060d1f..38763d4 100644
--- a/Entities/DiscordGuild.cs
+++ b/Entities/DiscordGuild.cs
@@ -1,24 +1,45 @@
-using Newtonsoft.Json;
+using System.Text.Json.Serialization;
+using yawaflua.Discord.Net.Entities.Enums;
+using yawaflua.Discord.Net.Interfaces.Models;
-namespace x3rt.DiscordOAuth2.Entities;
+namespace yawaflua.Discord.Net.Entities;
-public class DiscordGuild
+internal class DiscordGuild : IGuild
{
- [JsonProperty("id")] public ulong Id { get; set; }
+ [JsonPropertyName("id")] public ulong Id { get; set; }
- [JsonProperty("name")] public string Name { get; set; }
+ [JsonPropertyName("name")] public string Name { get; set; }
- [JsonProperty("icon")] public string? Icon { get; set; }
+ [JsonPropertyName("icon")] public string? IconHash { get; set; }
+ [JsonPropertyName("banner")] public string? BannerHash { get; set; }
- [JsonProperty("owner")] public bool Owner { get; set; }
+ [JsonPropertyName("owner")] public bool IsOwner { get; set; }
- [JsonProperty("permissions")] public string Permissions { get; set; }
+ [JsonPropertyName("permissions")] public string Permissions { get; set; }
- [JsonProperty("features")] public GuildFeatures Features { get; set; }
+ [JsonPropertyName("features")] public IEnumerable Features { get; set; } = [];
+
+ [JsonPropertyName("approximate_member_count")] public int ApproximateMemberCount { get; set; }
- public override string ToString()
+ [JsonPropertyName("approximate_presence_count")] public int ApproximatePresenceCount { get; set; }
+
+ public string GetIconUrl(int size = 128)
{
- return
- $"Id: {Id}; Name: {Name}; Icon: {Icon}; Owner: {Owner}; Permissions: {Permissions}; Features: {Features}";
+ if (string.IsNullOrEmpty(IconHash))
+ {
+ return string.Empty;
+ }
+
+ return $"https://cdn.discordapp.com/icons/{Id}/{IconHash}.png?size={size}";
+ }
+
+ public string GetBannerUrl(int size = 128)
+ {
+ if (string.IsNullOrEmpty(BannerHash))
+ {
+ return string.Empty;
+ }
+
+ return $"https://cdn.discordapp.com/banners/{Id}/{BannerHash}.png?size={size}";
}
}
\ No newline at end of file
diff --git a/Entities/DiscordGuildMember.cs b/Entities/DiscordGuildMember.cs
new file mode 100644
index 0000000..bb6d659
--- /dev/null
+++ b/Entities/DiscordGuildMember.cs
@@ -0,0 +1,39 @@
+using System.Text.Json.Serialization;
+using yawaflua.Discord.Net.Interfaces.Models;
+
+namespace yawaflua.Discord.Net.Entities;
+
+internal class DiscordGuildMember : IGuildMember
+{
+ private DiscordUser User { get; set; }
+ public string? Nick { get; set; }
+ [JsonPropertyName("avatar")]
+ public string? AvatarHash { get; set; }
+ [JsonPropertyName("banner")]
+ public string? BannerHash { get; set; }
+ public List Roles { get; set; }
+ public DateTime JoinedAt { get; set; }
+ public DateTime? PremiumSince { get; set; }
+ [JsonPropertyName("deaf")]
+ public bool IsDeaf { get; set; }
+ [JsonPropertyName("mute")]
+ public bool IsMute { get; set; }
+ public int Flags { get; set; }
+ [JsonPropertyName("pending")]
+ public bool IsPending { get; set; }
+ [JsonPropertyName("communication_disabled_until")]
+ public DateTime? CommunicationDisabledUntil { get; set; }
+ private AvatarDecoration? AvatarDecoration { get; set; }
+
+ IUser IGuildMember.User
+ {
+ get => User;
+ set => User = value as DiscordUser;
+ }
+
+ IAvatarDecoration IGuildMember.AvatarDecoration
+ {
+ get => AvatarDecoration;
+ set => AvatarDecoration = value as AvatarDecoration;
+ }
+}
\ No newline at end of file
diff --git a/Entities/DiscordRole.cs b/Entities/DiscordRole.cs
new file mode 100644
index 0000000..c5a06f5
--- /dev/null
+++ b/Entities/DiscordRole.cs
@@ -0,0 +1,26 @@
+using yawaflua.Discord.Net.Entities.Enums;
+using yawaflua.Discord.Net.Interfaces.Models;
+
+namespace yawaflua.Discord.Net.Entities;
+
+internal class DiscordRole : IRole
+{
+ public ulong Id { get; set; }
+ public string Name { get; set; }
+ public int Color { get; set; }
+ private RoleColor Colors { get; set; }
+ public bool IsHoisted { get; set; }
+ public bool IsManaged { get; set; }
+ public bool IsMentionable { get; set; }
+ public int Position { get; set; }
+ public ulong? Permissions { get; set; }
+ public string? IconHash { get; set; }
+ public string? UnicodeEmoji { get; set; }
+ public Dictionary? Tags { get; set; }
+
+ IRoleColor IRole.Colors
+ {
+ get => Colors;
+ set => Colors = (RoleColor)value;
+ }
+}
\ No newline at end of file
diff --git a/Entities/DiscordSession.cs b/Entities/DiscordSession.cs
new file mode 100644
index 0000000..c98f4e1
--- /dev/null
+++ b/Entities/DiscordSession.cs
@@ -0,0 +1,103 @@
+using System.Collections.Specialized;
+using System.Net.Http.Headers;
+using System.Text.Json;
+using yawaflua.Discord.Net.Interfaces.Models;
+
+namespace yawaflua.Discord.Net.Entities;
+
+internal class DiscordSession (IToken token, HttpClient httpClient, ScopesBuilder scopes, ulong clientId, string clientSecret, string redirectUri, bool prompt) : ISession
+{
+ private async Task _req(string endpoint, HttpMethod? method = null) where T : class
+ {
+ using var request = new HttpRequestMessage(method ?? HttpMethod.Get, $"https://discord.com/api/{endpoint}");
+ request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken);
+ var response = await httpClient.SendAsync(request);
+ if (!response.IsSuccessStatusCode)
+ {
+ return null;
+ }
+
+ var responseString = await response.Content.ReadAsStringAsync();
+ return JsonSerializer.Deserialize(responseString) ?? null;
+ }
+
+ public async Task?> GetGuildsAsync(CancellationToken cancellationToken = default)
+ {
+ if (token.AccessToken is null)
+ {
+ throw new ArgumentNullException(nameof(token), "Token cannot be null.");
+ }
+
+ return await _req("users/@me/guilds");
+ }
+
+ public async Task GetGuildMemberAsync(ulong guildId, CancellationToken cancellationToken = default)
+ {
+ if (token.AccessToken is null)
+ {
+ throw new ArgumentNullException(nameof(token), "Token cannot be null.");
+ }
+
+ return await _req($"users/@me/guilds/{guildId}/member");
+ }
+
+ public async Task AddMemberToGuildAsync(ulong guildId, ulong userId, CancellationToken cancellationToken = default)
+ {
+ if (token.AccessToken is null)
+ {
+ throw new ArgumentNullException(nameof(token), "Token cannot be null.");
+ }
+
+ return await _req($"guilds/{guildId}/members/{userId}", HttpMethod.Put);
+ }
+
+ public async Task GetCurrentUserAsync(CancellationToken cancellationToken = default)
+ {
+ if (token.AccessToken is null)
+ {
+ throw new ArgumentNullException(nameof(token), "Token cannot be null.");
+ }
+
+ return await _req("users/@me");
+ }
+
+ public async Task GetConnectionAsync(CancellationToken cancellationToken = default)
+ {
+ if (token.AccessToken is null)
+ {
+ throw new ArgumentNullException(nameof(token), "Token cannot be null.");
+ }
+ return await _req("users/@me/connections");
+ }
+
+ public string GetAuthorizationUrl(string state)
+ {
+
+ NameValueCollection query = new()
+ {
+ ["client_id"] = clientId.ToString(),
+ ["redirect_uri"] = redirectUri,
+ ["response_type"] = "code",
+ ["scope"] = scopes.ToString(),
+ ["state"] = state,
+ ["prompt"] = prompt ? "consent" : "none"
+ };
+
+ var uriBuilder = new UriBuilder("https://discord.com/api/oauth2/authorize")
+ {
+ Query = query.ToString()
+ };
+
+ return uriBuilder.ToString();
+
+ }
+
+ public IToken GetToken(CancellationToken cancellationToken = default)
+ {
+ if (token.AccessToken is null)
+ {
+ throw new ArgumentNullException(nameof(token), "Token cannot be null.");
+ }
+ return token;
+ }
+}
\ No newline at end of file
diff --git a/Entities/DiscordUser.cs b/Entities/DiscordUser.cs
index 3ae62dd..8f798df 100644
--- a/Entities/DiscordUser.cs
+++ b/Entities/DiscordUser.cs
@@ -1,38 +1,85 @@
-using x3rt.DiscordOAuth2.Entities.Enums;
+using System.Text.Json.Serialization;
+using yawaflua.Discord.Net.Entities.Enums;
+using yawaflua.Discord.Net.Interfaces.Models;
-namespace x3rt.DiscordOAuth2.Entities;
+namespace yawaflua.Discord.Net.Entities;
-public class DiscordUser
+internal class DiscordUser : IUser
{
+ [JsonPropertyName("id")]
public ulong Id { get; set; }
+
+ [JsonPropertyName("username")]
public string Username { get; set; }
+
+ [JsonPropertyName("global_name")]
+ public string GlobalName { get; set; }
+
+ [JsonPropertyName("discriminator")]
public string Discriminator { get; set; }
- public string? Avatar { get; set; }
+
+ [JsonPropertyName("avatar")]
+ public string? AvatarHash { get; set; }
+
+ [JsonPropertyName("bot")]
public bool? Bot { get; set; }
+
+ [JsonPropertyName("system")]
public bool? System { get; set; }
+
+ [JsonPropertyName("mfa_enabled")]
public bool? MfaEnabled { get; set; }
+
+ [JsonPropertyName("banner")]
public string? Banner { get; set; }
+
+ [JsonPropertyName("accent_color")]
public int? AccentColor { get; set; }
+
+ [JsonPropertyName("locale")]
public string? Locale { get; set; }
+
+ [JsonPropertyName("verified")]
public bool? Verified { get; set; }
+
+ [JsonPropertyName("email")]
public string? Email { get; set; }
+
+ [JsonPropertyName("flags")]
public UserFlag? Flags { get; set; }
+
+ [JsonPropertyName("premium_type")]
public PremiumType? PremiumType { get; set; }
+
+ [JsonPropertyName("public_flags")]
public UserFlag? PublicFlags { get; set; }
+
+ [JsonPropertyName("avatar_decoration")]
+ public AvatarDecoration? AvatarDecoration { get; set; }
- public override string ToString()
+ IAvatarDecoration IUser.AvatarDecoration
{
- string result = "";
- foreach (var property in GetType().GetProperties())
+ get => AvatarDecoration;
+ set => AvatarDecoration = value as AvatarDecoration;
+ }
+
+ public string GetAvatarUrl(int size = 128)
+ {
+ if (string.IsNullOrEmpty(AvatarHash))
{
- var value = property.GetValue(this);
- if (value is not null)
- {
- result += $"{property.Name}: {value}; ";
- }
+ return string.Empty;
}
- result = result.TrimEnd(' ', ';');
- return result;
+ return $"https://cdn.discordapp.com/avatars/{Id}/{AvatarHash}.png?size={size}";
+ }
+
+ public string GetBannerUrl(int size = 128)
+ {
+ if (string.IsNullOrEmpty(AvatarHash))
+ {
+ return string.Empty;
+ }
+
+ return $"https://cdn.discordapp.com/banners/{Id}/{AvatarHash}.png?size={size}";
}
}
\ No newline at end of file
diff --git a/Entities/Enums/ConnectionType.cs b/Entities/Enums/ConnectionType.cs
new file mode 100644
index 0000000..a6fa50a
--- /dev/null
+++ b/Entities/Enums/ConnectionType.cs
@@ -0,0 +1,24 @@
+namespace yawaflua.Discord.Net.Entities.Enums;
+
+public enum ConnectionType
+{
+ BattleNet,
+ Ebay,
+ EpicGames,
+ Facebook,
+ GitHub,
+ Instagram,
+ LeagueOfLegends,
+ PayPal,
+ PlayStation,
+ Reddit,
+ RiotGames,
+ Spotify,
+ Skype,
+ Steam,
+ TikTok,
+ Twitch,
+ Twitter,
+ Xbox,
+ YouTube
+}
\ No newline at end of file
diff --git a/Entities/Enums/ConnectionVisibility.cs b/Entities/Enums/ConnectionVisibility.cs
new file mode 100644
index 0000000..c67be24
--- /dev/null
+++ b/Entities/Enums/ConnectionVisibility.cs
@@ -0,0 +1,7 @@
+namespace yawaflua.Discord.Net.Entities.Enums;
+
+public enum ConnectionVisibility
+{
+ None,
+ Everyone
+}
\ No newline at end of file
diff --git a/Entities/Enums/GuildFeature.cs b/Entities/Enums/GuildFeature.cs
index 32cca01..9f0b50f 100644
--- a/Entities/Enums/GuildFeature.cs
+++ b/Entities/Enums/GuildFeature.cs
@@ -1,4 +1,4 @@
-namespace x3rt.DiscordOAuth2.Entities.Enums;
+namespace yawaflua.Discord.Net.Entities.Enums;
[Flags]
// Credit: Discord.Net
diff --git a/Entities/Enums/OAuthScope.cs b/Entities/Enums/OAuthScope.cs
index 2d37eb5..9db9aae 100644
--- a/Entities/Enums/OAuthScope.cs
+++ b/Entities/Enums/OAuthScope.cs
@@ -1,4 +1,4 @@
-namespace x3rt.DiscordOAuth2.Entities.Enums;
+namespace yawaflua.Discord.Net.Entities.Enums;
///
/// Represents the OAuth2 scopes available for a Discord application.
diff --git a/Entities/Enums/PremiumType.cs b/Entities/Enums/PremiumType.cs
index 3e0cfb5..b656329 100644
--- a/Entities/Enums/PremiumType.cs
+++ b/Entities/Enums/PremiumType.cs
@@ -1,4 +1,4 @@
-namespace x3rt.DiscordOAuth2.Entities.Enums;
+namespace yawaflua.Discord.Net.Entities.Enums;
public enum PremiumType
{
diff --git a/Entities/Enums/RoleTags.cs b/Entities/Enums/RoleTags.cs
new file mode 100644
index 0000000..5173ed9
--- /dev/null
+++ b/Entities/Enums/RoleTags.cs
@@ -0,0 +1,11 @@
+namespace yawaflua.Discord.Net.Entities.Enums;
+
+public enum RoleTags
+{
+ bot_id,
+ integration_id,
+ premium_subscriber,
+ subscription_listing_id,
+ available_for_purchase,
+ guild_connections,
+}
\ No newline at end of file
diff --git a/Entities/Enums/UserFlag.cs b/Entities/Enums/UserFlag.cs
index 6f2cb9f..1d72d27 100644
--- a/Entities/Enums/UserFlag.cs
+++ b/Entities/Enums/UserFlag.cs
@@ -1,4 +1,4 @@
-namespace x3rt.DiscordOAuth2.Entities.Enums;
+namespace yawaflua.Discord.Net.Entities.Enums;
[Flags]
public enum UserFlag : ulong
diff --git a/Entities/GuildFeatures.cs b/Entities/GuildFeatures.cs
deleted file mode 100644
index 92b154f..0000000
--- a/Entities/GuildFeatures.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using x3rt.DiscordOAuth2.Entities.Enums;
-
-namespace x3rt.DiscordOAuth2.Entities;
-
-public record GuildFeatures
-{
- IEnumerable Features { get; set; }
-
- public override string ToString()
- {
- return string.Join(", ", Features);
- }
-}
\ No newline at end of file
diff --git a/Entities/OAuthToken.cs b/Entities/OAuthToken.cs
index 9252d8a..ae76381 100644
--- a/Entities/OAuthToken.cs
+++ b/Entities/OAuthToken.cs
@@ -1,16 +1,69 @@
-using Newtonsoft.Json;
+using System.Net.Http.Headers;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using yawaflua.Discord.Net.Interfaces.Models;
-namespace x3rt.DiscordOAuth2.Entities;
+namespace yawaflua.Discord.Net.Entities;
-public class OAuthToken
+internal class OAuthToken (HttpClient client, ulong ClientId, string ClientSecret) : IToken
{
- [JsonProperty("access_token")] public string AccessToken { get; set; }
+ [JsonPropertyName("access_token")] public string AccessToken { get; set; }
- [JsonProperty("expires_in")] public int ExpiresIn { get; set; }
+ [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; }
- [JsonProperty("refresh_token")] public string RefreshToken { get; set; }
+ [JsonPropertyName("refresh_token")] public string RefreshToken { get; set; }
- [JsonProperty("scope")] public string Scope { get; set; }
+ [JsonPropertyName("scope")] public string Scope { get; set; }
- [JsonProperty("token_type")] public string TokenType { get; set; }
+ [JsonPropertyName("token_type")] public string TokenType { get; set; }
+ public Task RevokeAsync(CancellationToken cancellationToken = default)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Post, "https://discord.com/api/oauth2/token/revoke")
+ {
+ Content = new FormUrlEncodedContent(new Dictionary
+ {
+ { "token", AccessToken },
+ { "client_id", ClientId.ToString() },
+ { "client_secret", ClientSecret }
+ })
+ };
+ return client.SendAsync(request, cancellationToken)
+ .ContinueWith(task =>
+ {
+ if (!task.Result.IsSuccessStatusCode)
+ {
+ throw new Exception("Failed to revoke token.");
+ }
+ }, cancellationToken);
+ }
+
+ public async Task RefreshAsync(CancellationToken cancellationToken = default)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Post, "https://discord.com/api/oauth2/token")
+ {
+ Content = new FormUrlEncodedContent(new Dictionary
+ {
+ { "grant_type", "refresh_token" },
+ { "refresh_token", RefreshToken },
+ })
+ };
+ var byteArray = System.Text.Encoding.ASCII.GetBytes($"{ClientId}:{ClientSecret}");
+ request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
+ var req = await client.SendAsync(request, cancellationToken);
+ if (!req.IsSuccessStatusCode)
+ {
+ throw new Exception("Failed to refresh token.");
+ }
+ var responseString = await req.Content.ReadAsStringAsync();
+ var newToken = JsonSerializer.Deserialize(responseString);
+ if (newToken is null)
+ {
+ throw new Exception("Failed to deserialize token.");
+ }
+ AccessToken = newToken.AccessToken;
+ ExpiresIn = newToken.ExpiresIn;
+ RefreshToken = newToken.RefreshToken;
+ Scope = newToken.Scope;
+ TokenType = newToken.TokenType;
+ }
}
\ No newline at end of file
diff --git a/Entities/RoleColor.cs b/Entities/RoleColor.cs
new file mode 100644
index 0000000..c65b96e
--- /dev/null
+++ b/Entities/RoleColor.cs
@@ -0,0 +1,10 @@
+using yawaflua.Discord.Net.Interfaces.Models;
+
+namespace yawaflua.Discord.Net.Entities;
+
+internal class RoleColor : IRoleColor
+{
+ public int Primary { get; set; }
+ public int? Secondary { get; set; }
+ public int? Tertiary { get; set; }
+}
\ No newline at end of file
diff --git a/Interfaces/IDiscord.cs b/Interfaces/IDiscord.cs
new file mode 100644
index 0000000..ae70bc1
--- /dev/null
+++ b/Interfaces/IDiscord.cs
@@ -0,0 +1,9 @@
+using yawaflua.Discord.Net.Interfaces.Models;
+
+namespace yawaflua.Discord.Net.Interfaces;
+
+public interface IDiscord
+{
+ Task GetTokenAsync(string code);
+ ISession? CreateSession();
+}
\ No newline at end of file
diff --git a/Interfaces/Models/IAvatarDecoration.cs b/Interfaces/Models/IAvatarDecoration.cs
new file mode 100644
index 0000000..0e32f3f
--- /dev/null
+++ b/Interfaces/Models/IAvatarDecoration.cs
@@ -0,0 +1,7 @@
+namespace yawaflua.Discord.Net.Interfaces.Models;
+
+public interface IAvatarDecoration
+{
+ public string AssetHash { get; set; }
+ public ulong AssetArticular { get; set; }
+}
\ No newline at end of file
diff --git a/Interfaces/Models/IConnection.cs b/Interfaces/Models/IConnection.cs
new file mode 100644
index 0000000..bc83b4b
--- /dev/null
+++ b/Interfaces/Models/IConnection.cs
@@ -0,0 +1,17 @@
+using yawaflua.Discord.Net.Entities.Enums;
+
+namespace yawaflua.Discord.Net.Interfaces.Models;
+
+public interface IConnection
+{
+ public string Id { get; set; }
+ public string Name { get; set; }
+ public ConnectionType Type { get; set; }
+ public bool? Revoked { get; set; }
+ public object[] Integrations { get; set; }
+ public bool? Verified { get; set; }
+ public bool? FriendSync { get; set; }
+ public bool? ShowActivity { get; set; }
+ public bool? TwoWayLink { get; set; }
+ public ConnectionVisibility? Visibility { get; set; }
+}
\ No newline at end of file
diff --git a/Interfaces/Models/IGuild.cs b/Interfaces/Models/IGuild.cs
new file mode 100644
index 0000000..6560b42
--- /dev/null
+++ b/Interfaces/Models/IGuild.cs
@@ -0,0 +1,19 @@
+using yawaflua.Discord.Net.Entities.Enums;
+
+namespace yawaflua.Discord.Net.Interfaces.Models;
+
+public interface IGuild
+{
+ public ulong Id { get; set; }
+ public string Name { get; set; }
+ public string? IconHash { get; set; }
+ public string? BannerHash { get; set; }
+ public bool IsOwner { get; set; }
+ public string Permissions { get; set; }
+ public IEnumerable Features { get; set; }
+ public int ApproximateMemberCount { get; set; }
+ public int ApproximatePresenceCount { get; set; }
+
+ public string GetIconUrl(int size = 128);
+ public string GetBannerUrl(int size = 128);
+}
\ No newline at end of file
diff --git a/Interfaces/Models/IGuildMember.cs b/Interfaces/Models/IGuildMember.cs
new file mode 100644
index 0000000..f62a00c
--- /dev/null
+++ b/Interfaces/Models/IGuildMember.cs
@@ -0,0 +1,18 @@
+namespace yawaflua.Discord.Net.Interfaces.Models;
+
+public interface IGuildMember
+{
+ public IUser User { get; set; }
+ public string? Nick { get; set; }
+ public string? AvatarHash { get; set; }
+ public string? BannerHash { get; set; }
+ public List Roles { get; set; }
+ public DateTime JoinedAt { get; set; }
+ public DateTime? PremiumSince { get; set; }
+ public bool IsDeaf { get; set; }
+ public bool IsMute { get; set; }
+ public int Flags { get; set; }
+ public bool IsPending { get; set; }
+ public DateTime? CommunicationDisabledUntil { get; set; }
+ public IAvatarDecoration? AvatarDecoration { get; set; }
+}
\ No newline at end of file
diff --git a/Interfaces/Models/IRole.cs b/Interfaces/Models/IRole.cs
new file mode 100644
index 0000000..1064836
--- /dev/null
+++ b/Interfaces/Models/IRole.cs
@@ -0,0 +1,25 @@
+using yawaflua.Discord.Net.Entities.Enums;
+
+namespace yawaflua.Discord.Net.Interfaces.Models;
+
+public interface IRole
+{
+ public ulong Id { get; set; }
+ public string Name { get; set; }
+ [Obsolete("Deprecated integer representation of hexadecimal color code")]
+ public int Color { get; set; }
+
+ public IRoleColor Colors { get; set; }
+
+ public bool IsHoisted { get; set; }
+ public bool IsManaged { get; set; }
+ public bool IsMentionable { get; set; }
+
+ public int Position { get; set; }
+ public ulong? Permissions { get; set; }
+
+ public string? IconHash { get; set; }
+ public string? UnicodeEmoji { get; set; }
+
+ public Dictionary? Tags { get; set; }
+}
\ No newline at end of file
diff --git a/Interfaces/Models/IRoleColor.cs b/Interfaces/Models/IRoleColor.cs
new file mode 100644
index 0000000..876911c
--- /dev/null
+++ b/Interfaces/Models/IRoleColor.cs
@@ -0,0 +1,8 @@
+namespace yawaflua.Discord.Net.Interfaces.Models;
+
+public interface IRoleColor
+{
+ public int Primary { get; set; }
+ public int? Secondary { get; set; }
+ public int? Tertiary { get; set; }
+}
\ No newline at end of file
diff --git a/Interfaces/Models/ISession.cs b/Interfaces/Models/ISession.cs
new file mode 100644
index 0000000..58f57f0
--- /dev/null
+++ b/Interfaces/Models/ISession.cs
@@ -0,0 +1,16 @@
+namespace yawaflua.Discord.Net.Interfaces.Models;
+
+public interface ISession
+{
+ Task?> GetGuildsAsync(CancellationToken cancellationToken = default);
+ Task GetGuildMemberAsync(ulong guildId, CancellationToken cancellationToken = default);
+ Task AddMemberToGuildAsync(ulong guildId, ulong userId, CancellationToken cancellationToken = default);
+ Task GetCurrentUserAsync(CancellationToken cancellationToken = default);
+
+ Task GetConnectionAsync(CancellationToken cancellationToken = default);
+
+
+ string GetAuthorizationUrl(string state);
+ IToken GetToken(CancellationToken cancellationToken = default);
+
+}
\ No newline at end of file
diff --git a/Interfaces/Models/IToken.cs b/Interfaces/Models/IToken.cs
new file mode 100644
index 0000000..c922745
--- /dev/null
+++ b/Interfaces/Models/IToken.cs
@@ -0,0 +1,14 @@
+namespace yawaflua.Discord.Net.Interfaces.Models;
+
+public interface IToken
+{
+ public string? AccessToken { get; set; }
+ public int ExpiresIn { get; set; }
+ public string? RefreshToken { get; set; }
+ public string? Scope { get; set; }
+ public string? TokenType { get; set; }
+
+ Task RevokeAsync(CancellationToken cancellationToken = default);
+ Task RefreshAsync(CancellationToken cancellationToken = default);
+
+}
\ No newline at end of file
diff --git a/Interfaces/Models/IUser.cs b/Interfaces/Models/IUser.cs
new file mode 100644
index 0000000..2a7b6d2
--- /dev/null
+++ b/Interfaces/Models/IUser.cs
@@ -0,0 +1,115 @@
+using yawaflua.Discord.Net.Entities.Enums;
+
+namespace yawaflua.Discord.Net.Interfaces.Models;
+
+public interface IUser
+{
+ ///
+ /// the user's id
+ ///
+ /// User-object
+ public ulong Id { get; set; }
+
+ ///
+ /// the user's username, not unique across the platform
+ ///
+ /// User-object
+ public string Username { get; set; }
+
+ ///
+ /// the user's display name, if it is set. For bots, this is the application name
+ ///
+ public string GlobalName { get; set; }
+
+ ///
+ /// the user's Discord-tag (Obsolete)
+ ///
+ /// User-object
+ public string Discriminator { get; set; }
+
+ ///
+ /// the user's avatar hash
+ ///
+ /// Image formatting
+ /// User-object
+ public string? AvatarHash { get; set; }
+
+ ///
+ /// the user's banner hash
+ ///
+ /// Image formatting
+ /// User-object
+ /// Available in the User object only if the user has a banner set.
+ /// https://cdn.discordapp.com/banners/{user.id}/{banner}.png?size=512
+ public string? Banner { get; set; }
+
+ ///
+ /// whether the user belongs to an OAuth2 application
+ ///
+ /// User-object
+ public bool? Bot { get; set; }
+
+ ///
+ /// whether the user is an Official Discord System user (part of the urgent message system)
+ ///
+ /// User-object
+ public bool? System { get; set; }
+
+ ///
+ /// whether the user has two factor enabled on their account
+ ///
+ /// User-object
+ public bool? MfaEnabled { get; set; }
+
+
+ ///
+ /// the user's banner color encoded as an integer representation of hexadecimal color code
+ ///
+ /// User-object
+ public int? AccentColor { get; set; }
+
+ ///
+ /// the user's chosen language option
+ ///
+ /// User-object
+ public string? Locale { get; set; }
+
+ ///
+ /// whether the email on this account has been verified
+ ///
+ /// User-object
+ public bool? Verified { get; set; }
+ ///
+ /// the user's email
+ ///
+ /// User-object
+ public string? Email { get; set; }
+
+ ///
+ /// the flags on a user's account
+ ///
+ /// User-object
+ /// User flags
+ public UserFlag? Flags { get; set; }
+
+ ///
+ /// the type of Nitro subscription on a user's account
+ ///
+ /// User-object
+ public PremiumType? PremiumType { get; set; }
+
+ ///
+ /// the public flags on a user's account
+ ///
+ /// User-object
+ public UserFlag? PublicFlags { get; set; }
+
+ ///
+ /// data for the user's avatar decoration
+ ///
+ /// User-object
+ public IAvatarDecoration? AvatarDecoration { get; set; }
+
+ public string GetAvatarUrl(int size = 128);
+ public string GetBannerUrl(int size = 128);
+}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 8d240bd..545dac6 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,21 +1,13 @@
-MIT License
+Copyright 2025 Dmitrii Shimanskii
-Copyright (c) 2023 x3rt
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
+ http://www.apache.org/licenses/LICENSE-2.0
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
\ No newline at end of file
diff --git a/Options/GuildOptions.cs b/Options/GuildOptions.cs
deleted file mode 100644
index 3571b34..0000000
--- a/Options/GuildOptions.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-namespace x3rt.DiscordOAuth2.Options;
-
-public class GuildOptions
-{
- public ulong GuildId { get; set; }
- public string? Nickname { get; set; }
- public IEnumerable? RoleIds { get; set; }
- public bool? Muted { get; set; }
- public bool? Deafened { get; set; }
-
- public GuildOptions(ulong guildId, string? nickname = null, IEnumerable? roleIds = null, bool? mute = null, bool? deafened = null)
- {
- GuildId = guildId;
- Nickname = nickname;
- RoleIds = roleIds;
- Muted = mute;
- Deafened = deafened;
- }
-
- public GuildOptions Mute(bool mute = true)
- {
- Muted = mute;
- return this;
- }
-
- public GuildOptions Deafen(bool deafen = true)
- {
- Deafened = deafen;
- return this;
- }
-
- public GuildOptions WithNickname(string nickname)
- {
- Nickname = nickname;
- return this;
- }
-
- public GuildOptions WithRoleIds(params ulong[] roleIds)
- {
- RoleIds = roleIds;
- return this;
- }
-
- public GuildOptions WithRoleIds(IEnumerable roleIds)
- {
- RoleIds = roleIds.ToArray();
- return this;
- }
-
- public GuildOptions WithRoleId(ulong roleId)
- {
- if (RoleIds is null)
- {
- RoleIds = new[] { roleId };
- }
- else
- {
- RoleIds = RoleIds.Append(roleId);
- }
-
- return this;
- }
-
-
- public override string ToString()
- {
- return $"GuildId: {GuildId}; Nickname: {Nickname}; RoleIds: {RoleIds}";
- }
-}
\ No newline at end of file
diff --git a/README.md b/README.md
index 5483b65..2514251 100644
--- a/README.md
+++ b/README.md
@@ -1,41 +1,64 @@
-# x3rt.DiscordOAuth2
+# yawaflua.Discord.Net
-[](https://www.nuget.org/packages/x3rt.DiscordOAuth2/)
-[](https://www.nuget.org/packages/x3rt.DiscordOAuth2/)
+[](https://www.nuget.org/packages/yawaflua.Discord.Net/)
+[](https://www.nuget.org/packages/yawaflua.Discord.Net/)
-A **simple** library to handle Discord OAuth2 authentication.
-Meant to serve as an alternative to
-the [AspNet.Security.OAuth.Providers](https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers) library that
-gives more control to the user and one that can be used by people that are trying to avoid ASP.NETs cluttered and bulky Identity system.
+yawaflua.Discord.Net is a library for integrating Discord OAuth2 into your .NET applications. It provides a simple and easy-to-use interface for authenticating users with Discord, retrieving user information, and managing sessions.
+
+## Features
+- **OAuth2 Authentication**: Easily authenticate users with Discord using OAuth2.
+- **User Information**: Retrieve user information such as username, avatar, and guilds.
+- **Session Management**: Create and manage sessions for authenticated users.
+- **Dependency Injection**: Seamlessly integrate with your .NET application's dependency injection system.
## Usage
```csharp
-// Configure OAuth ClientID and ClientSecret globally
-DiscordOAuth.Configure(0123456789, "ClientSecret", "OptionalBotToken");
-```
-```csharp
-var scopes = new ScopesBuilder(OAuthScope.Identify);
-var oAuth = new DiscordOAuth("https://example.com/Login", scopes);
-var url = oAuth.GetAuthorizationUrl("state");
-/* Redirect user to url via preferred method */
-```
-```csharp
-// Your callback method
-if (DiscordOAuth.TryGetCode(HttpContext, out var code))
+// Configure configuration to DI
+builder.Services.AddSingleton(
+ new DiscordConfig
+ {
+ ClientId = "your-client-id",
+ ClientSecret = "shhhhh!",
+ RedirectUri = "https://example.com/Login",
+ Scopes = new ScopesBuilder(OAuthScope.Identify, OAuthScope.Guilds),
+ Token = "MyVeryCool.BotToken.For.Discord" // Optional, for bot interactions
+ });
+builder.Services.AddSingleton();
+
+// And use it in you very cool controller or smth
+
+public class MyController (IDiscord discord) : ControllerBase
{
- var scopes = new ScopesBuilder(OAuthScope.Identify);
- var oAuth = new DiscordOAuth("https://example.com/Login", scopes);
- var token = await oAuth.GetTokenAsync(code);
- var user = await oAuth.GetUserAsync(token);
-
- ulong userId = user.Id;
- // ...
+ public async Task Login()
+ {
+ var url = discord.GetAuthorizationUrl("state");
+ return Redirect(url);
+ }
+ public async Task Callback()
+ {
+ if (discord.TryGetCode(HttpContext, out var code))
+ {
+ var token = await discord.GetTokenAsync(code);
+ var user = await discord.GetUserAsync(token);
+
+ var session = discord.CreateSession();
+ var guilds = await session.GetGuildsAsync();
+ // Do something with the user and guilds, like storing them in a database or session
+ // ...
+
+ return Ok();
+ }
+
+ return BadRequest("Invalid code");
+ }
}
```
## Feedback
-DiscordOAuth2 is a work in progress and any feedback or suggestions are welcome.
-This library is released under the [MIT License](LICENSE).
+`yawaflua.Discord.Net` is a library still in development, and your feedback or support is very welcome. If you have any issues, suggestions, or questions, please open an issue on the [GitHub repository](https://github.com/yawaflua/Discord.Net/)
+
+
+This library is released under the [Apache 2.0](LICENSE).
diff --git a/ScopesBuilder.cs b/ScopesBuilder.cs
index 74d2d9e..5d00c51 100644
--- a/ScopesBuilder.cs
+++ b/ScopesBuilder.cs
@@ -1,6 +1,6 @@
-using x3rt.DiscordOAuth2.Entities.Enums;
+using yawaflua.Discord.Net.Entities.Enums;
-namespace x3rt.DiscordOAuth2;
+namespace yawaflua.Discord.Net;
public class ScopesBuilder
{
diff --git a/x3rt.DiscordOAuth2.csproj b/yawaflua.Discord.Net.csproj
similarity index 66%
rename from x3rt.DiscordOAuth2.csproj
rename to yawaflua.Discord.Net.csproj
index 6c4378a..381ffb6 100644
--- a/x3rt.DiscordOAuth2.csproj
+++ b/yawaflua.Discord.Net.csproj
@@ -1,28 +1,28 @@
- net7.0
+ net9.0
enable
enable
true
Discord OAuth2
- x3rt
+ yawaflua
Discord OAuth2 implementation for C#
- x3rt.DiscordOAuth2
- x3rt.DiscordOAuth2
+ yawaflua.Discord.Net
+ yawaflua.Discord.Net
README.md
LICENSE
+ httpsL//github.com/yawaflua/Discord.Net
true
- https://github.com/x3rt/x3rt.DiscordOAuth2
+ https://github.com/yawaflua/Discord.Net
GIT
- Discord-OAuth2;Discord-OAuth-2;Discord-OAuth;DiscordOAuth;Discord;OAuth;OAuth-2;OAuth2
+ Discord-OAuth2;Discord-OAuth-2;Discord-OAuth;DiscordOAuth;Discord;yawaflua;OAuth;OAuth-2;OAuth2
true
- 1.0.4
+ 1.0.5
-
diff --git a/x3rt.DiscordOAuth2.sln b/yawaflua.Discord.Net.sln
similarity index 81%
rename from x3rt.DiscordOAuth2.sln
rename to yawaflua.Discord.Net.sln
index bf29364..f5d9809 100644
--- a/x3rt.DiscordOAuth2.sln
+++ b/yawaflua.Discord.Net.sln
@@ -1,5 +1,5 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "x3rt.DiscordOAuth2", "x3rt.DiscordOAuth2.csproj", "{09C8F24A-5CC8-42E3-9D86-3DD68D6642E0}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "yawaflua.Discord.Net", "yawaflua.Discord.Net.csproj", "{09C8F24A-5CC8-42E3-9D86-3DD68D6642E0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution