Initial release

This commit is contained in:
x3rt
2023-04-26 18:19:56 -06:00
commit d946bdc47c
16 changed files with 1027 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
.idea

217
DiscordOAuth.cs Normal file
View 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);
}
}

View 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
View 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
View 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;
}
}

View 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
}

View 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,
}

View File

@@ -0,0 +1,9 @@
namespace x3rt.DiscordOAuth2.Entities.Enums;
public enum PremiumType
{
None = 0,
NitroClassic = 1,
Nitro = 2,
NitroBasic = 3
}

View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,42 @@
# x3rt.DiscordOAuth2
[![NuGet](https://img.shields.io/nuget/v/x3rt.DiscordOAuth2.svg)](https://www.nuget.org/packages/x3rt.DiscordOAuth2/)
[![NuGet](https://img.shields.io/nuget/dt/x3rt.DiscordOAuth2.svg)](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
View 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
View 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
View 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