mirror of
https://github.com/yawaflua/Discord.Net.git
synced 2025-12-08 19:39:35 +02:00
Refactor and rename project to yawaflua.Discord.Net; add core entities and interfaces for Discord OAuth2 integration
This commit is contained in:
23
.github/workflows/dotnet.yml
vendored
Normal file
23
.github/workflows/dotnet.yml
vendored
Normal file
@@ -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
|
||||
48
.github/workflows/nuget.yml
vendored
Normal file
48
.github/workflows/nuget.yml
vendored
Normal file
@@ -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 }}
|
||||
11
DiscordConfig.cs
Normal file
11
DiscordConfig.cs
Normal file
@@ -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;
|
||||
}
|
||||
220
DiscordOAuth.cs
220
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();
|
||||
|
||||
/// <summary>
|
||||
/// Now deprecated, use DiscordConfig instead.
|
||||
/// </summary>
|
||||
/// <param name="clientId"></param>
|
||||
/// <param name="clientSecret"></param>
|
||||
/// <param name="botToken"></param>
|
||||
[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<OAuthToken?> GetTokenAsync(string code)
|
||||
public async Task<IToken?> GetTokenAsync(string code)
|
||||
{
|
||||
var content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
@@ -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<OAuthToken>(responseString);
|
||||
var authToken = JsonSerializer.Deserialize<OAuthToken>(responseString);
|
||||
AccessToken = authToken?.AccessToken;
|
||||
token = authToken;
|
||||
return authToken;
|
||||
}
|
||||
|
||||
private async Task<T?> GetInformationAsync<T>(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<T?>(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<T?> GetInformationAsync<T>(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<T>(accessToken.AccessToken, endpoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await GetInformationAsync<T>(AccessToken, endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<T?> GetInformationAsync<T>(OAuthToken token, string endpoint) where T : class
|
||||
{
|
||||
return await GetInformationAsync<T>(token.AccessToken, endpoint);
|
||||
}
|
||||
|
||||
public async Task<DiscordUser?> GetUserAsync(string accessToken)
|
||||
{
|
||||
return await GetInformationAsync<DiscordUser>(accessToken, "users/@me");
|
||||
}
|
||||
|
||||
public async Task<DiscordUser?> GetUserAsync(HttpContext context)
|
||||
{
|
||||
return await GetInformationAsync<DiscordUser>(context, "users/@me");
|
||||
}
|
||||
|
||||
public async Task<DiscordUser?> GetUserAsync(OAuthToken token)
|
||||
{
|
||||
return await GetInformationAsync<DiscordUser>(token, "users/@me");
|
||||
}
|
||||
|
||||
public async Task<DiscordGuild[]?> GetGuildsAsync(string accessToken)
|
||||
{
|
||||
return await GetInformationAsync<DiscordGuild[]>(accessToken, "users/@me/guilds");
|
||||
}
|
||||
|
||||
public async Task<DiscordGuild[]?> GetGuildsAsync(HttpContext context)
|
||||
{
|
||||
return await GetInformationAsync<DiscordGuild[]>(context, "users/@me/guilds");
|
||||
}
|
||||
|
||||
public async Task<DiscordGuild[]?> GetGuildsAsync(OAuthToken token)
|
||||
{
|
||||
return await GetInformationAsync<DiscordGuild[]>(token, "users/@me/guilds");
|
||||
}
|
||||
|
||||
public async Task<DiscordConnection[]?> GetConnectionsAsync(string accessToken)
|
||||
{
|
||||
return await GetInformationAsync<DiscordConnection[]>(accessToken, "users/@me/connections");
|
||||
}
|
||||
|
||||
public async Task<DiscordConnection[]?> GetConnectionsAsync(HttpContext context)
|
||||
{
|
||||
return await GetInformationAsync<DiscordConnection[]>(context, "users/@me/connections");
|
||||
}
|
||||
|
||||
public async Task<DiscordConnection[]?> GetConnectionsAsync(OAuthToken token)
|
||||
{
|
||||
return await GetInformationAsync<DiscordConnection[]>(token, "users/@me/connections");
|
||||
}
|
||||
|
||||
public async Task<bool> 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<string, object>
|
||||
{
|
||||
["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<bool> 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<bool> 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);
|
||||
}
|
||||
|
||||
}
|
||||
12
Entities/AvatarDecoration.cs
Normal file
12
Entities/AvatarDecoration.cs
Normal file
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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<GuildFeature> 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}";
|
||||
}
|
||||
}
|
||||
39
Entities/DiscordGuildMember.cs
Normal file
39
Entities/DiscordGuildMember.cs
Normal file
@@ -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<IRole> 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;
|
||||
}
|
||||
}
|
||||
26
Entities/DiscordRole.cs
Normal file
26
Entities/DiscordRole.cs
Normal file
@@ -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<RoleTags, ulong?>? Tags { get; set; }
|
||||
|
||||
IRoleColor IRole.Colors
|
||||
{
|
||||
get => Colors;
|
||||
set => Colors = (RoleColor)value;
|
||||
}
|
||||
}
|
||||
103
Entities/DiscordSession.cs
Normal file
103
Entities/DiscordSession.cs
Normal file
@@ -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<T?> _req<T>(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<T?>(responseString) ?? null;
|
||||
}
|
||||
|
||||
public async Task<IList<IGuild>?> GetGuildsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (token.AccessToken is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(token), "Token cannot be null.");
|
||||
}
|
||||
|
||||
return await _req<DiscordGuild[]>("users/@me/guilds");
|
||||
}
|
||||
|
||||
public async Task<IGuildMember?> GetGuildMemberAsync(ulong guildId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (token.AccessToken is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(token), "Token cannot be null.");
|
||||
}
|
||||
|
||||
return await _req<DiscordGuildMember>($"users/@me/guilds/{guildId}/member");
|
||||
}
|
||||
|
||||
public async Task<IGuildMember?> 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<DiscordGuildMember>($"guilds/{guildId}/members/{userId}", HttpMethod.Put);
|
||||
}
|
||||
|
||||
public async Task<IUser?> GetCurrentUserAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (token.AccessToken is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(token), "Token cannot be null.");
|
||||
}
|
||||
|
||||
return await _req<DiscordUser>("users/@me");
|
||||
}
|
||||
|
||||
public async Task<IConnection?> GetConnectionAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (token.AccessToken is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(token), "Token cannot be null.");
|
||||
}
|
||||
return await _req<DiscordConnection>("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;
|
||||
}
|
||||
}
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
24
Entities/Enums/ConnectionType.cs
Normal file
24
Entities/Enums/ConnectionType.cs
Normal file
@@ -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
|
||||
}
|
||||
7
Entities/Enums/ConnectionVisibility.cs
Normal file
7
Entities/Enums/ConnectionVisibility.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace yawaflua.Discord.Net.Entities.Enums;
|
||||
|
||||
public enum ConnectionVisibility
|
||||
{
|
||||
None,
|
||||
Everyone
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace x3rt.DiscordOAuth2.Entities.Enums;
|
||||
namespace yawaflua.Discord.Net.Entities.Enums;
|
||||
|
||||
[Flags]
|
||||
// Credit: Discord.Net
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace x3rt.DiscordOAuth2.Entities.Enums;
|
||||
namespace yawaflua.Discord.Net.Entities.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the OAuth2 scopes available for a Discord application.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace x3rt.DiscordOAuth2.Entities.Enums;
|
||||
namespace yawaflua.Discord.Net.Entities.Enums;
|
||||
|
||||
public enum PremiumType
|
||||
{
|
||||
|
||||
11
Entities/Enums/RoleTags.cs
Normal file
11
Entities/Enums/RoleTags.cs
Normal file
@@ -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,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace x3rt.DiscordOAuth2.Entities.Enums;
|
||||
namespace yawaflua.Discord.Net.Entities.Enums;
|
||||
|
||||
[Flags]
|
||||
public enum UserFlag : ulong
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using x3rt.DiscordOAuth2.Entities.Enums;
|
||||
|
||||
namespace x3rt.DiscordOAuth2.Entities;
|
||||
|
||||
public record GuildFeatures
|
||||
{
|
||||
IEnumerable<GuildFeature> Features { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Join(", ", Features);
|
||||
}
|
||||
}
|
||||
@@ -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<string, string>
|
||||
{
|
||||
{ "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<string, string>
|
||||
{
|
||||
{ "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<OAuthToken>(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;
|
||||
}
|
||||
}
|
||||
10
Entities/RoleColor.cs
Normal file
10
Entities/RoleColor.cs
Normal file
@@ -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; }
|
||||
}
|
||||
9
Interfaces/IDiscord.cs
Normal file
9
Interfaces/IDiscord.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using yawaflua.Discord.Net.Interfaces.Models;
|
||||
|
||||
namespace yawaflua.Discord.Net.Interfaces;
|
||||
|
||||
public interface IDiscord
|
||||
{
|
||||
Task<IToken?> GetTokenAsync(string code);
|
||||
ISession? CreateSession();
|
||||
}
|
||||
7
Interfaces/Models/IAvatarDecoration.cs
Normal file
7
Interfaces/Models/IAvatarDecoration.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace yawaflua.Discord.Net.Interfaces.Models;
|
||||
|
||||
public interface IAvatarDecoration
|
||||
{
|
||||
public string AssetHash { get; set; }
|
||||
public ulong AssetArticular { get; set; }
|
||||
}
|
||||
17
Interfaces/Models/IConnection.cs
Normal file
17
Interfaces/Models/IConnection.cs
Normal file
@@ -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; }
|
||||
}
|
||||
19
Interfaces/Models/IGuild.cs
Normal file
19
Interfaces/Models/IGuild.cs
Normal file
@@ -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<GuildFeature> 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);
|
||||
}
|
||||
18
Interfaces/Models/IGuildMember.cs
Normal file
18
Interfaces/Models/IGuildMember.cs
Normal file
@@ -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<IRole> 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; }
|
||||
}
|
||||
25
Interfaces/Models/IRole.cs
Normal file
25
Interfaces/Models/IRole.cs
Normal file
@@ -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<RoleTags, ulong?>? Tags { get; set; }
|
||||
}
|
||||
8
Interfaces/Models/IRoleColor.cs
Normal file
8
Interfaces/Models/IRoleColor.cs
Normal file
@@ -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; }
|
||||
}
|
||||
16
Interfaces/Models/ISession.cs
Normal file
16
Interfaces/Models/ISession.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace yawaflua.Discord.Net.Interfaces.Models;
|
||||
|
||||
public interface ISession
|
||||
{
|
||||
Task<IList<IGuild>?> GetGuildsAsync(CancellationToken cancellationToken = default);
|
||||
Task<IGuildMember?> GetGuildMemberAsync(ulong guildId, CancellationToken cancellationToken = default);
|
||||
Task<IGuildMember?> AddMemberToGuildAsync(ulong guildId, ulong userId, CancellationToken cancellationToken = default);
|
||||
Task<IUser?> GetCurrentUserAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
Task<IConnection?> GetConnectionAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
string GetAuthorizationUrl(string state);
|
||||
IToken GetToken(CancellationToken cancellationToken = default);
|
||||
|
||||
}
|
||||
14
Interfaces/Models/IToken.cs
Normal file
14
Interfaces/Models/IToken.cs
Normal file
@@ -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);
|
||||
|
||||
}
|
||||
115
Interfaces/Models/IUser.cs
Normal file
115
Interfaces/Models/IUser.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using yawaflua.Discord.Net.Entities.Enums;
|
||||
|
||||
namespace yawaflua.Discord.Net.Interfaces.Models;
|
||||
|
||||
public interface IUser
|
||||
{
|
||||
/// <summary>
|
||||
/// the user's id
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public ulong Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the user's username, not unique across the platform
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the user's display name, if it is set. For bots, this is the application name
|
||||
/// </summary>
|
||||
public string GlobalName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the user's Discord-tag (Obsolete)
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public string Discriminator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the user's avatar hash
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/reference#image-formatting">Image formatting</seealso>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public string? AvatarHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the user's banner hash
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/reference#image-formatting">Image formatting</seealso>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
/// <remarks>Available in the User object only if the user has a banner set.</remarks>
|
||||
/// <example>https://cdn.discordapp.com/banners/{user.id}/{banner}.png?size=512</example>
|
||||
public string? Banner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// whether the user belongs to an OAuth2 application
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public bool? Bot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// whether the user is an Official Discord System user (part of the urgent message system)
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public bool? System { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// whether the user has two factor enabled on their account
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public bool? MfaEnabled { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// the user's banner color encoded as an integer representation of hexadecimal color code
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public int? AccentColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the user's chosen language option
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public string? Locale { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// whether the email on this account has been verified
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public bool? Verified { get; set; }
|
||||
/// <summary>
|
||||
/// the user's email
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public string? Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the flags on a user's account
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object-user-flags">User flags</seealso>
|
||||
public UserFlag? Flags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the type of Nitro subscription on a user's account
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public PremiumType? PremiumType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// the public flags on a user's account
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public UserFlag? PublicFlags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// data for the user's avatar decoration
|
||||
/// </summary>
|
||||
/// <seealso href="https://discord.com/developers/docs/resources/user#user-object">User-object</seealso>
|
||||
public IAvatarDecoration? AvatarDecoration { get; set; }
|
||||
|
||||
public string GetAvatarUrl(int size = 128);
|
||||
public string GetBannerUrl(int size = 128);
|
||||
}
|
||||
28
LICENSE
28
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.
|
||||
@@ -1,69 +0,0 @@
|
||||
namespace x3rt.DiscordOAuth2.Options;
|
||||
|
||||
public class GuildOptions
|
||||
{
|
||||
public ulong GuildId { get; set; }
|
||||
public string? Nickname { get; set; }
|
||||
public IEnumerable<ulong>? RoleIds { get; set; }
|
||||
public bool? Muted { get; set; }
|
||||
public bool? Deafened { get; set; }
|
||||
|
||||
public GuildOptions(ulong guildId, string? nickname = null, IEnumerable<ulong>? 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<ulong> 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}";
|
||||
}
|
||||
}
|
||||
79
README.md
79
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<DiscordConfig>(
|
||||
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<IDiscord, DiscordOAuth>();
|
||||
|
||||
// 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<IActionResult> Login()
|
||||
{
|
||||
var url = discord.GetAuthorizationUrl("state");
|
||||
return Redirect(url);
|
||||
}
|
||||
|
||||
public async Task<IActionResult> 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).
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Title>Discord OAuth2</Title>
|
||||
<Authors>x3rt</Authors>
|
||||
<Authors>yawaflua</Authors>
|
||||
<Description>Discord OAuth2 implementation for C#</Description>
|
||||
<AssemblyName>x3rt.DiscordOAuth2</AssemblyName>
|
||||
<RootNamespace>x3rt.DiscordOAuth2</RootNamespace>
|
||||
<AssemblyName>yawaflua.Discord.Net</AssemblyName>
|
||||
<RootNamespace>yawaflua.Discord.Net</RootNamespace>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageProjectUrl>httpsL//github.com/yawaflua/Discord.Net</PackageProjectUrl>
|
||||
<PUblishRepositoryUrl>true</PUblishRepositoryUrl>
|
||||
<RepositoryUrl>https://github.com/x3rt/x3rt.DiscordOAuth2</RepositoryUrl>
|
||||
<RepositoryUrl>https://github.com/yawaflua/Discord.Net</RepositoryUrl>
|
||||
<RepositoryType>GIT</RepositoryType>
|
||||
<PackageTags>Discord-OAuth2;Discord-OAuth-2;Discord-OAuth;DiscordOAuth;Discord;OAuth;OAuth-2;OAuth2</PackageTags>
|
||||
<PackageTags>Discord-OAuth2;Discord-OAuth-2;Discord-OAuth;DiscordOAuth;Discord;yawaflua;OAuth;OAuth-2;OAuth2</PackageTags>
|
||||
<Deterministic>true</Deterministic>
|
||||
<Version>1.0.4</Version>
|
||||
<Version>1.0.5</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0"/>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user