Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,4 @@ export * from './src/structures/threadChannel.ts'
export * from './src/structures/resolvable.ts'
export * from './src/utils/channelTypes.ts'
export * from './src/structures/messageSticker.ts'
export * from './src/utils/oauthURL.ts'
13 changes: 13 additions & 0 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type { DMChannel } from '../structures/dmChannel.ts'
import { Template } from '../structures/template.ts'
import { VoiceManager } from './voice.ts'
import { StickersManager } from '../managers/stickers.ts'
import { createOAuthURL, OAuthURLOptions } from '../utils/oauthURL.ts'

/** OS related properties sent with Gateway Identify */
export interface ClientProperties {
Expand Down Expand Up @@ -464,6 +465,18 @@ export class Client extends HarmonyEventEmitter<ClientEvents> {
const payload = await this.rest.api.guilds.templates[code].get()
return new Template(this, payload)
}

/** Creates an OAuth2 URL */
createOAuthURL(options: Omit<OAuthURLOptions, 'clientID'>): string {
return createOAuthURL(
Object.assign(
{
clientID: this.getEstimatedID()
},
options
)
)
}
}

/** Event decorator to create an Event handler from function */
Expand Down
23 changes: 23 additions & 0 deletions src/types/oauth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export type OAuthScope =
| 'identify'
| 'email'
| 'connections'
| 'guilds'
| 'guilds.join'
| 'gdm.join'
| 'rpc'
| 'rpc.notifications.read'
| 'rpc.voice.read'
| 'rpc.voice.write'
| 'rpc.activities.write'
| 'bot'
| 'webhook.incoming'
| 'messages.read'
| 'applications.builds.upload'
| 'applications.builds.read'
| 'applications.commands'
| 'applications.store.update'
| 'applications.entitlements'
| 'activities.read'
| 'activities.write'
| 'relationships.read'
4 changes: 2 additions & 2 deletions src/types/permissionFlags.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags

export const PermissionFlags: { [key: string]: bigint } = {
export const PermissionFlags = {
CREATE_INSTANT_INVITE: 1n << 0n,
KICK_MEMBERS: 1n << 1n,
BAN_MEMBERS: 1n << 2n,
Expand Down Expand Up @@ -46,4 +46,4 @@ export const PermissionFlags: { [key: string]: bigint } = {
USE_PUBLIC_THREADS: 1n << 35n,
USE_PRIVATE_THREADS: 1n << 36n,
USE_EXTERNAL_STICKERS: 1n << 37n
}
} as const
68 changes: 68 additions & 0 deletions src/utils/oauthURL.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { PermissionFlags } from '../../mod.ts'
import { Constants } from '../types/constants.ts'
import type { OAuthScope } from '../types/oauth.ts'
import { Permissions } from './permissions.ts'

export interface OAuthURLOptions {
clientID: string
scopes: OAuthScope[]
permissions?:
| string
| bigint
| Permissions
| Array<keyof typeof PermissionFlags | bigint>
redirectURI?: string
}

export function createOAuthURL(options: OAuthURLOptions): string {
if (options.scopes.length < 1)
throw new Error('Must provide at least one scope')

const params = new URLSearchParams({
client_id: options.clientID,
scopes: [new Set(options.scopes)].join(' ')
})

if (options.permissions !== undefined) {
let perms: string

if (
typeof options.permissions === 'string' ||
typeof options.permissions === 'bigint'
) {
perms = String(options.permissions)
} else if (
typeof options.permissions === 'object' &&
options.permissions !== null &&
options.permissions instanceof Permissions
) {
perms = String(options.permissions.bitfield)
} else if (Array.isArray(options.permissions)) {
let acum = 0n
for (const perm of options.permissions) {
if (typeof perm === 'string') {
const flag = PermissionFlags[perm]
if (typeof flag !== 'number')
throw new TypeError(`Invalid Permission Flag: ${flag}`)
acum |= flag
} else if (typeof perm === 'bigint') {
acum |= perm
} else throw new TypeError('Unexpected value in permissions array')
}
perms = String(acum)
} else throw new TypeError(`Unexpected value for permissions`)
params.set('permissions', perms)
}

if (options.permissions === undefined && options.scopes.includes('bot')) {
params.set('permissions', '0')
}

if (typeof options.redirectURI === 'string') {
params.set('redirect_uri', options.redirectURI)
}

return `${Constants.DISCORD_API_URL}/${
Constants.DISCORD_API_VERSION
}/oauth2/authorize?${params.toString()}`
}