-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
feat(invite): new endpoints and target users file and roles #11392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,12 @@ | ||
| 'use strict'; | ||
|
|
||
| const { Buffer } = require('node:buffer'); | ||
| const { Collection } = require('@discordjs/collection'); | ||
| const { Routes } = require('discord-api-types/v10'); | ||
| const { DiscordjsError, ErrorCodes } = require('../errors/index.js'); | ||
| const { GuildInvite } = require('../structures/GuildInvite.js'); | ||
| const { resolveInviteCode } = require('../util/DataResolver.js'); | ||
| const { resolveInviteCode, resolveFile } = require('../util/DataResolver.js'); | ||
| const { CachedManager } = require('./CachedManager.js'); | ||
|
|
||
| /** | ||
| * Manages API methods for GuildInvites and stores their cache. | ||
| * | ||
|
|
@@ -104,6 +104,18 @@ class GuildInviteManager extends CachedManager { | |
| * @property {boolean} [cache=true] Whether or not to cache the fetched invites | ||
| */ | ||
|
|
||
| /** | ||
| * Job status for target users of an invite | ||
| * | ||
| * @typedef {Object} TargetUsersJobStatusForInvite | ||
| * @property {InviteTargetUsersJobStatus} status The status of job processing the users | ||
| * @property {number} totalUsers The total number of users provided in the list | ||
| * @property {number} processedUsers The total number of users processed so far | ||
| * @property {Date|null} createdAt The time when the job was created | ||
| * @property {Date|null} completedAt The time when the job was successfully completed | ||
| * @property {string|null} errorMessage The error message if the job failed | ||
| */ | ||
|
|
||
| /** | ||
| * Fetches invite(s) from Discord. | ||
| * | ||
|
|
@@ -191,7 +203,7 @@ class GuildInviteManager extends CachedManager { | |
| /** | ||
| * Create an invite to the guild from the provided channel. | ||
| * | ||
| * @param {GuildInvitableChannelResolvable} channel The options for creating the invite from a channel. | ||
| * @param {GuildInvitableChannelResolvable} channel The channel where invite should be created. | ||
| * @param {InviteCreateOptions} [options={}] The options for creating the invite from a channel. | ||
| * @returns {Promise<GuildInvite>} | ||
| * @example | ||
|
|
@@ -202,23 +214,38 @@ class GuildInviteManager extends CachedManager { | |
| */ | ||
| async create( | ||
| channel, | ||
| { temporary, maxAge, maxUses, unique, targetUser, targetApplication, targetType, reason } = {}, | ||
| { | ||
| temporary, | ||
| maxAge, | ||
| maxUses, | ||
| unique, | ||
| targetUser, | ||
| targetApplication, | ||
| targetType, | ||
| roles, | ||
| targetUsersFile, | ||
| reason, | ||
| } = {}, | ||
| ) { | ||
| const id = this.guild.channels.resolveId(channel); | ||
| if (!id) throw new DiscordjsError(ErrorCodes.GuildChannelResolve); | ||
|
|
||
| const options = { | ||
| temporary, | ||
| max_age: maxAge, | ||
| max_uses: maxUses, | ||
| unique, | ||
| target_user_id: this.client.users.resolveId(targetUser), | ||
| target_application_id: targetApplication?.id ?? targetApplication?.applicationId ?? targetApplication, | ||
| role_ids: roles?.map(role => this.guild.roles.resolveId(role)), | ||
| target_type: targetType, | ||
| }; | ||
| const invite = await this.client.rest.post(Routes.channelInvites(id), { | ||
| body: { | ||
| temporary, | ||
| max_age: maxAge, | ||
| max_uses: maxUses, | ||
| unique, | ||
| target_user_id: this.client.users.resolveId(targetUser), | ||
| target_application_id: targetApplication?.id ?? targetApplication?.applicationId ?? targetApplication, | ||
| target_type: targetType, | ||
| }, | ||
| body: targetUsersFile ? await this._createInviteFormData({ targetUsersFile, ...options }) : options, | ||
| // This is necessary otherwise rest stringifies the body | ||
| passThroughBody: Boolean(targetUsersFile), | ||
| reason, | ||
| }); | ||
|
|
||
| return new GuildInvite(this.client, invite); | ||
| } | ||
|
|
||
|
|
@@ -234,6 +261,78 @@ class GuildInviteManager extends CachedManager { | |
|
|
||
| await this.client.rest.delete(Routes.invite(code), { reason }); | ||
| } | ||
|
|
||
| /** | ||
| * Get target users for an invite | ||
| * | ||
| * @param {InviteResolvable} invite The invite to get the target users | ||
| * @returns {Buffer} The csv file containing target users | ||
| */ | ||
| async fetchTargetUsers(invite) { | ||
| const code = resolveInviteCode(invite); | ||
| const arrayBuff = await this.client.rest.get(Routes.inviteTargetUsers(code)); | ||
|
|
||
| return Buffer.from(arrayBuff); | ||
| } | ||
|
|
||
| /** | ||
| * Updates target users for an invite | ||
| * | ||
| * @param {InviteResolvable} invite The invite to update the target users | ||
| * @param {UserResolvable[]|BufferResolvable} targetUsersFile An array of users or a csv file with a single column of user IDs | ||
| * for all the users able to accept this invite | ||
| * @returns {Promise<unknown>} | ||
| */ | ||
| async updateTargetUsers(invite, targetUsersFile) { | ||
| const code = resolveInviteCode(invite); | ||
|
|
||
| return this.client.rest.put(Routes.inviteTargetUsers(code), { | ||
| body: await this._createInviteFormData({ targetUsersFile }), | ||
| // This is necessary otherwise rest stringifies the body | ||
| passThroughBody: true, | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Get status of the job processing target users of an invite | ||
| * | ||
| * @param {InviteResolvable} invite The invite to get the target users for | ||
| * @returns {TargetUsersJobStatusForInvite[]} The target users | ||
| */ | ||
| async fetchTargetUsersJobStatus(invite) { | ||
| const code = resolveInviteCode(invite); | ||
| const job = await this.client.rest.get(Routes.inviteTargetUsersJobStatus(code)); | ||
| return { | ||
| status: job.status, | ||
| totalUsers: job.total_users, | ||
| processedUsers: job.processed_users, | ||
| createdAt: job.created_at ? new Date(job.created_at) : null, | ||
| completedAt: job.completed_at ? new Date(job.completed_at) : null, | ||
| errorMessage: job.error_message ?? null, | ||
| }; | ||
| } | ||
imnaiyar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Creates form data body payload for invite | ||
| * | ||
| * @param {InviteCreateOptions} options The options for creating invite | ||
| * @returns {Promise<FormData>} | ||
| * @private | ||
| */ | ||
| async _createInviteFormData({ targetUsersFile, ...rest } = {}) { | ||
| const formData = new FormData(); | ||
| let usersCsv; | ||
| if (Array.isArray(targetUsersFile)) { | ||
| usersCsv = targetUsersFile.map(user => this.client.users.resolveId(user)).join('\n'); | ||
| } else { | ||
| const resolved = await resolveFile(targetUsersFile); | ||
| usersCsv = resolved.data.toString('utf8'); | ||
| } | ||
|
|
||
| formData.append('target_users_file', new Blob([usersCsv], { type: 'text/csv' }), 'users.csv'); | ||
| formData.append('payload_json', JSON.stringify(rest)); | ||
| return formData; | ||
|
Comment on lines
+322
to
+334
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check Node engine requirement
echo "=== Node Engine Requirement ==="
rg -n "engines" packages/discord.js/package.json
echo -e "\n=== FormData/Blob usage in discord.js src ==="
rg -n "FormData\|Blob" packages/discord.js/src
echo -e "\n=== FormData/Blob imports ==="
rg -n "import.*FormData\|import.*Blob\|require.*FormData\|require.*Blob" packages/discord.js/src
echo -e "\n=== undici or polyfill usage ==="
rg -n "undici\|formdata" packages/discord.js/srcRepository: discordjs/discord.js Length of output: 226 🏁 Script executed: #!/bin/bash
# Locate and examine resolveId function
echo "=== Finding resolveId implementation ==="
rg -n "resolveId" packages/discord.js/src -A 5 -B 2 | head -100Repository: discordjs/discord.js Length of output: 9356 🏁 Script executed: #!/bin/bash
# Check package.json dependencies for any polyfills
echo "=== Checking for polyfill dependencies ==="
cat packages/discord.js/package.json | jq '.dependencies, .devDependencies' | grep -i "form\|blob\|fetch\|undici"Repository: discordjs/discord.js Length of output: 248 🏁 Script executed: #!/bin/bash
# Get the full engines object from package.json
echo "=== Full engines requirement ==="
cat packages/discord.js/package.json | jq '.engines'
echo -e "\n=== Check GuildInviteManager.js for imports ==="
head -50 packages/discord.js/src/managers/GuildInviteManager.jsRepository: discordjs/discord.js Length of output: 1479 🏁 Script executed: #!/bin/bash
# Find BaseManager to understand resolveId behavior
echo "=== BaseManager resolveId implementation ==="
rg -n "class BaseManager" packages/discord.js/src -A 50 | head -100
echo -e "\n=== resolveId implementation in BaseManager ==="
rg -n "resolveId.*{" packages/discord.js/src/managers/BaseManager.js -A 5Repository: discordjs/discord.js Length of output: 1265 🏁 Script executed: #!/bin/bash
# Get the full context of _createInviteFormData method
echo "=== Full _createInviteFormData method ==="
sed -n '322,334p' packages/discord.js/src/managers/GuildInviteManager.jsRepository: discordjs/discord.js Length of output: 668 🏁 Script executed: #!/bin/bash
# Find resolveId implementation in BaseManager or UserManager
echo "=== Find resolveId in BaseManager.js ==="
cat packages/discord.js/src/managers/BaseManager.js
echo -e "\n=== Find resolveId in UserManager.js ==="
rg -n "resolveId" packages/discord.js/src/managers/UserManager.js -A 8Repository: discordjs/discord.js Length of output: 1807 🏁 Script executed: #!/bin/bash
# Search for FormData usage to see how it's imported/used across the codebase
echo "=== FormData usage in GuildInviteManager and vicinity ==="
grep -n "FormData\|new Blob" packages/discord.js/src/managers/GuildInviteManager.js
echo -e "\n=== Check if there are any imports for FormData at top of file ==="
head -20 packages/discord.js/src/managers/GuildInviteManager.js | grep -i "form\|blob\|import\|require"Repository: discordjs/discord.js Length of output: 1048 🏁 Script executed: #!/bin/bash
# Get a better understanding of how resolveId behaves in UserManager
echo "=== UserManager resolve/resolveId methods ==="
rg -n "resolve\(" packages/discord.js/src/managers/UserManager.js -B 2 -A 8 | head -80Repository: discordjs/discord.js Length of output: 558 🏁 Script executed: #!/bin/bash
# Check CachedManager and parent classes for the actual resolveId implementation
echo "=== CachedManager.js ==="
cat packages/discord.js/src/managers/CachedManager.js
echo -e "\n=== Check parent class chain in managers ==="
head -30 packages/discord.js/src/managers/CachedManager.jsRepository: discordjs/discord.js Length of output: 2474 🏁 Script executed: #!/bin/bash
# See how resolveId is actually implemented in the full chain
rg -n "class.*Manager extends" packages/discord.js/src/managers/CachedManager.js
echo -e "\n=== Look for the actual resolveId implementation ==="
rg -n "resolveId" packages/discord.js/src/managers/CachedManager.js -B 2 -A 6Repository: discordjs/discord.js Length of output: 162 🏁 Script executed: #!/bin/bash
# Check DataManager.js for resolveId implementation
echo "=== DataManager.js ==="
cat packages/discord.js/src/managers/DataManager.jsRepository: discordjs/discord.js Length of output: 1736 Filter out unresolved user IDs to prevent invalid entries in the CSV. When FormData and Blob are available as stable globals in Node 22.12.0+ (the required minimum version), so no polyfill is needed. Proposed fix- usersCsv = targetUsersFile.map(user => this.client.users.resolveId(user)).join('\n');
+ const userIds = targetUsersFile
+ .map(user => this.client.users.resolveId(user))
+ .filter(Boolean);
+ usersCsv = userIds.join('\n');🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
|
||
| exports.GuildInviteManager = GuildInviteManager; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid serializing unresolved role IDs.
roles?.map(...resolveId)can yieldnull, which becomesnullentries inrole_idsand can trigger 400s from the API. Filter (or fail fast) before POST.Proposed fix (filter unresolved IDs)
🤖 Prompt for AI Agents