Refactor and rename project to yawaflua.Discord.Net; add core entities and interfaces for Discord OAuth2 integration

This commit is contained in:
Dmitri Shimanski
2025-08-22 06:48:43 +03:00
parent 423bc8def0
commit e0d2b65fff
37 changed files with 867 additions and 382 deletions

View File

@@ -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);
}
}