mirror of
https://github.com/yawaflua/Discord.Net.git
synced 2025-12-08 19:39:35 +02:00
Initial release
This commit is contained in:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
.idea
|
||||
217
DiscordOAuth.cs
Normal file
217
DiscordOAuth.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Newtonsoft.Json;
|
||||
using x3rt.DiscordOAuth2.Entities;
|
||||
using x3rt.DiscordOAuth2.Options;
|
||||
|
||||
namespace x3rt.DiscordOAuth2;
|
||||
|
||||
public class DiscordOAuth
|
||||
{
|
||||
private static ulong ClientId { get; set; }
|
||||
private static string ClientSecret { get; set; } = string.Empty;
|
||||
private static 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)
|
||||
{
|
||||
ClientId = clientId;
|
||||
ClientSecret = clientSecret;
|
||||
BotToken = botToken;
|
||||
}
|
||||
|
||||
private readonly HttpClient _httpClient = new HttpClient();
|
||||
|
||||
public DiscordOAuth(string redirectUri, ScopesBuilder scopes, bool prompt = true)
|
||||
{
|
||||
RedirectUri = redirectUri;
|
||||
Scopes = scopes;
|
||||
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))
|
||||
{
|
||||
code = codeValues[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetCode(HttpContext context, out string? code)
|
||||
{
|
||||
var b = TryGetCode(context.Request, out var a);
|
||||
code = a;
|
||||
return b;
|
||||
}
|
||||
|
||||
public async Task<OAuthToken?> GetTokenAsync(string code)
|
||||
{
|
||||
var content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "client_id", ClientId.ToString() },
|
||||
{ "client_secret", ClientSecret },
|
||||
{ "grant_type", "authorization_code" },
|
||||
{ "code", code },
|
||||
{ "redirect_uri", RedirectUri },
|
||||
{ "scope", Scopes.ToString() }
|
||||
});
|
||||
|
||||
var response = await _httpClient.PostAsync("https://discord.com/api/oauth2/token", content);
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
var authToken = JsonConvert.DeserializeObject<OAuthToken>(responseString);
|
||||
AccessToken = authToken?.AccessToken;
|
||||
return authToken;
|
||||
}
|
||||
|
||||
private async Task<T> GetInformationAsync<T>(string accessToken, string endpoint)
|
||||
{
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
|
||||
var response = await _httpClient.GetAsync($"https://discord.com/api/{endpoint}");
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<T>(responseString) ?? default!;
|
||||
}
|
||||
|
||||
private async Task<T?> GetInformationAsync<T>(HttpContext context, string endpoint)
|
||||
{
|
||||
if (AccessToken is null)
|
||||
{
|
||||
if (!TryGetCode(context, out var code)) return default;
|
||||
var accessToken = await GetTokenAsync(code!);
|
||||
if (accessToken is null) return default;
|
||||
return await GetInformationAsync<T>(accessToken.AccessToken, endpoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
return await GetInformationAsync<T>(AccessToken, endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<T> GetInformationAsync<T>(OAuthToken token, string endpoint)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
48
Entities/DiscordConnection.cs
Normal file
48
Entities/DiscordConnection.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace x3rt.DiscordOAuth2.Entities;
|
||||
|
||||
public class DiscordConnection
|
||||
{
|
||||
[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
|
||||
}
|
||||
}
|
||||
24
Entities/DiscordGuild.cs
Normal file
24
Entities/DiscordGuild.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace x3rt.DiscordOAuth2.Entities;
|
||||
|
||||
public class DiscordGuild
|
||||
{
|
||||
[JsonProperty("id")] public ulong Id { get; set; }
|
||||
|
||||
[JsonProperty("name")] public string Name { get; set; }
|
||||
|
||||
[JsonProperty("icon")] public string? Icon { get; set; }
|
||||
|
||||
[JsonProperty("owner")] public bool Owner { get; set; }
|
||||
|
||||
[JsonProperty("permissions")] public string Permissions { get; set; }
|
||||
|
||||
[JsonProperty("features")] public GuildFeatures Features { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return
|
||||
$"Id: {Id}; Name: {Name}; Icon: {Icon}; Owner: {Owner}; Permissions: {Permissions}; Features: {Features}";
|
||||
}
|
||||
}
|
||||
38
Entities/DiscordUser.cs
Normal file
38
Entities/DiscordUser.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using x3rt.DiscordOAuth2.Entities.Enums;
|
||||
|
||||
namespace x3rt.DiscordOAuth2.Entities;
|
||||
|
||||
public class DiscordUser
|
||||
{
|
||||
public ulong Id { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Discriminator { get; set; }
|
||||
public string? Avatar { get; set; }
|
||||
public bool? Bot { get; set; }
|
||||
public bool? System { get; set; }
|
||||
public bool? MfaEnabled { get; set; }
|
||||
public string? Banner { get; set; }
|
||||
public int? AccentColor { get; set; }
|
||||
public string? Locale { get; set; }
|
||||
public bool? Verified { get; set; }
|
||||
public string? Email { get; set; }
|
||||
public UserFlag? Flags { get; set; }
|
||||
public PremiumType? PremiumType { get; set; }
|
||||
public UserFlag? PublicFlags { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string result = "";
|
||||
foreach (var property in GetType().GetProperties())
|
||||
{
|
||||
var value = property.GetValue(this);
|
||||
if (value is not null)
|
||||
{
|
||||
result += $"{property.Name}: {value}; ";
|
||||
}
|
||||
}
|
||||
|
||||
result = result.TrimEnd(' ', ';');
|
||||
return result;
|
||||
}
|
||||
}
|
||||
266
Entities/Enums/GuildFeature.cs
Normal file
266
Entities/Enums/GuildFeature.cs
Normal file
@@ -0,0 +1,266 @@
|
||||
namespace x3rt.DiscordOAuth2.Entities.Enums;
|
||||
|
||||
[Flags]
|
||||
// Credit: Discord.Net
|
||||
public enum GuildFeature : long
|
||||
{
|
||||
/// <summary>
|
||||
/// The guild has no features.
|
||||
/// </summary>
|
||||
None = 0L,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has access to animated banners.
|
||||
/// </summary>
|
||||
AnimatedBanner = 1L << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has access to set an animated guild icon.
|
||||
/// </summary>
|
||||
AnimatedIcon = 1L << 1,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has access to set a guild banner image.
|
||||
/// </summary>
|
||||
Banner = 1L << 2,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has access to channel banners.
|
||||
/// </summary>
|
||||
ChannelBanner = 1L << 3,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has access to use commerce features (i.e. create store channels).
|
||||
/// </summary>
|
||||
Commerce = 1L << 4,
|
||||
|
||||
/// <summary>
|
||||
/// The guild can enable welcome screen, Membership Screening, stage channels and discovery, and receives community updates.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This feature is mutable.
|
||||
/// </remarks>
|
||||
Community = 1L << 5,
|
||||
|
||||
/// <summary>
|
||||
/// The guild is able to be discovered in the directory.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This feature is mutable.
|
||||
/// </remarks>
|
||||
Discoverable = 1L << 6,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has discoverable disabled.
|
||||
/// </summary>
|
||||
DiscoverableDisabled = 1L << 7,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has enabled discoverable before.
|
||||
/// </summary>
|
||||
EnabledDiscoverableBefore = 1L << 8,
|
||||
|
||||
/// <summary>
|
||||
/// The guild is able to be featured in the directory.
|
||||
/// </summary>
|
||||
Featureable = 1L << 9,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has a force relay.
|
||||
/// </summary>
|
||||
ForceRelay = 1L << 10,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has a directory entry.
|
||||
/// </summary>
|
||||
HasDirectoryEntry = 1L << 11,
|
||||
|
||||
/// <summary>
|
||||
/// The guild is a hub.
|
||||
/// </summary>
|
||||
Hub = 1L << 12,
|
||||
|
||||
/// <summary>
|
||||
/// You shouldn't be here...
|
||||
/// </summary>
|
||||
InternalEmployeeOnly = 1L << 13,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has access to set an invite splash background.
|
||||
/// </summary>
|
||||
InviteSplash = 1L << 14,
|
||||
|
||||
/// <summary>
|
||||
/// The guild is linked to a hub.
|
||||
/// </summary>
|
||||
LinkedToHub = 1L << 15,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has member profiles.
|
||||
/// </summary>
|
||||
MemberProfiles = 1L << 16,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has enabled <seealso href="https://discord.com/developers/docs/resources/guild#membership-screening-object">Membership Screening</seealso>.
|
||||
/// </summary>
|
||||
MemberVerificationGateEnabled = 1L << 17,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has enabled monetization.
|
||||
/// </summary>
|
||||
MonetizationEnabled = 1L << 18,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has more emojis.
|
||||
/// </summary>
|
||||
MoreEmoji = 1L << 19,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has increased custom sticker slots.
|
||||
/// </summary>
|
||||
MoreStickers = 1L << 20,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has access to create news channels.
|
||||
/// </summary>
|
||||
News = 1L << 21,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has new thread permissions.
|
||||
/// </summary>
|
||||
NewThreadPermissions = 1L << 22,
|
||||
|
||||
/// <summary>
|
||||
/// The guild is partnered.
|
||||
/// </summary>
|
||||
Partnered = 1L << 23,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has a premium tier three override; guilds made by Discord usually have this.
|
||||
/// </summary>
|
||||
PremiumTier3Override = 1L << 24,
|
||||
|
||||
/// <summary>
|
||||
/// The guild can be previewed before joining via Membership Screening or the directory.
|
||||
/// </summary>
|
||||
PreviewEnabled = 1L << 25,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has access to create private threads.
|
||||
/// </summary>
|
||||
PrivateThreads = 1L << 26,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has relay enabled.
|
||||
/// </summary>
|
||||
RelayEnabled = 1L << 27,
|
||||
|
||||
/// <summary>
|
||||
/// The guild is able to set role icons.
|
||||
/// </summary>
|
||||
RoleIcons = 1L << 28,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has role subscriptions available for purchase.
|
||||
/// </summary>
|
||||
RoleSubscriptionsAvailableForPurchase = 1L << 29,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has role subscriptions enabled.
|
||||
/// </summary>
|
||||
RoleSubscriptionsEnabled = 1L << 30,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has access to the seven day archive time for threads.
|
||||
/// </summary>
|
||||
SevenDayThreadArchive = 1L << 31,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has text in voice enabled.
|
||||
/// </summary>
|
||||
TextInVoiceEnabled = 1L << 32,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has threads enabled.
|
||||
/// </summary>
|
||||
ThreadsEnabled = 1L << 33,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has testing threads enabled.
|
||||
/// </summary>
|
||||
ThreadsEnabledTesting = 1L << 34,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has the default thread auto archive.
|
||||
/// </summary>
|
||||
ThreadsDefaultAutoArchiveDuration = 1L << 35,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has access to the three day archive time for threads.
|
||||
/// </summary>
|
||||
ThreeDayThreadArchive = 1L << 36,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has enabled ticketed events.
|
||||
/// </summary>
|
||||
TicketedEventsEnabled = 1L << 37,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has access to set a vanity URL.
|
||||
/// </summary>
|
||||
VanityUrl = 1L << 38,
|
||||
|
||||
/// <summary>
|
||||
/// The guild is verified.
|
||||
/// </summary>
|
||||
Verified = 1L << 39,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has access to set 384kbps bitrate in voice (previously VIP voice servers).
|
||||
/// </summary>
|
||||
VIPRegions = 1L << 40,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has enabled the welcome screen.
|
||||
/// </summary>
|
||||
WelcomeScreenEnabled = 1L << 41,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has been set as a support server on the App Directory.
|
||||
/// </summary>
|
||||
DeveloperSupportServer = 1L << 42,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has invites disabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This feature is mutable.
|
||||
/// </remarks>
|
||||
InvitesDisabled = 1L << 43,
|
||||
|
||||
/// <summary>
|
||||
/// The guild has auto moderation enabled.
|
||||
/// </summary>
|
||||
AutoModeration = 1L << 44,
|
||||
|
||||
/// <summary>
|
||||
/// This guild has alerts for join raids disabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This feature is mutable.
|
||||
/// </remarks>
|
||||
RaidAlertsDisabled = 1L << 45,
|
||||
|
||||
/// <summary>
|
||||
/// This guild has Clyde AI enabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This feature is mutable.
|
||||
/// </remarks>
|
||||
ClydeEnabled = 1L << 46,
|
||||
|
||||
/// <summary>
|
||||
/// This guild has a guild web page vanity url.
|
||||
/// </summary>
|
||||
GuildWebPageVanityUrl = 1L << 47
|
||||
}
|
||||
134
Entities/Enums/OAuthScope.cs
Normal file
134
Entities/Enums/OAuthScope.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
namespace x3rt.DiscordOAuth2.Entities.Enums;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the OAuth2 scopes available for a Discord application.
|
||||
/// <a href="https://github.com/DSharpPlus/DSharpPlus/blob/e62b2cc3e434b744ef3cf14929f506c21be4d0d4/DSharpPlus/Entities/Application/DiscordApplication.cs#L442">
|
||||
/// <br></br>
|
||||
/// Credit to DSharpPlus
|
||||
/// </a>
|
||||
/// </summary>
|
||||
public enum OAuthScope
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows <c>/users/@me</c> without <c>email</c>.
|
||||
/// </summary>
|
||||
Identify,
|
||||
|
||||
/// <summary>
|
||||
/// Enables <c>/users/@me</c> to return <c>email</c>.
|
||||
/// </summary>
|
||||
Email,
|
||||
|
||||
/// <summary>
|
||||
/// Allows <c>/users/@me/connections</c> to return linked third-party accounts.
|
||||
/// </summary>
|
||||
Connections,
|
||||
|
||||
/// <summary>
|
||||
/// Allows <c>/users/@me/guilds</c> to return basic information about all of a user's guilds.
|
||||
/// </summary>
|
||||
Guilds,
|
||||
|
||||
/// <summary>
|
||||
/// Allows <c>/guilds/{guild.id}/members/{user.id}</c> to be used for joining users into a guild.
|
||||
/// </summary>
|
||||
GuildsJoin,
|
||||
|
||||
/// <summary>
|
||||
/// Allows <c>/users/@me/guilds/{guild.id}/members</c> to return a user's member information in a guild.
|
||||
/// </summary>
|
||||
GuildsMembersRead,
|
||||
|
||||
/// <summary>Allows your app to join users into a group DM.</summary>
|
||||
GdmJoin,
|
||||
|
||||
/// <summary>
|
||||
/// For local RPC server access, this allows you to control a user's local Discord client.
|
||||
/// </summary>
|
||||
/// <remarks>This scope requires Discord approval.</remarks>
|
||||
Rpc,
|
||||
|
||||
/// <summary>
|
||||
/// For local RPC server access, this allows you to receive notifications pushed to the user.
|
||||
/// </summary>
|
||||
/// <remarks>This scope requires Discord approval.</remarks>
|
||||
RpcNotificationsRead,
|
||||
|
||||
/// <summary>
|
||||
/// For local RPC server access, this allows you to read a user's voice settings and listen for voice events.
|
||||
/// </summary>
|
||||
/// <remarks>This scope requires Discord approval.</remarks>
|
||||
RpcVoiceRead,
|
||||
|
||||
/// <summary>
|
||||
/// For local RPC server access, this allows you to update a user's voice settings.
|
||||
/// </summary>
|
||||
/// <remarks>This scope requires Discord approval.</remarks>
|
||||
RpcVoiceWrite,
|
||||
|
||||
/// <summary>
|
||||
/// For local RPC server access, this allows you to update a user's activity.
|
||||
/// </summary>
|
||||
/// <remarks>This scope requires Discord approval.</remarks>
|
||||
RpcActivitiesWrite,
|
||||
|
||||
/// <summary>
|
||||
/// For OAuth2 bots, this puts the bot in the user's selected guild by default.
|
||||
/// </summary>
|
||||
Bot,
|
||||
|
||||
/// <summary>
|
||||
/// This generates a webhook that is returned in the OAuth token response for authorization code grants.
|
||||
/// </summary>
|
||||
WebhookIncoming,
|
||||
|
||||
/// <summary>
|
||||
/// For local RPC server access, this allows you to read messages from all client channels
|
||||
/// (otherwise restricted to channels/guilds your application creates).
|
||||
/// </summary>
|
||||
MessagesRead,
|
||||
|
||||
/// <summary>
|
||||
/// Allows your application to upload/update builds for a user's applications.
|
||||
/// </summary>
|
||||
/// <remarks>This scope requires Discord approval.</remarks>
|
||||
ApplicationsBuildsUpload,
|
||||
|
||||
/// <summary>
|
||||
/// Allows your application to read build data for a user's applications.
|
||||
/// </summary>
|
||||
ApplicationsBuildsRead,
|
||||
|
||||
/// <summary>
|
||||
/// Allows your application to use application commands in a guild.
|
||||
/// </summary>
|
||||
ApplicationsCommands,
|
||||
|
||||
/// <summary>
|
||||
/// Allows your application to read and update store data (SKUs, store listings, achievements etc.) for a user's applications.
|
||||
/// </summary>
|
||||
ApplicationsStoreUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// Allows your application to read entitlements for a user's applications.
|
||||
/// </summary>
|
||||
ApplicationsEntitlements,
|
||||
|
||||
/// <summary>
|
||||
/// Allows your application to fetch data from a user's "Now Playing/Recently Played" list.
|
||||
/// </summary>
|
||||
/// <remarks>This scope requires Discord approval.</remarks>
|
||||
ActivitiesRead,
|
||||
|
||||
/// <summary>Allows your application to update a user's activity.</summary>
|
||||
/// <remarks>
|
||||
/// Outside of the GameSDK activity manager, this scope requires Discord approval.
|
||||
/// </remarks>
|
||||
ActivitiesWrite,
|
||||
|
||||
/// <summary>
|
||||
/// Allows your application to know a user's friends and implicit relationships.
|
||||
/// </summary>
|
||||
/// <remarks>This scope requires Discord approval.</remarks>
|
||||
RelationshipsRead,
|
||||
}
|
||||
9
Entities/Enums/PremiumType.cs
Normal file
9
Entities/Enums/PremiumType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace x3rt.DiscordOAuth2.Entities.Enums;
|
||||
|
||||
public enum PremiumType
|
||||
{
|
||||
None = 0,
|
||||
NitroClassic = 1,
|
||||
Nitro = 2,
|
||||
NitroBasic = 3
|
||||
}
|
||||
21
Entities/Enums/UserFlag.cs
Normal file
21
Entities/Enums/UserFlag.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace x3rt.DiscordOAuth2.Entities.Enums;
|
||||
|
||||
[Flags]
|
||||
public enum UserFlag : ulong
|
||||
{
|
||||
Staff = 1 << 0,
|
||||
Partner = 1 << 1,
|
||||
HypeSquad = 1 << 2,
|
||||
BugHunterLevel1 = 1 << 3,
|
||||
HouseBraveryMember = 1 << 6,
|
||||
HouseBrillianceMember = 1 << 7,
|
||||
HouseBalanceMember = 1 << 8,
|
||||
EarlyNitroSupporter = 1 << 9,
|
||||
TeamPseudoUser = 1 << 10,
|
||||
BugHunterLevel2 = 1 << 14,
|
||||
VerifiedBot = 1 << 16,
|
||||
VerifiedDeveloper = 1 << 17,
|
||||
CertifiedModerator = 1 << 18,
|
||||
BotHttpInteractions = 1 << 19,
|
||||
ActiveDeveloper = 1 << 22
|
||||
}
|
||||
13
Entities/GuildFeatures.cs
Normal file
13
Entities/GuildFeatures.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
16
Entities/OAuthToken.cs
Normal file
16
Entities/OAuthToken.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace x3rt.DiscordOAuth2.Entities;
|
||||
|
||||
public class OAuthToken
|
||||
{
|
||||
[JsonProperty("access_token")] public string AccessToken { get; set; }
|
||||
|
||||
[JsonProperty("expires_in")] public int ExpiresIn { get; set; }
|
||||
|
||||
[JsonProperty("refresh_token")] public string RefreshToken { get; set; }
|
||||
|
||||
[JsonProperty("scope")] public string Scope { get; set; }
|
||||
|
||||
[JsonProperty("token_type")] public string TokenType { get; set; }
|
||||
}
|
||||
69
Options/GuildOptions.cs
Normal file
69
Options/GuildOptions.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
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}";
|
||||
}
|
||||
}
|
||||
42
README.md
Normal file
42
README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# x3rt.DiscordOAuth2
|
||||
|
||||
[](https://www.nuget.org/packages/x3rt.DiscordOAuth2/)
|
||||
[](https://www.nuget.org/packages/x3rt.DiscordOAuth2/)
|
||||
|
||||
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.
|
||||
|
||||
## 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();
|
||||
/* Redirect user to url via preferred method */
|
||||
```
|
||||
```csharp
|
||||
// Your callback method
|
||||
if (DiscordOAuth.TryGetCode(HttpContext, out var code))
|
||||
{
|
||||
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;
|
||||
// ...
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Feedback
|
||||
|
||||
DiscordOAuth2 is a work in progress and any feedback or suggestions are welcome.
|
||||
This library is released under the [MIT License](LICENSE).
|
||||
```
|
||||
89
ScopesBuilder.cs
Normal file
89
ScopesBuilder.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using x3rt.DiscordOAuth2.Entities.Enums;
|
||||
|
||||
namespace x3rt.DiscordOAuth2;
|
||||
|
||||
public class ScopesBuilder
|
||||
{
|
||||
List<OAuthScope> _scopes = new List<OAuthScope>();
|
||||
|
||||
|
||||
public ScopesBuilder()
|
||||
{
|
||||
}
|
||||
|
||||
public ScopesBuilder(OAuthScope scope)
|
||||
{
|
||||
_scopes.Add(scope);
|
||||
}
|
||||
|
||||
public ScopesBuilder(IEnumerable<OAuthScope> scopes)
|
||||
{
|
||||
_scopes.AddRange(scopes);
|
||||
}
|
||||
|
||||
public ScopesBuilder(params OAuthScope[] scopes)
|
||||
{
|
||||
_scopes.AddRange(scopes);
|
||||
}
|
||||
|
||||
public ScopesBuilder AddScope(OAuthScope scope)
|
||||
{
|
||||
_scopes.Add(scope);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScopesBuilder AddScopes(IEnumerable<OAuthScope> scopes)
|
||||
{
|
||||
_scopes.AddRange(scopes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScopesBuilder AddScopes(params OAuthScope[] scopes)
|
||||
{
|
||||
_scopes.AddRange(scopes);
|
||||
return this;
|
||||
}
|
||||
|
||||
public string Build()
|
||||
{
|
||||
return string.Join(" ", _scopes.Select(TranslateOAuthScope));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Build();
|
||||
}
|
||||
|
||||
|
||||
private string? TranslateOAuthScope(OAuthScope scope)
|
||||
{
|
||||
return scope switch
|
||||
{
|
||||
OAuthScope.Identify => "identify",
|
||||
OAuthScope.Email => "email",
|
||||
OAuthScope.Connections => "connections",
|
||||
OAuthScope.Guilds => "guilds",
|
||||
OAuthScope.GuildsJoin => "guilds.join",
|
||||
OAuthScope.GuildsMembersRead => "guilds.members.read",
|
||||
OAuthScope.GdmJoin => "gdm.join",
|
||||
OAuthScope.Rpc => "rpc",
|
||||
OAuthScope.RpcNotificationsRead => "rpc.notifications.read",
|
||||
OAuthScope.RpcVoiceRead => "rpc.voice.read",
|
||||
OAuthScope.RpcVoiceWrite => "rpc.voice.write",
|
||||
OAuthScope.RpcActivitiesWrite => "rpc.activities.write",
|
||||
OAuthScope.Bot => "bot",
|
||||
OAuthScope.WebhookIncoming => "webhook.incoming",
|
||||
OAuthScope.MessagesRead => "messages.read",
|
||||
OAuthScope.ApplicationsBuildsUpload => "applications.builds.upload",
|
||||
OAuthScope.ApplicationsBuildsRead => "applications.builds.read",
|
||||
OAuthScope.ApplicationsCommands => "applications.commands",
|
||||
OAuthScope.ApplicationsStoreUpdate => "applications.store.update",
|
||||
OAuthScope.ApplicationsEntitlements => "applications.entitlements",
|
||||
OAuthScope.ActivitiesRead => "activities.read",
|
||||
OAuthScope.ActivitiesWrite => "activities.write",
|
||||
OAuthScope.RelationshipsRead => "relationships.read",
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
20
x3rt.DiscordOAuth2.csproj
Normal file
20
x3rt.DiscordOAuth2.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<Title>DiscordOAuth2</Title>
|
||||
<Authors>x3rt</Authors>
|
||||
<Description>Discord OAuth2 implementation for C# </Description>
|
||||
<AssemblyName>x3rt.DiscordOAuth2</AssemblyName>
|
||||
<RootNamespace>x3rt.DiscordOAuth2</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
15
x3rt.DiscordOAuth2.sln
Normal file
15
x3rt.DiscordOAuth2.sln
Normal file
@@ -0,0 +1,15 @@
|
||||
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}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{09C8F24A-5CC8-42E3-9D86-3DD68D6642E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{09C8F24A-5CC8-42E3-9D86-3DD68D6642E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{09C8F24A-5CC8-42E3-9D86-3DD68D6642E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{09C8F24A-5CC8-42E3-9D86-3DD68D6642E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
Reference in New Issue
Block a user