mirror of
https://github.com/discordeno/discordeno.git
synced 2026-05-21 02:40:08 +00:00
feat: Community Invites (#4685)
* api-docs: Community Invites Add support for invites that gives roles to users. Add support for target users on invites. Sort-of unrleated change required: `restManager.makeRequest` `resolve` function had to changed or else the `getTargetUsers` would hang forever due to a JSON parsing issue. * fix type error & add bot helpers
This commit is contained in:
@@ -493,6 +493,7 @@ export function createDesiredPropertiesObject<T extends RecursivePartial<Transfo
|
||||
guildScheduledEvent: defaultValue,
|
||||
expiresAt: defaultValue,
|
||||
flags: defaultValue,
|
||||
roles: defaultValue,
|
||||
...desiredProperties.invite,
|
||||
},
|
||||
member: {
|
||||
|
||||
@@ -46,6 +46,7 @@ import type {
|
||||
DiscordInviteMetadata,
|
||||
DiscordListArchivedThreads,
|
||||
DiscordPrunedCount,
|
||||
DiscordTargetUsersJobStatus,
|
||||
DiscordTokenExchange,
|
||||
DiscordTokenRevocation,
|
||||
DiscordVanityUrl,
|
||||
@@ -626,6 +627,15 @@ export function createBotHelpers<TProps extends TransformersDesiredProperties, T
|
||||
unlinkChannelToLobby: async (lobbyId, bearerToken) => {
|
||||
return bot.transformers.lobby(bot, snakelize(await bot.rest.unlinkChannelToLobby(lobbyId, bearerToken)));
|
||||
},
|
||||
getTargetUsers: async (inviteCode) => {
|
||||
return await bot.rest.getTargetUsers(inviteCode);
|
||||
},
|
||||
updateTargetUsers: async (inviteCode, targetUsersFile) => {
|
||||
await bot.rest.updateTargetUsers(inviteCode, targetUsersFile);
|
||||
},
|
||||
getTargetUsersJobStatus: async (inviteCode) => {
|
||||
return await bot.rest.getTargetUsersJobStatus(inviteCode);
|
||||
},
|
||||
// All useless void return functions here
|
||||
addReaction: async (channelId, messageId, reaction) => {
|
||||
return await bot.rest.addReaction(channelId, messageId, reaction);
|
||||
@@ -1119,6 +1129,9 @@ export type BotHelpers<TProps extends TransformersDesiredProperties, TBehavior e
|
||||
addMemberToLobby: (lobbyId: BigString, userId: BigString, options: AddLobbyMember) => Promise<SetupDesiredProps<LobbyMember, TProps, TBehavior>>;
|
||||
linkChannelToLobby: (lobbyId: BigString, bearerToken: string, options: LinkChannelToLobby) => Promise<SetupDesiredProps<Lobby, TProps, TBehavior>>;
|
||||
unlinkChannelToLobby: (lobbyId: BigString, bearerToken: string) => Promise<SetupDesiredProps<Lobby, TProps, TBehavior>>;
|
||||
getTargetUsers: (inviteCode: string) => Promise<string>;
|
||||
updateTargetUsers: (inviteCode: string, targetUsersFile: Blob) => Promise<void>;
|
||||
getTargetUsersJobStatus: (inviteCode: string) => Promise<Camelize<DiscordTargetUsersJobStatus>>;
|
||||
// functions return Void so dont need any special handling
|
||||
addReaction: (channelId: BigString, messageId: BigString, reaction: string) => Promise<void>;
|
||||
addReactions: (channelId: BigString, messageId: BigString, reactions: string[], ordered?: boolean) => Promise<void>;
|
||||
|
||||
@@ -35,6 +35,7 @@ export function transformInvite(bot: Bot, payload: DiscordInviteCreate | Discord
|
||||
invite.expiresAt = Date.parse(payload.expires_at);
|
||||
}
|
||||
if (props.flags && payload.flags) invite.flags = new ToggleBitfield(payload.flags);
|
||||
if (props.roles && payload.roles) invite.roles = payload.roles.map((role) => bot.transformers.role(bot, role));
|
||||
} else {
|
||||
if (props.channelId && payload.channel_id) invite.channelId = bot.transformers.snowflake(payload.channel_id);
|
||||
if (props.guildId && payload.guild_id) invite.guildId = bot.transformers.snowflake(payload.guild_id);
|
||||
|
||||
@@ -1142,6 +1142,10 @@ export interface Invite {
|
||||
approximatePresenceCount?: number;
|
||||
/** Guild invite flags for guild invites. */
|
||||
flags?: ToggleBitfield;
|
||||
/**
|
||||
* The roles assigned to the user upon accepting the invite
|
||||
*/
|
||||
roles?: Role[];
|
||||
}
|
||||
|
||||
export interface Member {
|
||||
|
||||
@@ -48,6 +48,7 @@ import type {
|
||||
DiscordSticker,
|
||||
DiscordStickerPack,
|
||||
DiscordSubscription,
|
||||
DiscordTargetUsersJobStatus,
|
||||
DiscordTemplate,
|
||||
DiscordThreadMember,
|
||||
DiscordUser,
|
||||
@@ -647,7 +648,7 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
|
||||
await rest.processRequest(payload);
|
||||
},
|
||||
resolve: (data) => {
|
||||
resolve(data.status !== 204 ? (typeof data.body === 'string' ? JSON.parse(data.body) : data.body) : undefined);
|
||||
resolve(data.status !== 204 ? (data.body as Parameters<typeof resolve>[0]) : undefined!);
|
||||
},
|
||||
reject: (reason) => {
|
||||
let errorText: string;
|
||||
@@ -810,7 +811,23 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
|
||||
},
|
||||
|
||||
async createInvite(channelId, body = {}, reason) {
|
||||
return await rest.post<DiscordInvite>(rest.routes.channels.invites(channelId), { body, reason });
|
||||
if (!body.targetUsersFile) {
|
||||
return await rest.post<DiscordInvite>(rest.routes.channels.invites(channelId), { body, reason });
|
||||
}
|
||||
|
||||
// When we have to upload a file, we need to use FormData, and each field has to be appended individually
|
||||
const form = new FormData();
|
||||
|
||||
for (const [key, value] of Object.entries(body)) {
|
||||
if (key !== 'targetUsersFile' && key !== 'roleIds') {
|
||||
form.append(camelToSnakeCase(key), rest.changeToDiscordFormat(value).toString());
|
||||
}
|
||||
}
|
||||
|
||||
form.append('target_users_file', body.targetUsersFile);
|
||||
if (body.roleIds) form.append('role_ids', body.roleIds.map((x) => x.toString()).join(','));
|
||||
|
||||
return await rest.post<DiscordInvite>(rest.routes.channels.invites(channelId), { body: form, reason });
|
||||
},
|
||||
|
||||
async getGuildRoleMemberCounts(guildId) {
|
||||
@@ -889,6 +906,21 @@ export function createRestManager(options: CreateRestManagerOptions): RestManage
|
||||
await rest.delete(rest.routes.guilds.invite(inviteCode), { reason });
|
||||
},
|
||||
|
||||
async getTargetUsers(inviteCode) {
|
||||
return await rest.get<string>(rest.routes.guilds.inviteTargetUsers(inviteCode));
|
||||
},
|
||||
|
||||
async updateTargetUsers(inviteCode, targetUsersFile) {
|
||||
const form = new FormData();
|
||||
form.append('target_users_file', targetUsersFile);
|
||||
|
||||
await rest.put(rest.routes.guilds.inviteTargetUsers(inviteCode), { body: form });
|
||||
},
|
||||
|
||||
async getTargetUsersJobStatus(inviteCode) {
|
||||
return await rest.get<DiscordTargetUsersJobStatus>(rest.routes.guilds.inviteTargetUsersJobStatus(inviteCode));
|
||||
},
|
||||
|
||||
async deleteMessage(channelId, messageId, reason) {
|
||||
await rest.delete(rest.routes.channels.message(channelId, messageId), { reason });
|
||||
},
|
||||
|
||||
@@ -373,6 +373,12 @@ export function createRoutes(disableURIEncode: boolean = false): RestRoutes {
|
||||
|
||||
return url;
|
||||
},
|
||||
inviteTargetUsers(inviteCode) {
|
||||
return `/invites/${encode(inviteCode)}/target-users`;
|
||||
},
|
||||
inviteTargetUsersJobStatus(inviteCode) {
|
||||
return `/invites/${encode(inviteCode)}/target-users/job-status`;
|
||||
},
|
||||
invites: (guildId) => {
|
||||
return `/guilds/${encode(guildId)}/invites`;
|
||||
},
|
||||
|
||||
@@ -79,6 +79,7 @@ import type {
|
||||
DiscordSticker,
|
||||
DiscordStickerPack,
|
||||
DiscordSubscription,
|
||||
DiscordTargetUsersJobStatus,
|
||||
DiscordTemplate,
|
||||
DiscordThreadMember,
|
||||
DiscordTokenExchange,
|
||||
@@ -809,6 +810,44 @@ export interface RestManager {
|
||||
* @see {@link https://discord.com/developers/docs/resources/channel#delete-channel-invite}
|
||||
*/
|
||||
deleteInvite: (inviteCode: string, reason?: string) => Promise<void>;
|
||||
/**
|
||||
* Gets the users allowed to see and accept this invite.
|
||||
*
|
||||
* @param inviteCode - The invite code of the invite to update.
|
||||
* @returns CSV file with a single column `Users` containing the user IDs.
|
||||
*
|
||||
* @remarks
|
||||
* Requires the `MANAGE_GUILD` permission.
|
||||
*
|
||||
* @see {@link https://discord.com/developers/docs/resources/invite#get-target-users}
|
||||
*/
|
||||
getTargetUsers: (inviteCode: string) => Promise<string>;
|
||||
/**
|
||||
* Updates the users allowed to see and accept this invite.
|
||||
*
|
||||
* @param inviteCode - The invite code of the invite to update.
|
||||
* @param targetUsersFile - A CSV file with a single column of user IDs for all the users able to accept this invite
|
||||
*
|
||||
* @remarks
|
||||
* Requires the `MANAGE_GUILD` permission.
|
||||
*
|
||||
* Uploading a file with invalid user IDs will result in a 400 with the invalid IDs described.
|
||||
*
|
||||
* @see {@link https://discord.com/developers/docs/resources/invite#update-target-users}
|
||||
*/
|
||||
updateTargetUsers: (inviteCode: string, targetUsersFile: Blob) => Promise<void>;
|
||||
/**
|
||||
* Processing target users from a CSV when creating or updating an invite is done asynchronously. This endpoint allows you to check the status of that job.
|
||||
*
|
||||
* @param inviteCode - The invite code of the invite to check the target users job status for.
|
||||
* @returns An object containing the status of the target users job.
|
||||
*
|
||||
* @remarks
|
||||
* Requires the `MANAGE_GUILD` permission.
|
||||
*
|
||||
* @see {@link https://discord.com/developers/docs/resources/invite#get-target-users-job-status}
|
||||
*/
|
||||
getTargetUsersJobStatus: (inviteCode: string) => Promise<Camelize<DiscordTargetUsersJobStatus>>;
|
||||
/**
|
||||
* Deletes a message from a channel.
|
||||
*
|
||||
|
||||
@@ -157,6 +157,10 @@ export interface RestRoutes {
|
||||
integrations: (guildId: BigString) => string;
|
||||
/** Route for handling a specific guild invite. */
|
||||
invite: (inviteCode: string, options?: GetInvite) => string;
|
||||
/** Route for a specific invite's target users */
|
||||
inviteTargetUsers: (inviteCode: string) => string;
|
||||
/** Route for a specific invite's target users */
|
||||
inviteTargetUsersJobStatus: (inviteCode: string) => string;
|
||||
/** Route for handling non-specific invites in a guild. */
|
||||
invites: (guildId: BigString) => string;
|
||||
/** Route for handling a bot leaving a guild. */
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { DiscordApplication } from './application.js';
|
||||
import type { DiscordChannel } from './channel.js';
|
||||
import type { DiscordGuild, DiscordMember } from './guild.js';
|
||||
import type { DiscordScheduledEvent } from './guildScheduledEvent.js';
|
||||
import type { DiscordRole } from './permissions.js';
|
||||
import type { DiscordUser } from './user.js';
|
||||
|
||||
/** https://discord.com/developers/docs/resources/invite#invite-object-invite-structure */
|
||||
@@ -38,6 +39,10 @@ export interface DiscordInvite {
|
||||
* @see {@link DiscordGuildInviteFlags}
|
||||
*/
|
||||
flags?: number;
|
||||
/**
|
||||
* The roles assigned to the user upon accepting the invite
|
||||
*/
|
||||
roles?: DiscordRole[];
|
||||
}
|
||||
|
||||
/** https://discord.com/developers/docs/resources/invite#invite-object-invite-types */
|
||||
@@ -87,3 +92,30 @@ export interface DiscordInviteStageInstance {
|
||||
/** The topic of the Stage instance (1-120 characters) */
|
||||
topic: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/resources/invite#get-target-users-job-status-example-response
|
||||
*
|
||||
* @remarks
|
||||
* Discord does not seem to actually document the type for this response, so this is based on the example provided
|
||||
*/
|
||||
export interface DiscordTargetUsersJobStatus {
|
||||
status: DiscordGetTargetUsersJobStatusErrorCodes;
|
||||
total_users: number;
|
||||
processed_users: number;
|
||||
created_at: string;
|
||||
completed_at: string | null;
|
||||
error_message: string | null;
|
||||
}
|
||||
|
||||
/** https://discord.com/developers/docs/resources/invite#get-target-users-job-status-error-codes */
|
||||
export enum DiscordGetTargetUsersJobStatusErrorCodes {
|
||||
/** The default value. */
|
||||
Unspecified,
|
||||
/** The job is still being processed. */
|
||||
Processing,
|
||||
/** The job has been completed successfully. */
|
||||
Completed,
|
||||
/** The job has failed, see `error_message` field for more details. */
|
||||
Failed,
|
||||
}
|
||||
|
||||
@@ -159,6 +159,10 @@ export enum HTTPJsonErrorCodes {
|
||||
UnknownTag = 10087,
|
||||
/** Unknown sound */
|
||||
UnknownSound = 10097,
|
||||
/** Unknown invite target users job (invite exists but has no target users) */
|
||||
UnknownInviteTargetUsersJob = 10124,
|
||||
/** Unknown invite target users (invite exists but has no target users) */
|
||||
UnknownInviteTargetUsers = 10129,
|
||||
|
||||
/** Bots cannot use this endpoint */
|
||||
BotsCannotUseThis = 20001,
|
||||
|
||||
@@ -300,6 +300,22 @@ export interface CreateChannelInvite {
|
||||
targetUserId?: BigString;
|
||||
/** The id of the embedded application to open for this invite, required if `target_type` is 2, the application must have the `EMBEDDED` flag */
|
||||
targetApplicationId?: BigString;
|
||||
/**
|
||||
* A csv file with a single column of user IDs for all the users able to accept this invite
|
||||
*
|
||||
* @remarks
|
||||
* Requires the `MANAGE_GUILD` permission.
|
||||
*
|
||||
* Uploading a file with invalid user IDs will result in a 400 with the invalid IDs described.
|
||||
*/
|
||||
targetUsersFile?: Blob;
|
||||
/**
|
||||
* The role ID(s) for roles in the guild given to the users that accept this invite
|
||||
*
|
||||
* @remarks
|
||||
* Requires the `MANAGE_ROLES` permission and cannot assign roles with higher permissions than the sender.
|
||||
*/
|
||||
roleIds?: BigString[];
|
||||
}
|
||||
|
||||
/** https://discord.com/developers/docs/resources/channel#group-dm-add-recipient-json-params */
|
||||
|
||||
Reference in New Issue
Block a user