From c0a9b7a448bbb2d6fde2820fc0db6653d26d4be9 Mon Sep 17 00:00:00 2001 From: Regalijan <72576136+Regalijan@users.noreply.github.com> Date: Tue, 6 May 2025 19:05:22 -0400 Subject: [PATCH 01/28] Request forwarding for CI (#863) * Add request manipulation for ci testing in http module * URL -> HOSTNAME and fix auth header * Here goes nothing * Curse you standard * Use node 22 for tests * Revert back to 20 22 works but none of the other workflows use it * URL -> HOSTNAME (again) * Make requests actually route through forwarder * Add api key to env * Move header setting to jar check * Actually fix it this time * Add comments on request forwarder --- .github/workflows/run-tests.yml | 27 ++++++++++++++++++++++----- lib/util/http.js | 17 +++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 1473bc1ff..c9d839694 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -6,27 +6,44 @@ on: jobs: test: + permissions: + id-token: write + runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up node - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: 20 - - name: Install yarn - run: npm install -g yarn + - name: Enable Corepack + run: corepack enable - name: Install dependencies - run: yarn install + run: yarn install --frozen-lockfile - name: Lint lib/ run: yarn lint + + - id: auth + name: Generate ID Token + uses: google-github-actions/auth@v2 + with: + token_format: id_token + workload_identity_provider: ${{ vars.workload_identity_provider }} + service_account: ${{ vars.service_account }} + id_token_audience: "https://${{ vars.forwarder_hostname }}" + id_token_include_email: true + - name: Run tests env: + API_KEY: ${{ secrets.API_KEY }} COOKIE: ${{ secrets.COOKIE }} COOKIE_2: ${{ secrets.COOKIE_2 }} + FORWARDER_HOSTNAME: ${{ vars.forwarder_hostname }} + ID_TOKEN: ${{ steps.auth.outputs.id_token }} run: yarn test diff --git a/lib/util/http.js b/lib/util/http.js index 80279f5a8..d38c7dd42 100644 --- a/lib/util/http.js +++ b/lib/util/http.js @@ -67,6 +67,23 @@ function http (url, opt) { if (url.indexOf('http') !== 0) { url = 'https:' + url } + + /* + In CI, actions does not allow us to use a static ip address (nor guarantees any particular location) + This is a problem as Roblox locks sessions to a particular region + Therefore, we intercept the request during testing and send it to the forwarder (which has a static ip address), the Roblox hostname is set as the Destination-Host header + The ID token is for Google Cloud and allows authenticating with the Cloud Run service that acts as our forwarder + Requests without headers do not go through the forwarder as they are unauthenticated and do not need forwarding + */ + if (process?.env.CI && process.env.FORWARDER_HOSTNAME && opt.headers) { + const urlObj = new URL(url) + const { hostname } = urlObj + + opt.headers['Destination-Host'] = hostname + opt.headers['x-serverless-authorization'] = `Bearer ${process.env.ID_TOKEN}` + urlObj.hostname = process.env.FORWARDER_HOSTNAME + url = urlObj.href + } return request(url, opt) } From 5840266b16b1e2c802cb9a8d97e28ff002fed3ea Mon Sep 17 00:00:00 2001 From: mutex_lock <9419610+mutex-lock@users.noreply.github.com> Date: Mon, 9 Jun 2025 21:30:27 -0400 Subject: [PATCH 02/28] Fixes #864 (#865) Support caching the value `0` --- lib/cache/addIf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cache/addIf.js b/lib/cache/addIf.js index 41441bb7a..1f55f4543 100644 --- a/lib/cache/addIf.js +++ b/lib/cache/addIf.js @@ -7,7 +7,7 @@ module.exports = function (cache, type, index, callbacks) { const got = get(cache, type, index) const item = got[0] const refresh = got[1] - if (item) { + if (item != null) { callbacks.done(item) if (refresh) { const group = cache[type] From bf07f5fc48cc62e63432eba822908d2148310ca8 Mon Sep 17 00:00:00 2001 From: Regalijan <72576136+Regalijan@users.noreply.github.com> Date: Tue, 29 Jul 2025 15:46:59 -0400 Subject: [PATCH 03/28] Remove buy() (#867) * Delete lib/economy/buy.js * Remove BuyAssetResponse type * Remove BuyProductInfo and BuyAssetResponse * Remove it all this time --- lib/economy/buy.js | 99 ---------------------------------------------- typings/index.d.ts | 17 -------- typings/jsDocs.ts | 18 --------- 3 files changed, 134 deletions(-) delete mode 100644 lib/economy/buy.js diff --git a/lib/economy/buy.js b/lib/economy/buy.js deleted file mode 100644 index ec5b57596..000000000 --- a/lib/economy/buy.js +++ /dev/null @@ -1,99 +0,0 @@ -// Includes -const http = require('../util/http.js').func -const getProductInfo = require('../asset/getProductInfo.js').func -const getGeneralToken = require('../util/getGeneralToken.js').func - -// Args -exports.required = [['asset', 'product']] -exports.optional = ['price', 'jar'] - -// Docs -/** - * 🔐 Buy an asset from the marketplace. - * @category Assets - * @param {number} asset - The ID of the product. - * @param {number=} price - The price of the product. - * @returns {Promise} - * @example const noblox = require("noblox.js") - * // Login using your cookie - * noblox.buy(1117747196) -**/ - -// Define -function buy (jar, token, product, price) { - const robux = product.PriceInRobux || 0 - const productId = product.ProductId - if (price) { - if (typeof price === 'number') { - if (robux !== price) { - throw new Error('Price requirement not met. Requested price: ' + price + ' Actual price: ' + robux) - } - } else if (typeof price === 'object') { - const high = price.high - const low = price.low - if (high) { - if (robux > high) { - throw new Error('Price requirement not met. Requested price: <=' + high + ' Actual price: ' + robux) - } - } - if (low) { - if (robux < low) { - throw new Error('Price requirement not met. Requested price: >=' + low + ' Actual price: ' + robux) - } - } - } - } - const httpOpt = { - url: '//economy.roblox.com/v1/purchases/products/' + productId, - options: { - method: 'POST', - jar, - headers: { - 'X-CSRF-TOKEN': token - }, - json: { - expectedCurrency: 1, - expectedPrice: robux, - expectedSellerId: product.Creator.Id - } - } - } - return http(httpOpt) - .then(function (json) { - let err = json.errorMsg - if (json.reason === 'InsufficientFunds') { - err = 'You need ' + json.shortfallPrice + ' more robux to purchase this item.' - } else if (json.errorMsg) { - err = json.errorMsg - } - if (!err) { - return { productId, price: robux } - } else { - throw new Error(err) - } - }) -} - -function runWithToken (args) { - const jar = args.jar - return getGeneralToken({ - jar - }) - .then(function (token) { - return buy(jar, token, args.product, args.price) - }) -} - -exports.func = function (args) { - if (!args.product) { - return getProductInfo({ - asset: args.asset - }) - .then(function (product) { - args.product = product - return runWithToken(args) - }) - } else { - return runWithToken(args) - } -} diff --git a/typings/index.d.ts b/typings/index.d.ts index b81be4ab5..efbc1c7d6 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -220,23 +220,11 @@ declare module "noblox.js" { type GamePassProductInfo = Omit; - interface BuyProductInfo { - ProductId: number; - Creator: { Id: number }; - PriceInRobux: number; - UserAssetId: number; - } - interface PriceRange { high: number; low: number; } - interface BuyAssetResponse { - productId: number; - price: number; - } - interface ChartDataPointResponse { value?: number; date?: Date; @@ -1759,11 +1747,6 @@ declare module "noblox.js" { /// Economy - /** - * 🔐 Buys asset `asset` with `price` restrictions. This can be a single value or an object with `high` and `low` that sets the respective price limits (both inclusive). This allows you to buy assets with a minimum or maximum amount of robux that can be used or a single required value and therefore guarantees you can't be scammed by a sudden price change. If a price restriction is not set, the asset will be bought for however much it costs (works with free assets). You are able to use product instead of asset, the options in `product` are collected automatically if not provided. - */ - function buy(asset: number | ProductInfo | BuyProductInfo, price?: number | PriceRange, jar?: CookieJar): Promise; - /** * 🔓 Gets the amount of Robux in a group. */ diff --git a/typings/jsDocs.ts b/typings/jsDocs.ts index 68f660111..c6f3ad2c2 100644 --- a/typings/jsDocs.ts +++ b/typings/jsDocs.ts @@ -197,16 +197,6 @@ type ProductInfo = { */ type GamePassProductInfo = Omit; -/** - * @typedef -*/ -type BuyProductInfo = { - ProductId: number; - Creator: { Id: number }; - PriceInRobux: number; - UserAssetId: number; -} - /** * @typedef */ @@ -215,14 +205,6 @@ type PriceRange = { low: number; } -/** - * @typedef -*/ -type BuyAssetResponse = { - productId: number; - price: number; -} - /** * @typedef */ From f8f4caba81b5f7338fbd3ba5c37f235dcea4512a Mon Sep 17 00:00:00 2001 From: Regalijan <72576136+Regalijan@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:12:29 -0400 Subject: [PATCH 04/28] Migrate methods to RobloxAPIError class (#855) * Here goes nothing * Add caller getter to RobloxAPIError * Fix linting * Migrate canManage and configureItem * Redo error parsing for addDeveloperProduct --- lib/accountinformation/getUserSocialLinks.js | 12 ++++----- lib/accountsettings/block.js | 9 ++----- lib/accountsettings/unblock.js | 9 ++----- lib/asset/deleteFromInventory.js | 20 +++----------- lib/asset/getGamePassProductInfo.js | 5 ++-- lib/asset/getProductInfo.js | 14 ++-------- lib/avatar/avatarRules.js | 3 ++- lib/avatar/currentlyWearing.js | 3 ++- lib/avatar/getAvatar.js | 3 ++- lib/avatar/getCurrentAvatar.js | 3 ++- lib/avatar/getRecentItems.js | 9 +++---- lib/avatar/outfitDetails.js | 3 ++- lib/avatar/outfits.js | 3 ++- lib/avatar/redrawAvatar.js | 5 ++-- lib/avatar/removeAssetId.js | 8 ++---- lib/avatar/setAvatarBodyColors.js | 5 ++-- lib/avatar/setAvatarScales.js | 5 ++-- lib/avatar/setPlayerAvatarType.js | 5 ++-- lib/avatar/setWearingAssets.js | 5 ++-- lib/avatar/wearAssetId.js | 8 ++---- lib/badges/getAwardedTimestamps.js | 7 ++--- lib/badges/getBadgeInfo.js | 3 ++- lib/badges/getGameBadges.js | 3 ++- lib/badges/updateBadgeInfo.js | 11 +++----- lib/chat/addUsersToConversation.js | 9 +++---- lib/chat/chatSettings.js | 3 ++- lib/chat/getChatMessages.js | 3 ++- lib/chat/getConversations.js | 9 ++----- lib/chat/getRolloutSettings.js | 3 ++- lib/chat/getUnreadConversationCount.js | 3 ++- lib/chat/getUnreadMessages.js | 3 ++- lib/chat/getUserConversations.js | 3 ++- lib/chat/markChatAsRead.js | 5 ++-- lib/chat/markChatAsSeen.js | 5 ++-- lib/chat/multiGetLatestMessages.js | 3 ++- lib/chat/removeFromGroupConversation.js | 9 +++---- lib/chat/renameGroupConversation.js | 5 ++-- lib/chat/sendChatMessage.js | 5 ++-- lib/chat/setChatUserTyping.js | 9 +++---- lib/chat/start121Conversation.js | 5 ++-- lib/chat/startCloudEditConversation.js | 5 ++-- lib/chat/startGroupConversation.js | 9 +++---- lib/datastores/deleteDatastoreEntry.js | 14 ++-------- lib/datastores/getDatastoreEntry.js | 14 ++-------- lib/datastores/getDatastoreEntryVersions.js | 14 ++-------- lib/datastores/getDatastoreKeys.js | 14 ++-------- lib/datastores/getDatastores.js | 14 ++-------- lib/datastores/incrementDatastoreEntry.js | 14 ++-------- lib/datastores/setDatastoreEntry.js | 14 ++-------- lib/develop/canManage.js | 12 ++++----- lib/develop/configureItem.js | 27 ++++++++++++------- lib/develop/updateUniverse.js | 11 ++++---- lib/develop/updateUniverseAccess.js | 9 +++---- lib/economy/getGroupFunds.js | 11 ++++---- lib/economy/getGroupRevenueSummary.js | 16 ++++------- lib/economy/getResaleData.js | 28 ++++++++------------ lib/economy/getUserFunds.js | 11 ++++---- lib/friends/acceptFriendRequest.js | 9 ++----- lib/friends/declineAllFriendRequests.js | 9 ++----- lib/friends/declineFriendRequest.js | 9 ++----- lib/friends/getFollowerCount.js | 5 ++-- lib/friends/getFollowers.js | 9 ++----- lib/friends/getFollowingCount.js | 5 ++-- lib/friends/getFollowings.js | 9 ++----- lib/friends/getFriendCount.js | 5 ++-- lib/friends/getFriendRequests.js | 9 ++----- lib/friends/getFriends.js | 9 ++----- lib/friends/removeFriend.js | 9 ++----- lib/friends/sendFriendRequest.js | 9 ++----- lib/friends/unfollow.js | 9 ++----- lib/games/addDeveloperProduct.js | 14 +++++----- lib/games/configureGamePass.js | 8 ++---- lib/games/getDeveloperProducts.js | 11 ++------ lib/games/getGameRevenue.js | 5 ++-- lib/games/getGameSocialLinks.js | 13 ++++----- lib/games/getPlaceInfo.js | 11 ++++---- lib/games/getUniverseInfo.js | 11 ++++---- lib/games/publishToTopic.js | 8 ++---- lib/games/updateDeveloperProduct.js | 19 ++++--------- lib/groups/deleteWallPost.js | 9 ++----- lib/groups/deleteWallPostsByUser.js | 8 ++---- lib/groups/exile.js | 8 ++---- lib/groups/getAuditLog.js | 7 ++--- lib/groups/getGroup.js | 11 ++------ lib/groups/getGroupSocialLinks.js | 13 ++++----- lib/groups/getGroups.js | 10 ++----- lib/groups/getJoinRequest.js | 9 ++----- lib/groups/getJoinRequests.js | 9 ++----- lib/groups/getPlayers.js | 9 ++----- lib/groups/getRankInGroup.js | 15 +++-------- lib/groups/getRankNameInGroup.js | 16 +++-------- lib/groups/getRolePermissions.js | 7 ++--- lib/groups/getRoles.js | 7 ++--- lib/groups/getShout.js | 8 +++--- lib/groups/getWall.js | 7 ++--- lib/groups/handleJoinRequest.js | 9 +++---- lib/groups/leaveGroup.js | 8 ++---- lib/groups/setGroupDescription.js | 11 ++------ lib/groups/setGroupName.js | 11 ++------ lib/groups/setRank.js | 9 ++----- lib/groups/shout.js | 11 ++------ lib/inventory/getOwnership.js | 11 ++------ lib/premiumfeatures/getPremium.js | 9 ++----- lib/presence/getPresences.js | 7 ++--- lib/privatemessages/getMessages.js | 9 ++----- lib/privatemessages/message.js | 9 ++----- lib/thumbnails/getLogo.js | 18 +++++-------- lib/thumbnails/getPlayerThumbnail.js | 11 ++++---- lib/thumbnails/getThumbnails.js | 11 ++++---- lib/trades/acceptTrade.js | 10 ++----- lib/trades/canTradeWith.js | 9 ++----- lib/trades/counterTrade.js | 10 ++----- lib/trades/declineTrade.js | 10 ++----- lib/trades/getTradeInfo.js | 9 ++----- lib/trades/sendTrade.js | 10 ++----- lib/users/getBlurb.js | 3 ++- lib/users/getIdFromUsername.js | 9 ++++--- lib/users/getUserInfo.js | 3 ++- lib/users/getUsernameFromId.js | 3 ++- lib/util/apiError.js | 11 +++++++- 120 files changed, 353 insertions(+), 716 deletions(-) diff --git a/lib/accountinformation/getUserSocialLinks.js b/lib/accountinformation/getUserSocialLinks.js index bce82e72a..5451eda4f 100644 --- a/lib/accountinformation/getUserSocialLinks.js +++ b/lib/accountinformation/getUserSocialLinks.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -25,14 +26,11 @@ function getUserSocialLinks (userId, jar) { resolveWithFullResponse: true } }) - .then(({ statusCode, body }) => { - const { errors } = JSON.parse(body) - if (statusCode === 200) { - return JSON.parse(body) - } else if (statusCode === 400) { - throw new Error(`${errors[0].message} | userId: ${userId}`) + .then((res) => { + if (res.statusCode === 200) { + return JSON.parse(res.body) } else { - throw new Error(`An unknown error occurred with getUserSocialLinks() | [${statusCode}] userId: ${userId}`) + throw new RobloxAPIError(res) } }) } diff --git a/lib/accountsettings/block.js b/lib/accountsettings/block.js index 81e555048..fe3123b44 100644 --- a/lib/accountsettings/block.js +++ b/lib/accountsettings/block.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -37,13 +38,7 @@ function block (jar, token, userId) { if (res.statusCode === 200) { resolve() } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/accountsettings/unblock.js b/lib/accountsettings/unblock.js index 2619e0449..c43937827 100644 --- a/lib/accountsettings/unblock.js +++ b/lib/accountsettings/unblock.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -37,13 +38,7 @@ function unblock (jar, token, userId) { if (res.statusCode === 200) { resolve() } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/asset/deleteFromInventory.js b/lib/asset/deleteFromInventory.js index 46ebfa405..a90357fe9 100644 --- a/lib/asset/deleteFromInventory.js +++ b/lib/asset/deleteFromInventory.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['assetId'] @@ -28,30 +29,17 @@ function deleteFromInventory (jar, assetId, xcsrf) { resolveWithFullResponse: true, jar, headers: { - 'X-CSRF-TOKEN': xcsrf, - 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8' + 'X-CSRF-TOKEN': xcsrf } } } return http(httpOpt) .then(function (res) { - const responseData = typeof res.body === 'string' ? JSON.parse(res.body) : res.body - // Roblox likes to error here with 200 status codes too with inconsistency if (res.statusCode === 200) { - let error = 'An unknown error has occurred.' - if (responseData && !responseData.isValid) { - error = responseData.error - reject(new Error(error)) - } else if (responseData && responseData.isValid) { - resolve() - } + resolve() } else { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } }) .catch(error => reject(error)) diff --git a/lib/asset/getGamePassProductInfo.js b/lib/asset/getGamePassProductInfo.js index 762f64645..978709154 100644 --- a/lib/asset/getGamePassProductInfo.js +++ b/lib/asset/getGamePassProductInfo.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const cache = require('../cache') +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['gamepass'] @@ -33,9 +34,7 @@ function getGamePassProductInfo (gamepass) { if (res.statusCode === 200) { resolve(data) } else { - const errors = data.errors.map((e) => e.message) - - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) + reject(new RobloxAPIError(res)) } }) .catch(error => reject(error)) diff --git a/lib/asset/getProductInfo.js b/lib/asset/getProductInfo.js index 3e850032c..a5a8fcb04 100644 --- a/lib/asset/getProductInfo.js +++ b/lib/asset/getProductInfo.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const cache = require('../cache') +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['asset'] @@ -30,18 +31,7 @@ function getProductInfo (asset) { if (res.statusCode === 200) { resolve(JSON.parse(res.body)) } else { - // Sourced from: https://stackoverflow.com/a/32278428 - const isAnObject = (val) => !!(val instanceof Array || val instanceof Object) - - const body = isAnObject(res.body) ? JSON.parse(res.body) : {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } else { - reject(new Error(`${res.statusCode} ${res.body}`)) - } + reject(new RobloxAPIError(res)) } }) .catch(error => reject(error)) diff --git a/lib/avatar/avatarRules.js b/lib/avatar/avatarRules.js index 24675b028..416453cb9 100644 --- a/lib/avatar/avatarRules.js +++ b/lib/avatar/avatarRules.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.optional = ['option', 'jar'] @@ -32,7 +33,7 @@ exports.func = (args) => { return result } else { - throw new Error('Error fetching avatar rules') + throw new RobloxAPIError(res) } }) } diff --git a/lib/avatar/currentlyWearing.js b/lib/avatar/currentlyWearing.js index 2f8c0eddf..a6110fd4b 100644 --- a/lib/avatar/currentlyWearing.js +++ b/lib/avatar/currentlyWearing.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util') exports.required = ['userId'] @@ -26,7 +27,7 @@ exports.func = (args) => { if (res.statusCode === 200) { return JSON.parse(res.body) } else { - throw new Error('User does not exist') + throw new RobloxAPIError(res) } }) } diff --git a/lib/avatar/getAvatar.js b/lib/avatar/getAvatar.js index 30c18a163..8f7d9eb56 100644 --- a/lib/avatar/getAvatar.js +++ b/lib/avatar/getAvatar.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['userId'] @@ -24,7 +25,7 @@ const getAvatar = (userId) => { if (res.statusCode === 200) { return JSON.parse(res.body) } else { - throw new Error('User does not exist') + throw new RobloxAPIError(res) } }) } diff --git a/lib/avatar/getCurrentAvatar.js b/lib/avatar/getCurrentAvatar.js index 0f2f3dbd4..ca4a21190 100644 --- a/lib/avatar/getCurrentAvatar.js +++ b/lib/avatar/getCurrentAvatar.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.optional = ['option', 'jar'] @@ -28,7 +29,7 @@ exports.func = (args) => { } }).then((res) => { if (res.statusCode !== 200) { - throw new Error('You are not logged in') + throw new RobloxAPIError(res) } else { const json = JSON.parse(res.body) return (option ? json[option] : json) diff --git a/lib/avatar/getRecentItems.js b/lib/avatar/getRecentItems.js index bbde8e1f8..756a30577 100644 --- a/lib/avatar/getRecentItems.js +++ b/lib/avatar/getRecentItems.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.optional = ['listType', 'jar'] @@ -26,12 +27,10 @@ exports.func = (args) => { resolveWithFullResponse: true } }).then((res) => { - if (res.statusCode === 401) { - throw new Error('You are not logged in') - } else if (res.statusCode === 400) { - throw new Error('Invalid list type') - } else { + if (res.statusCode === 200) { return JSON.parse(res.body) + } else { + throw new RobloxAPIError(res) } }) } diff --git a/lib/avatar/outfitDetails.js b/lib/avatar/outfitDetails.js index 8a238d8c2..dcf4b5dde 100644 --- a/lib/avatar/outfitDetails.js +++ b/lib/avatar/outfitDetails.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['outfitId'] @@ -26,7 +27,7 @@ exports.func = (args) => { if (res.statusCode === 200) { return JSON.parse(res.body) } else { - throw new Error('Outfit does not exist') + throw new RobloxAPIError(res) } }) } diff --git a/lib/avatar/outfits.js b/lib/avatar/outfits.js index 11627c4c8..efca2c2e2 100644 --- a/lib/avatar/outfits.js +++ b/lib/avatar/outfits.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['userId'] exports.optional = ['page', 'itemsPerPage'] @@ -31,7 +32,7 @@ exports.func = (args) => { if (res.statusCode === 200) { return JSON.parse(res.body) } else { - throw new Error('User does not exist') + throw new RobloxAPIError(res) } }) } diff --git a/lib/avatar/redrawAvatar.js b/lib/avatar/redrawAvatar.js index 30e933da3..8975e128c 100644 --- a/lib/avatar/redrawAvatar.js +++ b/lib/avatar/redrawAvatar.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.optional = ['jar'] @@ -30,10 +31,8 @@ function redrawAvatar (jar, token) { return http(httpOpt).then((res) => { if (res.statusCode === 200) { resolve() - } else if (res.statusCode === 429) { - reject(new Error('Redraw avatar floodchecked')) } else { - reject(new Error('Redraw avatar failed')) + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/avatar/removeAssetId.js b/lib/avatar/removeAssetId.js index 0030f05a2..e2ebe403e 100644 --- a/lib/avatar/removeAssetId.js +++ b/lib/avatar/removeAssetId.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['assetId'] exports.optional = ['jar'] @@ -32,13 +33,8 @@ function removeAssetId (assetId, jar, xcsrf) { return http(httpOpt) .then(function (res) { - const responseData = JSON.parse(res.body) if (res.statusCode !== 200) { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } else { resolve() } diff --git a/lib/avatar/setAvatarBodyColors.js b/lib/avatar/setAvatarBodyColors.js index 955de1da8..866371e3b 100644 --- a/lib/avatar/setAvatarBodyColors.js +++ b/lib/avatar/setAvatarBodyColors.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['headColorId', 'torsoColorId', 'rightArmColorId', 'leftArmColorId', 'rightLegColorId', 'leftLegColorId'] exports.optional = ['jar'] @@ -43,10 +44,10 @@ const nextFunction = (jar, token, headColorId, torsoColorId, rightArmColorId, le }).then((res) => { if (res.statusCode === 200) { if (!res.body.success) { - throw new Error(res.body) + throw new RobloxAPIError(res) } } else { - throw new Error('Set body colors failed') + throw new RobloxAPIError(res) } }) } diff --git a/lib/avatar/setAvatarScales.js b/lib/avatar/setAvatarScales.js index cee36f527..dea4ef99f 100644 --- a/lib/avatar/setAvatarScales.js +++ b/lib/avatar/setAvatarScales.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['height', 'width', 'head'] exports.optional = ['depth', 'proportion', 'bodyType', 'jar'] @@ -43,10 +44,10 @@ const nextFunction = (jar, token, height, width, head, depth, proportion, bodyTy }).then((res) => { if (res.statusCode === 200) { if (!res.body.success) { - throw new Error(res.body) + throw new RobloxAPIError(res) } } else { - throw new Error('Set avatar scale failed') + throw new RobloxAPIError(res) } }) } diff --git a/lib/avatar/setPlayerAvatarType.js b/lib/avatar/setPlayerAvatarType.js index fa5addaa0..e58018da3 100644 --- a/lib/avatar/setPlayerAvatarType.js +++ b/lib/avatar/setPlayerAvatarType.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['avatarType'] exports.optional = ['jar'] @@ -33,10 +34,10 @@ const nextFunction = (jar, token, avatarType) => { }).then((res) => { if (res.statusCode === 200) { if (!res.body.success) { - throw new Error(res.body) + throw new RobloxAPIError(res) } } else { - throw new Error('Set avatar type failed') + throw new RobloxAPIError(res) } }) } diff --git a/lib/avatar/setWearingAssets.js b/lib/avatar/setWearingAssets.js index ca5667a8b..2fcce9982 100644 --- a/lib/avatar/setWearingAssets.js +++ b/lib/avatar/setWearingAssets.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['assetIds'] exports.optional = ['jar'] @@ -33,10 +34,10 @@ const nextFunction = (jar, token, assetIds) => { }).then((res) => { if (res.statusCode === 200) { if (!res.body.success) { - throw new Error('Invalid assets: ' + res.body.invalidAssetIds.join(', ')) + throw new RobloxAPIError(res) } } else { - throw new Error('Wear assets failed') + throw new RobloxAPIError(res) } }) } diff --git a/lib/avatar/wearAssetId.js b/lib/avatar/wearAssetId.js index 6dad06dc5..3cadd1a4e 100644 --- a/lib/avatar/wearAssetId.js +++ b/lib/avatar/wearAssetId.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['assetId'] exports.optional = ['jar'] @@ -32,13 +33,8 @@ function wearAssetId (assetId, jar, xcsrf) { return http(httpOpt) .then(function (res) { - const responseData = JSON.parse(res.body) if (res.statusCode !== 200) { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } else { resolve() } diff --git a/lib/badges/getAwardedTimestamps.js b/lib/badges/getAwardedTimestamps.js index 3ba10c3b7..ac6efd612 100644 --- a/lib/badges/getAwardedTimestamps.js +++ b/lib/badges/getAwardedTimestamps.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http').func +const RobloxAPIError = require('../util/apiError') // Args exports.required = ['userId', 'badgeId'] @@ -31,11 +32,7 @@ const getAwardedTimestamps = (userId, badgeId) => { .then(function (res) { const responseData = JSON.parse(res.body) if (res.statusCode !== 200) { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } else { resolve(responseData) } diff --git a/lib/badges/getBadgeInfo.js b/lib/badges/getBadgeInfo.js index 5c6a2b42c..fff815fb0 100644 --- a/lib/badges/getBadgeInfo.js +++ b/lib/badges/getBadgeInfo.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http').func +const RobloxAPIError = require('../util/apiError') // Args exports.required = ['badgeId'] @@ -30,7 +31,7 @@ const badgeInfo = async (id) => { json.updated = new Date(json.updated) return json } else { - throw new Error('Badge is invalid or does not exist.') + throw new RobloxAPIError(res) } }) } diff --git a/lib/badges/getGameBadges.js b/lib/badges/getGameBadges.js index 927f84527..eec7387d0 100644 --- a/lib/badges/getGameBadges.js +++ b/lib/badges/getGameBadges.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http').func +const RobloxAPIError = require('../util/apiError') // Args exports.required = ['universeId'] @@ -37,7 +38,7 @@ const gameBadges = async (id, limit, cursor, order) => { }) return json.data } else { - throw new Error('The game is invalid or does not exist.') + throw new RobloxAPIError(res) } }) } diff --git a/lib/badges/updateBadgeInfo.js b/lib/badges/updateBadgeInfo.js index f39cad3fa..8407cce22 100644 --- a/lib/badges/updateBadgeInfo.js +++ b/lib/badges/updateBadgeInfo.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['badgeId'] @@ -42,14 +43,8 @@ const updateInfo = (id, name, desc, enabled, xcrsf, jar) => { }).then(res => { if (res.statusCode === 200) { return JSON.parse(res.body) - } else if (res.statusCode === 400) { - throw new Error('Text moderated.') - } else if (res.statusCode === 401) { - throw new Error('Authorization has been denied for this request.') - } else if (res.statusCode === 403) { - throw new Error('Token Validation failed or you do not have permission to manage this badge.') - } else if (res.statusCode === 404) { - throw new Error('Badge is invalid or does not exist.') + } else { + throw new RobloxAPIError(res) } }) } diff --git a/lib/chat/addUsersToConversation.js b/lib/chat/addUsersToConversation.js index 7ec357ec5..e0f18a8ab 100644 --- a/lib/chat/addUsersToConversation.js +++ b/lib/chat/addUsersToConversation.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['conversationId', 'userIds'] exports.optional = ['jar'] @@ -37,16 +38,12 @@ function addUsersToConversation (conversationId, userIds, jar, token) { return http(httpOpt).then((res) => { if (res.statusCode === 200) { if (!res.body.resultType === 'Success') { - reject(new Error(res.body.statusMessage)) + reject(new RobloxAPIError(res)) } else { resolve(res.body) } } else { - let error = 'An unknown error has occurred.' - if (res.body && res.body.errors) { - error = res.body.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/chat/chatSettings.js b/lib/chat/chatSettings.js index 1098af238..533433062 100644 --- a/lib/chat/chatSettings.js +++ b/lib/chat/chatSettings.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.optional = ['jar'] @@ -25,7 +26,7 @@ exports.func = (args) => { } }).then((res) => { if (res.statusCode !== 200) { - throw new Error('You are not logged in') + throw new RobloxAPIError(res) } else { return JSON.parse(res.body) } diff --git a/lib/chat/getChatMessages.js b/lib/chat/getChatMessages.js index dc08a5819..e1aa87655 100644 --- a/lib/chat/getChatMessages.js +++ b/lib/chat/getChatMessages.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['conversationId'] exports.optional = ['pageSize', 'exclusiveStartMessageId', 'jar'] @@ -32,7 +33,7 @@ exports.func = (args) => { } }).then((res) => { if (res.statusCode !== 200) { - throw new Error('You are not logged in') + throw new RobloxAPIError(res) } else { return JSON.parse(res.body) } diff --git a/lib/chat/getConversations.js b/lib/chat/getConversations.js index 16838706f..92c4cd032 100644 --- a/lib/chat/getConversations.js +++ b/lib/chat/getConversations.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['conversationIds'] exports.optional = ['jar'] @@ -28,13 +29,7 @@ exports.func = (args) => { } }).then((res) => { if (res.statusCode !== 200) { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - throw new Error(`${res.statusCode} ${errors.join(', ')}`) - } + throw new RobloxAPIError(res) } else { let response = JSON.parse(res.body) diff --git a/lib/chat/getRolloutSettings.js b/lib/chat/getRolloutSettings.js index 1753e79f0..fd16cd4a5 100644 --- a/lib/chat/getRolloutSettings.js +++ b/lib/chat/getRolloutSettings.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.optional = ['featureNames', 'jar'] @@ -27,7 +28,7 @@ exports.func = (args) => { } }).then((res) => { if (res.statusCode !== 200) { - throw new Error('You are not logged in') + throw new RobloxAPIError(res) } else { return JSON.parse(res.body) } diff --git a/lib/chat/getUnreadConversationCount.js b/lib/chat/getUnreadConversationCount.js index 606ee8c72..02341d3b2 100644 --- a/lib/chat/getUnreadConversationCount.js +++ b/lib/chat/getUnreadConversationCount.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.optional = ['jar'] @@ -25,7 +26,7 @@ exports.func = (args) => { } }).then((res) => { if (res.statusCode !== 200) { - throw new Error('You are not logged in') + throw new RobloxAPIError(res) } else { return JSON.parse(res.body) } diff --git a/lib/chat/getUnreadMessages.js b/lib/chat/getUnreadMessages.js index 9dee2aef2..97663b1d1 100644 --- a/lib/chat/getUnreadMessages.js +++ b/lib/chat/getUnreadMessages.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['conversationIds'] exports.optional = ['pageSize', 'jar'] @@ -30,7 +31,7 @@ exports.func = (args) => { } }).then((res) => { if (res.statusCode !== 200) { - throw new Error('You are not logged in') + throw new RobloxAPIError(res) } else { return JSON.parse(res.body) } diff --git a/lib/chat/getUserConversations.js b/lib/chat/getUserConversations.js index 48f9a2b42..998b9a9ac 100644 --- a/lib/chat/getUserConversations.js +++ b/lib/chat/getUserConversations.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.optional = ['pageNumber', 'pageSize', 'jar'] @@ -29,7 +30,7 @@ exports.func = (args) => { } }).then((res) => { if (res.statusCode !== 200) { - throw new Error('You are not logged in') + throw new RobloxAPIError(res) } else { return JSON.parse(res.body) } diff --git a/lib/chat/markChatAsRead.js b/lib/chat/markChatAsRead.js index 3615b2dc2..bc70679a6 100644 --- a/lib/chat/markChatAsRead.js +++ b/lib/chat/markChatAsRead.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['conversationId', 'endMessageId'] exports.optional = ['jar'] @@ -35,10 +36,10 @@ const nextFunction = (jar, token, conversationId, endMessageId) => { }).then((res) => { if (res.statusCode === 200) { if (!res.body.resultType === 'Success') { - throw new Error(res.body.statusMessage) + throw new RobloxAPIError(res) } } else { - throw new Error('Mark as read failed') + throw new RobloxAPIError(res) } }) } diff --git a/lib/chat/markChatAsSeen.js b/lib/chat/markChatAsSeen.js index aae16b0cd..74773c720 100644 --- a/lib/chat/markChatAsSeen.js +++ b/lib/chat/markChatAsSeen.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['conversationIds'] exports.optional = ['jar'] @@ -33,10 +34,10 @@ const nextFunction = (jar, token, conversationIds) => { }).then((res) => { if (res.statusCode === 200) { if (!res.body.resultType === 'Success') { - throw new Error(res.body.statusMessage) + throw new RobloxAPIError(res) } } else { - throw new Error('Mark as seen failed') + throw new RobloxAPIError(res) } }) } diff --git a/lib/chat/multiGetLatestMessages.js b/lib/chat/multiGetLatestMessages.js index 939fa4728..699a85cc8 100644 --- a/lib/chat/multiGetLatestMessages.js +++ b/lib/chat/multiGetLatestMessages.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['conversationIds'] exports.optional = ['pageSize', 'jar'] @@ -29,7 +30,7 @@ exports.func = (args) => { } }).then((res) => { if (res.statusCode !== 200) { - throw new Error('You are not logged in') + throw new RobloxAPIError(res) } else { return JSON.parse(res.body) } diff --git a/lib/chat/removeFromGroupConversation.js b/lib/chat/removeFromGroupConversation.js index 1f7be6073..ac2ff1632 100644 --- a/lib/chat/removeFromGroupConversation.js +++ b/lib/chat/removeFromGroupConversation.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['conversationId', 'userId'] exports.optional = ['jar'] @@ -38,16 +39,12 @@ function removeFromGroupConversation (conversationId, userId, jar, token) { return http(httpOpt).then((res) => { if (res.statusCode === 200) { if (!res.body.resultType === 'Success') { - reject(new Error(res.body.statusMessage)) + reject(new RobloxAPIError(res)) } else { resolve(res.body) } } else { - let error = 'An unknown error has occurred.' - if (res.body && res.body.errors) { - error = res.body.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/chat/renameGroupConversation.js b/lib/chat/renameGroupConversation.js index 7e2c14509..d70185da1 100644 --- a/lib/chat/renameGroupConversation.js +++ b/lib/chat/renameGroupConversation.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['conversationId', 'title'] exports.optional = ['jar'] @@ -37,12 +38,12 @@ function renameGroupConversation (conversationId, newTitle, jar, token) { return http(httpOpt).then((res) => { if (res.statusCode === 200) { if (!res.body.resultType === 'Success') { - reject(new Error(res.body.statusMessage)) + reject(new RobloxAPIError(res)) } else { resolve(res.body) } } else { - reject(new Error('Rename group chat failed')) + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/chat/sendChatMessage.js b/lib/chat/sendChatMessage.js index 04ada9da4..f71520d79 100644 --- a/lib/chat/sendChatMessage.js +++ b/lib/chat/sendChatMessage.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['conversationId', 'message'] exports.optional = ['jar'] @@ -37,12 +38,12 @@ function sendChatMessage (conversationId, messageText, jar, token) { return http(httpOpt).then((res) => { if (res.statusCode === 200) { if (!res.body.resultType === 'Success') { - reject(new Error(res.body.statusMessage)) + reject(new RobloxAPIError(res)) } else { resolve(res.body) } } else { - throw new Error('Send chat message failed') + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/chat/setChatUserTyping.js b/lib/chat/setChatUserTyping.js index 05911afba..f96276ae5 100644 --- a/lib/chat/setChatUserTyping.js +++ b/lib/chat/setChatUserTyping.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['conversationId', 'isTyping'] exports.optional = ['jar'] @@ -37,16 +38,12 @@ function setChatUserTyping (conversationId, isTyping, jar, token) { return http(httpOpt).then((res) => { if (res.statusCode === 200) { if (!res.body.resultType === 'Success') { - reject(new Error(res.body.statusMessage)) + reject(new RobloxAPIError(res)) } else { resolve(res.body) } } else { - let error = 'An unknown error has occurred.' - if (res.body && res.body.errors) { - error = res.body.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/chat/start121Conversation.js b/lib/chat/start121Conversation.js index e7118be49..907594ff6 100644 --- a/lib/chat/start121Conversation.js +++ b/lib/chat/start121Conversation.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['userId'] exports.optional = ['jar'] @@ -33,10 +34,10 @@ const nextFunction = (jar, token, userId) => { }).then((res) => { if (res.statusCode === 200) { if (!res.body.resultType === 'Success') { - throw new Error(res.body.statusMessage) + throw new RobloxAPIError(res) } } else { - throw new Error('Start conversation failed') + throw new RobloxAPIError(res) } }) } diff --git a/lib/chat/startCloudEditConversation.js b/lib/chat/startCloudEditConversation.js index c4458965b..7da4efe81 100644 --- a/lib/chat/startCloudEditConversation.js +++ b/lib/chat/startCloudEditConversation.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['placeId'] exports.optional = ['jar'] @@ -33,10 +34,10 @@ const nextFunction = (jar, token, placeId) => { }).then((res) => { if (res.statusCode === 200) { if (!res.body.resultType === 'Success') { - throw new Error(res.body.statusMessage) + throw new RobloxAPIError(res) } } else { - throw new Error('Start cloud edit chat failed') + throw new RobloxAPIError(res) } }) } diff --git a/lib/chat/startGroupConversation.js b/lib/chat/startGroupConversation.js index 137879de0..72e93ee83 100644 --- a/lib/chat/startGroupConversation.js +++ b/lib/chat/startGroupConversation.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['userIds', 'title'] exports.optional = ['jar'] @@ -38,16 +39,12 @@ function startGroupConversation (userIds, chatTitle, jar, token) { return http(httpOpt).then((res) => { if (res.statusCode === 200) { if (!res.body.resultType === 'Success') { - reject(new Error(res.body.statusMessage)) + reject(new RobloxAPIError(res)) } else { resolve(res.body) } } else { - let error = 'An unknown error has occurred.' - if (res.body && res.body.errors) { - error = res.body.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/datastores/deleteDatastoreEntry.js b/lib/datastores/deleteDatastoreEntry.js index 3240e0630..bc1db1f48 100644 --- a/lib/datastores/deleteDatastoreEntry.js +++ b/lib/datastores/deleteDatastoreEntry.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['universeId', 'datastoreName', 'entryKey'] @@ -43,18 +44,7 @@ function deleteDatastoreEntry (universeId, datastoreName, entryKey, scope = 'glo if (res.statusCode === 204) { resolve() } else { - // Sourced from: https://stackoverflow.com/a/32278428 - const isAnObject = (val) => !!(val instanceof Array || val instanceof Object) - - let body - - try { - body = isAnObject(JSON.parse(res.body)) ? JSON.parse(res.body) : {} - } catch (error) { - reject(new Error(`${res.statusCode} ${res.statusMessage}`)) - } - - reject(new Error(`${res.statusCode} ${body.error} ${body.message}`)) + reject(new RobloxAPIError(res)) } }) .catch(error => reject(error)) diff --git a/lib/datastores/getDatastoreEntry.js b/lib/datastores/getDatastoreEntry.js index 74008e2ad..a650a5cc4 100644 --- a/lib/datastores/getDatastoreEntry.js +++ b/lib/datastores/getDatastoreEntry.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['universeId', 'datastoreName', 'entryKey'] @@ -75,18 +76,7 @@ function getDatastoreEntry (universeId, datastoreName, entryKey, scope = 'global } }) } else { - // Sourced from: https://stackoverflow.com/a/32278428 - const isAnObject = (val) => !!(val instanceof Array || val instanceof Object) - - let body - - try { - body = isAnObject(JSON.parse(res.body)) ? JSON.parse(res.body) : {} - } catch (error) { - reject(new Error(`${res.statusCode} ${res.statusMessage}`)) - } - - reject(new Error(`${res.statusCode} ${body.error} ${body.message}`)) + reject(new RobloxAPIError(res)) } }) .catch(error => reject(error)) diff --git a/lib/datastores/getDatastoreEntryVersions.js b/lib/datastores/getDatastoreEntryVersions.js index a4db43b76..01454a600 100644 --- a/lib/datastores/getDatastoreEntryVersions.js +++ b/lib/datastores/getDatastoreEntryVersions.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['universeId', 'datastoreName', 'entryKey'] @@ -60,18 +61,7 @@ function getDatastoreEntryVersions (universeId, datastoreName, entryKey, scope = resolve(response) } else { - // Sourced from: https://stackoverflow.com/a/32278428 - const isAnObject = (val) => !!(val instanceof Array || val instanceof Object) - - let body - - try { - body = isAnObject(JSON.parse(res.body)) ? JSON.parse(res.body) : {} - } catch (error) { - reject(new Error(`${res.statusCode} ${res.statusMessage}`)) - } - - reject(new Error(`${res.statusCode} ${body.error} ${body.message}`)) + reject(new RobloxAPIError(res)) } }) .catch(error => reject(error)) diff --git a/lib/datastores/getDatastoreKeys.js b/lib/datastores/getDatastoreKeys.js index 737d2923b..212eab512 100644 --- a/lib/datastores/getDatastoreKeys.js +++ b/lib/datastores/getDatastoreKeys.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['universeId', 'datastoreName'] @@ -50,18 +51,7 @@ function getDatastoreKeys (universeId, datastoreName, scope = 'global', prefix, resolve(response) } else { - // Sourced from: https://stackoverflow.com/a/32278428 - const isAnObject = (val) => !!(val instanceof Array || val instanceof Object) - - let body - - try { - body = isAnObject(JSON.parse(res.body)) ? JSON.parse(res.body) : {} - } catch (error) { - reject(new Error(`${res.statusCode} ${res.statusMessage}`)) - } - - reject(new Error(`${res.statusCode} ${body.error} ${body.message}`)) + reject(new RobloxAPIError(res)) } }) .catch(error => reject(error)) diff --git a/lib/datastores/getDatastores.js b/lib/datastores/getDatastores.js index a094a3d29..8c48847ba 100644 --- a/lib/datastores/getDatastores.js +++ b/lib/datastores/getDatastores.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['universeId'] @@ -49,18 +50,7 @@ function getDatastores (universeId, prefix, limit, cursor, jar) { resolve(response) } else { - // Sourced from: https://stackoverflow.com/a/32278428 - const isAnObject = (val) => !!(val instanceof Array || val instanceof Object) - - let body - - try { - body = isAnObject(JSON.parse(res.body)) ? JSON.parse(res.body) : {} - } catch (error) { - reject(new Error(`${res.statusCode} ${res.statusMessage}`)) - } - - reject(new Error(`${res.statusCode} ${body.error} ${body.message}`)) + reject(new RobloxAPIError(res)) } }) .catch(error => reject(error)) diff --git a/lib/datastores/incrementDatastoreEntry.js b/lib/datastores/incrementDatastoreEntry.js index 414f01ae1..00d55777f 100644 --- a/lib/datastores/incrementDatastoreEntry.js +++ b/lib/datastores/incrementDatastoreEntry.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const crypto = require('crypto') +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['universeId', 'datastoreName', 'entryKey', 'incrementBy'] @@ -70,18 +71,7 @@ function incrementDatastoreEntry (universeId, datastoreName, entryKey, increment } }) } else { - // Sourced from: https://stackoverflow.com/a/32278428 - const isAnObject = (val) => !!(val instanceof Array || val instanceof Object) - - let body - - try { - body = isAnObject(JSON.parse(res.body)) ? JSON.parse(res.body) : {} - } catch (error) { - reject(new Error(`${res.statusCode} ${res.statusMessage}`)) - } - - reject(new Error(`${res.statusCode} ${body.error} ${body.message}`)) + reject(new RobloxAPIError(res)) } }) .catch(error => reject(error)) diff --git a/lib/datastores/setDatastoreEntry.js b/lib/datastores/setDatastoreEntry.js index 8ea33f155..3178679ae 100644 --- a/lib/datastores/setDatastoreEntry.js +++ b/lib/datastores/setDatastoreEntry.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const crypto = require('crypto') +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['universeId', 'datastoreName', 'entryKey', 'body'] @@ -67,18 +68,7 @@ function setDatastoreEntry (universeId, datastoreName, entryKey, body, scope = ' resolve(response) } else { - // Sourced from: https://stackoverflow.com/a/32278428 - const isAnObject = (val) => !!(val instanceof Array || val instanceof Object) - - let body - - try { - body = isAnObject(JSON.parse(res.body)) ? JSON.parse(res.body) : {} - } catch (error) { - reject(new Error(`${res.statusCode} ${res.statusMessage}`)) - } - - reject(new Error(`${res.statusCode} ${body.error} ${body.message}`)) + reject(new RobloxAPIError(res)) } }) .catch(error => reject(error)) diff --git a/lib/develop/canManage.js b/lib/develop/canManage.js index 12d7fed6e..d498b3281 100644 --- a/lib/develop/canManage.js +++ b/lib/develop/canManage.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId', 'assetId'] @@ -25,16 +26,13 @@ function canManage (userId, assetId) { resolveWithFullResponse: true } }) - .then(function ({ statusCode, body }) { - const { Success: success, CanManage: canManage, ErrorMessage: error } = JSON.parse(body) + .then(function (res) { + const { body } = res + const { Success: success, CanManage: canManage } = JSON.parse(body) if (success) { return canManage } else { - if (error) { - throw new Error(`${error} | userId: ${userId}, assetId: ${assetId}`) - } else { - throw new Error(`An unknown error occurred with canManage() | [${statusCode}] userId: ${userId}, assetId: ${assetId}`) - } + throw new RobloxAPIError(res) } }) } diff --git a/lib/develop/configureItem.js b/lib/develop/configureItem.js index 967488aca..7e63810ef 100644 --- a/lib/develop/configureItem.js +++ b/lib/develop/configureItem.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['id', 'name', 'description'] @@ -39,9 +40,12 @@ function configure (jar, token, id, name, description, enableComments, sellForRo enableComments, genres: genreSelection || ['All'], isCopyingAllowed: typeof (sellForRobux) === 'boolean' ? sellForRobux : null - } + }, + resolveWithFullResponse: true } - }).then(function (json) { + }).then(function (res) { + const json = JSON.parse(res.body) + if (!json.errors) { const response = { name, @@ -58,7 +62,7 @@ function configure (jar, token, id, name, description, enableComments, sellForRo if (json.errors[0].code === 13) { // "Only a marketplace asset can be updated with IsCopyingAllowed." throw new Error('Attempting to make a sellable asset copyable; it must be sold for robux. (Use a number for sellForRobux.)') } - throw new Error(json.errors[0].message) + throw new RobloxAPIError(res) } }) } @@ -77,10 +81,13 @@ function configureRobux (args) { priceConfiguration: { priceInRobux: args.sellForRobux || 0 } - } + }, + resolveWithFullResponse: true } }) - .then(function (json) { + .then(function (res) { + const json = res.body + if (!json.errors) { return configure(args.jar, args.token, args.id, args.name, args.description, args.enableComments, args.sellForRobux, args.genreSelection, args.sellForRobux) } else { @@ -98,9 +105,11 @@ function configureRobux (args) { priceConfiguration: { priceInRobux: args.sellForRobux } - } + }, + resolveWithFullResponse: true } - }).then((err) => { + }).then((res) => { + const err = res.body if (!err.errors) { return configure(args.jar, args.token, args.id, args.name, args.description, args.enableComments, args.sellForRobux, args.genreSelection, args.sellForRobux) } else { @@ -159,7 +168,7 @@ function configureRobux (args) { resolveWithFullResponse: true } }).then((response) => { - if (!response.ok) throw new Error(response.body) + if (!response.ok) throw new RobloxAPIError(response) return { name: args.name, @@ -173,7 +182,7 @@ function configureRobux (args) { } else if (json.errors[0].code === 40) { // "Use collecibles publishing endpoint." - Publishing fee has not been paid for this asset throw new Error(`The publishing fee for asset ${args.id} has not been paid, you must pay the fee before setting or updating the price.`) } - throw new Error(`An unknown error occurred: [${json.errors[0].code}] ${json.errors[0].message}`) + throw new RobloxAPIError(res) } }) } diff --git a/lib/develop/updateUniverse.js b/lib/develop/updateUniverse.js index 4eac3c9f5..8c0c9fd7d 100644 --- a/lib/develop/updateUniverse.js +++ b/lib/develop/updateUniverse.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['universeId', 'settings'] @@ -33,13 +34,11 @@ function updateUniverse (universeId, settings, jar, token) { json: settings, resolveWithFullResponse: true } - }).then(({ statusCode, body }) => { - if (statusCode === 200) { - resolve(body) - } else if (body && body.errors) { - reject(new Error(`[${statusCode}] ${body.errors[0].message} | universeId: ${universeId}, settings: ${JSON.stringify(settings)} ${body.errors.field ? ` | ${body.errors.field} is incorrect` : ''}`)) + }).then((res) => { + if (res.statusCode === 200) { + resolve(res.body) } else { - reject(new Error(`An unknown error occurred with updateUniverse() | [${statusCode}] universeId: ${universeId}, settings: ${JSON.stringify(settings)}`)) + reject(new RobloxAPIError(res)) } }).catch(reject) }) diff --git a/lib/develop/updateUniverseAccess.js b/lib/develop/updateUniverseAccess.js index a696edfaa..95ca28f2a 100644 --- a/lib/develop/updateUniverseAccess.js +++ b/lib/develop/updateUniverseAccess.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['universeId', 'isPublic'] @@ -35,13 +36,11 @@ function updateUniverseAccess (universeId, isPublic, jar, token) { }, resolveWithFullResponse: true } - }).then(({ statusCode, body }) => { - if (statusCode === 200) { + }).then((res) => { + if (res.statusCode === 200) { resolve() - } else if (body && body.errors) { - reject(new Error(`[${statusCode}] ${body.errors[0].message} | universeId: ${universeId}, isPublic: ${isPublic} ${body.errors.field ? ` | ${body.errors.field} is incorrect` : ''}`)) } else { - reject(new Error(`An unknown error occurred with updateUniverseAccess() | [${statusCode}] universeId: ${universeId}, isPublic: ${isPublic}`)) + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/economy/getGroupFunds.js b/lib/economy/getGroupFunds.js index a515bc2b7..3ff249ce1 100644 --- a/lib/economy/getGroupFunds.js +++ b/lib/economy/getGroupFunds.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group'] @@ -26,14 +27,12 @@ function getGroupFunds (group, jar) { resolveWithFullResponse: true } }) - .then(({ statusCode, body }) => { - const { robux, errors } = JSON.parse(body) - if (statusCode === 200) { + .then((res) => { + const { robux } = JSON.parse(res.body) + if (res.statusCode === 200) { return robux - } else if (statusCode === 400 || statusCode === 403) { - throw new Error(`${errors[0].message} | groupId: ${group}`) } else { - throw new Error(`An unknown error occurred with getGroupFunds() | [${statusCode}] groupId: ${group}`) + throw new RobloxAPIError(res) } }) } diff --git a/lib/economy/getGroupRevenueSummary.js b/lib/economy/getGroupRevenueSummary.js index 1756b8d42..679283aaf 100644 --- a/lib/economy/getGroupRevenueSummary.js +++ b/lib/economy/getGroupRevenueSummary.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['groupId'] @@ -27,18 +28,11 @@ function getGroupRevenueSummary (group, timeFrame, jar) { resolveWithFullResponse: true } }) - .then(({ statusCode, body }) => { - const { errors } = JSON.parse(body) - if (statusCode === 200) { - return JSON.parse(body) - } else if (statusCode === 400) { - throw new Error(`${errors[0].message} | group: ${group}, timeFrame: ${timeFrame}`) - } else if (statusCode === 401) { - throw new Error(`${errors[0].message} (Are you logged in?) | group: ${group}, timeFrame: ${timeFrame}`) - } else if (statusCode === 403) { - throw new Error('Insufficient permissions: "Spend group funds" role permissions required') + .then((res) => { + if (res.statusCode === 200) { + return JSON.parse(res.body) } else { - throw new Error(`An unknown error occurred with getGroupRevenueSummary() | [${statusCode}] group: ${group}, timeFrame: ${timeFrame}`) + throw new RobloxAPIError(res) } }) } diff --git a/lib/economy/getResaleData.js b/lib/economy/getResaleData.js index 5de5b4b65..d995a1325 100644 --- a/lib/economy/getResaleData.js +++ b/lib/economy/getResaleData.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http').func +const RobloxAPIError = require('../util/apiError') // Args exports.required = ['assetId'] @@ -23,25 +24,18 @@ const getResaleData = async (assetId) => { options: { resolveWithFullResponse: true } - }).then(({ body, statusCode }) => { - const { errors } = JSON.parse(body) - if (statusCode === 200) { - try { - const resaleData = JSON.parse(body) - for (const priceDataPoint of resaleData.priceDataPoints) { - priceDataPoint.date = new Date(priceDataPoint.date) - } - for (const volumeDataPoint of resaleData.volumeDataPoints) { - volumeDataPoint.date = new Date(volumeDataPoint.date) - } - return resaleData - } catch (err) { - throw new Error(`An unknown error occurred with getResaleData() | [${statusCode}] assetId: ${assetId}`) + }).then((res) => { + if (res.statusCode === 200) { + const resaleData = JSON.parse(res.body) + for (const priceDataPoint of resaleData.priceDataPoints) { + priceDataPoint.date = new Date(priceDataPoint.date) } - } else if (statusCode === 400) { - throw new Error(`${errors[0].message} | assetId: ${assetId}`) + for (const volumeDataPoint of resaleData.volumeDataPoints) { + volumeDataPoint.date = new Date(volumeDataPoint.date) + } + return resaleData } else { - throw new Error(`An unknown error occurred with getResaleData() | [${statusCode}] assetId: ${assetId}`) + throw new RobloxAPIError(res) } }) } diff --git a/lib/economy/getUserFunds.js b/lib/economy/getUserFunds.js index d73fc94b1..569c00fff 100644 --- a/lib/economy/getUserFunds.js +++ b/lib/economy/getUserFunds.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -27,14 +28,12 @@ function getUserFunds (userId, jar) { resolveWithFullResponse: true } }) - .then(({ statusCode, body }) => { - const { robux, errors } = JSON.parse(body) - if (statusCode === 200) { + .then((res) => { + const { robux } = JSON.parse(res.body) + if (res.statusCode === 200) { return robux - } else if (statusCode === 400 || statusCode === 403) { - throw new Error(`${errors[0].message} | userId: ${userId}`) } else { - throw new Error(`An unknown error occurred with getUserFunds() | [${statusCode}] userId: ${userId}`) + throw new RobloxAPIError(res) } }) } diff --git a/lib/friends/acceptFriendRequest.js b/lib/friends/acceptFriendRequest.js index d6e81beb9..efd232583 100644 --- a/lib/friends/acceptFriendRequest.js +++ b/lib/friends/acceptFriendRequest.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -38,13 +39,7 @@ function acceptFriendRequest (jar, token, userId) { if (res.statusCode === 200) { resolve() } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/friends/declineAllFriendRequests.js b/lib/friends/declineAllFriendRequests.js index 63f01f344..c2374ad37 100644 --- a/lib/friends/declineAllFriendRequests.js +++ b/lib/friends/declineAllFriendRequests.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = [] @@ -36,13 +37,7 @@ function declineAllFriendRequests (jar, token) { if (res.statusCode === 200) { resolve() } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/friends/declineFriendRequest.js b/lib/friends/declineFriendRequest.js index c76cd1e29..50bdf9003 100644 --- a/lib/friends/declineFriendRequest.js +++ b/lib/friends/declineFriendRequest.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -38,13 +39,7 @@ function declineFriendRequest (jar, token, userId) { if (res.statusCode === 200) { resolve() } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/friends/getFollowerCount.js b/lib/friends/getFollowerCount.js index 49cf3cc8e..0536723c2 100644 --- a/lib/friends/getFollowerCount.js +++ b/lib/friends/getFollowerCount.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -29,8 +30,6 @@ exports.func = function (args) { return http(httpOpt).then(function (res) { if (res.statusCode === 200) { return res.body.count } - throw new Error( - `Failed to retrieve follower count: (${res.statusCode}) ${JSON.stringify(res.body)}` - ) + throw new RobloxAPIError(res) }) } diff --git a/lib/friends/getFollowers.js b/lib/friends/getFollowers.js index b6abac8eb..68d0e766f 100644 --- a/lib/friends/getFollowers.js +++ b/lib/friends/getFollowers.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -40,13 +41,7 @@ function getFollowers (jar, userId, sortOrder, limit, cursor) { }) resolve(response) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/friends/getFollowingCount.js b/lib/friends/getFollowingCount.js index 7a4548392..d8bf5a9dc 100644 --- a/lib/friends/getFollowingCount.js +++ b/lib/friends/getFollowingCount.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -29,8 +30,6 @@ exports.func = function (args) { return http(httpOpt).then(function (res) { if (res.statusCode === 200) { return res.body.count } - throw new Error( - `Failed to retrieve following count: (${res.statusCode}) ${JSON.stringify(res.body)}` - ) + throw new RobloxAPIError(res) }) } diff --git a/lib/friends/getFollowings.js b/lib/friends/getFollowings.js index 8055eaacd..a2ab157c6 100644 --- a/lib/friends/getFollowings.js +++ b/lib/friends/getFollowings.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -40,13 +41,7 @@ function getFollowings (jar, userId, sortOrder, limit, cursor) { }) resolve(response) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/friends/getFriendCount.js b/lib/friends/getFriendCount.js index 912dd1ff7..61ff1d9c5 100644 --- a/lib/friends/getFriendCount.js +++ b/lib/friends/getFriendCount.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -29,8 +30,6 @@ exports.func = function (args) { return http(httpOpt).then(function (res) { if (res.statusCode === 200) { return res.body.count } - throw new Error( - `Failed to retrieve friend count: (${res.statusCode}) ${JSON.stringify(res.body)}` - ) + throw new RobloxAPIError(res) }) } diff --git a/lib/friends/getFriendRequests.js b/lib/friends/getFriendRequests.js index efee1146d..f6969eccd 100644 --- a/lib/friends/getFriendRequests.js +++ b/lib/friends/getFriendRequests.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = [] @@ -41,13 +42,7 @@ function getFriendsRequests (args) { }) resolve(response) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/friends/getFriends.js b/lib/friends/getFriends.js index c2a5a9294..318f20f2a 100644 --- a/lib/friends/getFriends.js +++ b/lib/friends/getFriends.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] exports.optional = ['jar'] @@ -36,13 +37,7 @@ function getFriends (jar, userId) { }) resolve(response) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/friends/removeFriend.js b/lib/friends/removeFriend.js index e5b4fd665..5d1a83ab6 100644 --- a/lib/friends/removeFriend.js +++ b/lib/friends/removeFriend.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -37,13 +38,7 @@ function removeFriend (jar, token, userId) { if (res.statusCode === 200) { resolve() } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/friends/sendFriendRequest.js b/lib/friends/sendFriendRequest.js index 302244805..f5ca2db27 100644 --- a/lib/friends/sendFriendRequest.js +++ b/lib/friends/sendFriendRequest.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -38,13 +39,7 @@ function sendFriendRequest (jar, token, userId) { if (res.statusCode === 200) { resolve() } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/friends/unfollow.js b/lib/friends/unfollow.js index 0a1ea9440..e56df5a54 100644 --- a/lib/friends/unfollow.js +++ b/lib/friends/unfollow.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -37,13 +38,7 @@ function unfollow (jar, token, userId) { if (res.statusCode === 200) { resolve() } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/games/addDeveloperProduct.js b/lib/games/addDeveloperProduct.js index 939b422fa..30f84ea55 100644 --- a/lib/games/addDeveloperProduct.js +++ b/lib/games/addDeveloperProduct.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['universeId', 'name', 'priceInRobux'] exports.optional = ['description', 'jar'] @@ -32,14 +33,13 @@ const nextFunction = (jar, token, universeId, name, priceInRobux, description) = resolveWithFullResponse: true } }).then(function (res) { + if (res.statusCode !== 200) { + throw new RobloxAPIError(res) + } try { - const json = JSON.parse(res.body) - if (res.statusCode === 200) { - return json - } - throw new Error(json) - } catch (err) { - throw new Error(res.body) + return JSON.parse(res.body) + } catch (_) { + throw new Error('Failed to parse response body (not valid JSON)') } }) } diff --git a/lib/games/configureGamePass.js b/lib/games/configureGamePass.js index a6f335c60..2f438625d 100644 --- a/lib/games/configureGamePass.js +++ b/lib/games/configureGamePass.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http').func const getGeneralToken = require('../util/getGeneralToken').func +const RobloxAPIError = require('../util/apiError') // Args exports.required = ['gamePassId', 'name'] @@ -61,12 +62,7 @@ function configureGamePass (gamePassId, name, description, price, icon, jar, tok iconChanged: !!icon // Boolean Cast }) } else { - const priceComment = (typeof (price) === 'number') ? ` | NOTE: Price has successfully been changed to ${price}R.` : '' - if (res.statusCode === 403) { - reject(new Error(`You do not have permission to edit this game pass.${priceComment}`)) - } else { - reject(new Error(`An unexpected error occurred with status code ${res.statusCode}.${priceComment}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/games/getDeveloperProducts.js b/lib/games/getDeveloperProducts.js index 33c3ea027..9e455adc3 100644 --- a/lib/games/getDeveloperProducts.js +++ b/lib/games/getDeveloperProducts.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['placeId'] @@ -30,15 +31,7 @@ function getDeveloperProducts (jar, placeId, page) { if (res.statusCode === 200) { resolve(JSON.parse(res.body)) } else { - const body = res.body || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } else { - reject(new Error(`${res.statusCode} An error has occurred ${res.body ? res.body : ''}`)) - } + reject(new RobloxAPIError(res)) } }) .catch(error => reject(error)) diff --git a/lib/games/getGameRevenue.js b/lib/games/getGameRevenue.js index 81f061db5..31230070d 100644 --- a/lib/games/getGameRevenue.js +++ b/lib/games/getGameRevenue.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['placeId', 'type', 'granularity'] exports.optional = ['jar'] @@ -32,9 +33,7 @@ function getGameRevenue (placeId, type, granularity, jar, token) { return http(httpOpt) .then(function (res) { if (res.statusCode === 200) resolve(JSON.parse(res.body)) - else if (res.statusCode === 401) reject(new Error('You are not logged in.')) - else if (res.statusCode === 403) reject(new Error('You do not have permission to view this game.')) - else reject(new Error('An unknown error occurred.')) + else reject(new RobloxAPIError(res)) }) .catch(function (err) { console.error(err); reject(err) }) }) diff --git a/lib/games/getGameSocialLinks.js b/lib/games/getGameSocialLinks.js index 91a2d978d..ce2c05685 100644 --- a/lib/games/getGameSocialLinks.js +++ b/lib/games/getGameSocialLinks.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['universeId'] @@ -26,16 +27,12 @@ function getGameSocialLinks (universeId, jar) { resolveWithFullResponse: true } }) - .then(({ statusCode, body }) => { - const { errors, data } = JSON.parse(body) - if (statusCode === 200 && data) { + .then((res) => { + const { data } = JSON.parse(res.body) + if (res.statusCode === 200) { return data - } else if (statusCode === 400 || statusCode === 403 || statusCode === 404) { - throw new Error(`${errors[0].message} | universeId: ${universeId}`) - } else if (statusCode === 401) { - throw new Error(`${errors[0].message} (Are you logged in?) | universeId: ${universeId}`) } else { - throw new Error(`An unknown error occurred with getGameSocialLinks() | [${statusCode}] universeId: ${universeId}`) + throw new RobloxAPIError(res) } }) } diff --git a/lib/games/getPlaceInfo.js b/lib/games/getPlaceInfo.js index 76120cc25..a1b05a821 100644 --- a/lib/games/getPlaceInfo.js +++ b/lib/games/getPlaceInfo.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['placeId'] @@ -31,13 +32,11 @@ function getPlaceInfo (placeIds, jar) { } return http(httpOpt) - .then(function ({ statusCode, body }) { - if (statusCode === 200) { - resolve(body) - } else if (body && body.errors) { - reject(new Error(`[${statusCode}] ${body.errors[0].message} | placeIds: ${placeIds.join(',')} ${body.errors.field ? ` | ${body.errors.field} is incorrect` : ''}`)) + .then(function (res) { + if (res.statusCode === 200) { + resolve(res.body) } else { - reject(new Error(`An unknown error occurred with getPlaceInfo() | [${statusCode}] placeIds: ${placeIds.join(',')}`)) + reject(new RobloxAPIError(res)) } }).catch(reject) }) diff --git a/lib/games/getUniverseInfo.js b/lib/games/getUniverseInfo.js index 650f4be6c..db5917e63 100644 --- a/lib/games/getUniverseInfo.js +++ b/lib/games/getUniverseInfo.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['universeId'] exports.optional = ['jar'] @@ -29,18 +30,16 @@ function getUniverseInfo (universeIds, jar) { } return http(httpOpt) - .then(function ({ statusCode, body }) { - if (statusCode === 200) { - resolve(body.data.map((universe) => { + .then(function (res) { + if (res.statusCode === 200) { + resolve(res.body.data.map((universe) => { universe.created = new Date(universe.created) universe.updated = new Date(universe.updated) return universe })) - } else if (body && body.errors) { - reject(new Error(`[${statusCode}] ${body.errors[0].message} | universeIds: ${universeIds.join(',')} ${body.errors.field ? ` | ${body.errors.field} is incorrect` : ''}`)) } else { - reject(new Error(`An unknown error occurred with getUniverseInfo() | [${statusCode}] universeIds: ${universeIds.join(',')}`)) + reject(new RobloxAPIError(res)) } }).catch(reject) }) diff --git a/lib/games/publishToTopic.js b/lib/games/publishToTopic.js index b1efe0fe7..6f2edbe0d 100644 --- a/lib/games/publishToTopic.js +++ b/lib/games/publishToTopic.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['universeId', 'topic', 'data'] exports.optional = ['jar'] @@ -39,12 +40,7 @@ function publishToTopic (universeId, topic, data, jar) { if (res.statusCode === 200) { resolve(true) } else { - if (typeof (res.body) === 'string') { - reject(new Error(`[${res.statusCode}] ${res.statusMessage} ${res.body}`)) - } else { - const data = Object.assign(res.body) - reject(new Error(`[${res.statusCode}] ${data.Error} ${data.Message}`)) - } + reject(new RobloxAPIError(res)) } }) .catch(reject) diff --git a/lib/games/updateDeveloperProduct.js b/lib/games/updateDeveloperProduct.js index 58ddf3b7a..3c16b1b82 100644 --- a/lib/games/updateDeveloperProduct.js +++ b/lib/games/updateDeveloperProduct.js @@ -1,5 +1,6 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['universeId', 'productId', 'priceInRobux'] exports.optional = ['name', 'description', 'jar'] @@ -37,21 +38,11 @@ function updateDeveloperProduct (universeId, productId, priceInRobux, name, desc }, resolveWithFullResponse: true } - }).then(({ statusCode, body }) => { - if (statusCode === 200) { - resolve(body) - } else if (body && body.errors) { - reject(new Error(`[${statusCode}] ${body.errors[0].message} | universeId: ${universeId}, body: ${JSON.stringify({ - Name: name, - Description: description, - PriceInRobux: priceInRobux - })}`)) + }).then((res) => { + if (res.statusCode === 200) { + resolve(res.body) } else { - reject(new Error(`An unknown error occurred with updateDeveloperProduct() | [${statusCode}] universeId: ${universeId}, body: ${JSON.stringify({ - Name: name, - Description: description, - PriceInRobux: priceInRobux - })}`)) + reject(new RobloxAPIError(res)) } }).catch(reject) }) diff --git a/lib/groups/deleteWallPost.js b/lib/groups/deleteWallPost.js index 45fd42e4e..cc4c1ff2a 100644 --- a/lib/groups/deleteWallPost.js +++ b/lib/groups/deleteWallPost.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group', ['postId', 'post']] @@ -38,13 +39,7 @@ function deleteWallPost (jar, token, group, postId) { if (res.statusCode === 200) { resolve() } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/groups/deleteWallPostsByUser.js b/lib/groups/deleteWallPostsByUser.js index 56a97d1ca..69a4ce7d3 100644 --- a/lib/groups/deleteWallPostsByUser.js +++ b/lib/groups/deleteWallPostsByUser.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group', 'userId'] @@ -35,13 +36,8 @@ function deleteWallPostsByUser (group, userId, jar, xcsrf) { return http(httpOpt) .then(function (res) { - const responseData = JSON.parse(res.body) if (res.statusCode !== 200) { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } else { resolve() } diff --git a/lib/groups/exile.js b/lib/groups/exile.js index b63e59588..02460f33f 100644 --- a/lib/groups/exile.js +++ b/lib/groups/exile.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group', 'target'] @@ -35,13 +36,8 @@ function exileUser (group, target, jar, xcsrf) { return http(httpOpt) .then(function (res) { - const responseData = JSON.parse(res.body) if (res.statusCode !== 200) { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } else { resolve() } diff --git a/lib/groups/getAuditLog.js b/lib/groups/getAuditLog.js index 3b8f8016f..919637b66 100644 --- a/lib/groups/getAuditLog.js +++ b/lib/groups/getAuditLog.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['group'] exports.optional = ['actionType', 'userId', 'sortOrder', 'limit', 'cursor', 'jar'] @@ -36,11 +37,7 @@ function getAuditLog (group, actionType, userId, sortOrder, limit, cursor, jar) .then(function (res) { const responseData = JSON.parse(res.body) if (res.statusCode !== 200) { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } else { responseData.data = responseData.data.map((entry) => { // We need to set milliseconds to 0 because Roblox does this fascinating thing diff --git a/lib/groups/getGroup.js b/lib/groups/getGroup.js index 23dfc301c..7c7bf38c6 100644 --- a/lib/groups/getGroup.js +++ b/lib/groups/getGroup.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['groupId'] @@ -38,15 +39,7 @@ function getGroup (groupId) { resolve(body) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } else { - reject(new Error(`${res.statusCode} ${res.body}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/groups/getGroupSocialLinks.js b/lib/groups/getGroupSocialLinks.js index cd2d96a4e..4cb1f070b 100644 --- a/lib/groups/getGroupSocialLinks.js +++ b/lib/groups/getGroupSocialLinks.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['groupId'] exports.optional = ['jar'] @@ -25,16 +26,12 @@ function getGroupSocialLinks (groupId, jar) { resolveWithFullResponse: true } }) - .then(({ statusCode, body }) => { - const { errors, data } = JSON.parse(body) - if (statusCode === 200 && data) { + .then((res) => { + const { data } = JSON.parse(res.body) + if (res.statusCode === 200 && data) { return data - } else if (statusCode === 400 || statusCode === 403 || statusCode === 404) { - throw new Error(`${errors[0].message} | groupId: ${groupId}`) - } else if (statusCode === 401) { - throw new Error(`${errors[0].message} (Are you logged in?) | groupId: ${groupId}`) } else { - throw new Error(`An unknown error occurred with getGroupSocialLinks() | [${statusCode}] groupId: ${groupId}`) + throw new RobloxAPIError(res) } }) } diff --git a/lib/groups/getGroups.js b/lib/groups/getGroups.js index db7717ae7..70e2d08e8 100644 --- a/lib/groups/getGroups.js +++ b/lib/groups/getGroups.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -34,14 +35,7 @@ function getGroups (userId) { const failedResponse = (responses[0].statusCode !== 200 || !responses[0].body) // we only check the first request because the second one errors if a primary is not set if (failedResponse) { - const body = responses[0].body || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${responses[0].statusCode} ${errors.join(', ')}`)) - } - reject(new Error('The provided user ID is not valid.')) + reject(new RobloxAPIError(responses[0])) } responses = responses.map(r => r.body) diff --git a/lib/groups/getJoinRequest.js b/lib/groups/getJoinRequest.js index 3b3e6c7ff..4a0c7f59f 100644 --- a/lib/groups/getJoinRequest.js +++ b/lib/groups/getJoinRequest.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group', 'userId'] @@ -34,13 +35,7 @@ function getJoinRequest (jar, group, userId) { if (res.statusCode === 200) { resolve(JSON.parse(res.body) || null) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/groups/getJoinRequests.js b/lib/groups/getJoinRequests.js index 8746085d9..6f70ff4da 100644 --- a/lib/groups/getJoinRequests.js +++ b/lib/groups/getJoinRequests.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group'] @@ -36,13 +37,7 @@ function getJoinRequests (jar, group, sortOrder, limit, cursor) { if (res.statusCode === 200) { resolve(JSON.parse(res.body)) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) .catch(error => reject(error)) diff --git a/lib/groups/getPlayers.js b/lib/groups/getPlayers.js index 949ac1ef3..20cd7a6b2 100644 --- a/lib/groups/getPlayers.js +++ b/lib/groups/getPlayers.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group', ['rolesetId']] @@ -36,13 +37,7 @@ function getPlayersInRoleOnPage (jar, group, rolesetId, sortOrder, limit, cursor if (res.statusCode === 200) { resolve(JSON.parse(res.body)) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/groups/getRankInGroup.js b/lib/groups/getRankInGroup.js index 3b779f86a..de0542a6a 100644 --- a/lib/groups/getRankInGroup.js +++ b/lib/groups/getRankInGroup.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const cache = require('../cache') +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group', 'userId'] @@ -27,18 +28,10 @@ function getRankInGroup (groupId, userId) { throw new Error('Group id should be a number') } } - return http({ url: `//groups.roblox.com/v2/users/${userId}/groups/roles`, options: { json: true } }).then((body) => { - const error = body.errors && body.errors[0] + return http({ url: `//groups.roblox.com/v2/users/${userId}/groups/roles`, options: { json: true, resolveWithFullResponse: true } }).then((res) => { + if (res.statusCode !== 200) throw new RobloxAPIError(res) - if (error) { - if (error.message === 'NotFound') { - throw new Error('An invalid UserID or GroupID was provided.') - } else { - throw new Error(error.message) - } - } - - const groupObject = body.data.find((info) => groupId === info.group.id) + const groupObject = res.body.data.find((info) => groupId === info.group.id) return groupObject ? parseInt(groupObject.role.rank) : 0 }) diff --git a/lib/groups/getRankNameInGroup.js b/lib/groups/getRankNameInGroup.js index 9464306f6..6bb37b99d 100644 --- a/lib/groups/getRankNameInGroup.js +++ b/lib/groups/getRankNameInGroup.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const cache = require('../cache') +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group', 'userId'] @@ -27,18 +28,9 @@ function getRankNameInGroup (group, userId) { throw new Error('Group id should be a number') } } - return http({ url: `//groups.roblox.com/v2/users/${userId}/groups/roles`, options: { json: true } }).then((body) => { - const error = body.errors && body.errors[0] - - if (error) { - if (error.message === 'NotFound') { - throw new Error('An invalid UserID or GroupID was provided.') - } else { - throw new Error(error.message) - } - } - - const groupObject = body.data.find((info) => group === info.group.id) + return http({ url: `//groups.roblox.com/v2/users/${userId}/groups/roles`, options: { json: true, resolveWithFullResponse: true } }).then((res) => { + if (res.statusCode !== 200) throw new RobloxAPIError(res) + const groupObject = res.body.data.find((info) => group === info.group.id) return groupObject ? groupObject.role.name : 'Guest' }) diff --git a/lib/groups/getRolePermissions.js b/lib/groups/getRolePermissions.js index 9fe345ad6..41fd5a707 100644 --- a/lib/groups/getRolePermissions.js +++ b/lib/groups/getRolePermissions.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['group', 'rolesetId'] exports.optional = ['jar'] @@ -32,11 +33,7 @@ function getRolePermissions (group, rolesetId, jar) { .then(function (res) { const responseData = JSON.parse(res.body) if (res.statusCode !== 200) { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } else { resolve(responseData) } diff --git a/lib/groups/getRoles.js b/lib/groups/getRoles.js index 27f14b548..584240720 100644 --- a/lib/groups/getRoles.js +++ b/lib/groups/getRoles.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const cache = require('../cache') +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group'] @@ -30,11 +31,7 @@ function getRoles (group) { .then(function (res) { const responseData = JSON.parse(res.body) if (res.statusCode !== 200) { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } else { let roles = responseData.roles roles = roles.sort((a, b) => a.rank - b.rank) diff --git a/lib/groups/getShout.js b/lib/groups/getShout.js index a12ddd401..1e2817152 100644 --- a/lib/groups/getShout.js +++ b/lib/groups/getShout.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group'] @@ -31,11 +32,8 @@ function getShout (group, jar) { return http(httpOpt) .then(function (res) { const responseData = JSON.parse(res.body) - if (res.statusCode === 400) { - reject(new Error('The group is invalid or does not exist.')) - } - if (responseData.shout === null) { - reject(new Error('You do not have permissions to view the shout for the group.')) + if (res.statusCode !== 400) { + reject(new RobloxAPIError(res)) } else { resolve(responseData.shout) } diff --git a/lib/groups/getWall.js b/lib/groups/getWall.js index fbc7d3a17..c674b1a28 100644 --- a/lib/groups/getWall.js +++ b/lib/groups/getWall.js @@ -1,4 +1,5 @@ const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') exports.required = ['group'] exports.optional = ['sortOrder', 'limit', 'cursor', 'jar'] @@ -33,11 +34,7 @@ function getPosts (group, sortOrder, limit, cursor, jar) { .then(function (res) { const responseData = JSON.parse(res.body) if (res.statusCode !== 200) { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } else { responseData.data = responseData.data.map((entry) => { entry.created = new Date(entry.created) diff --git a/lib/groups/handleJoinRequest.js b/lib/groups/handleJoinRequest.js index 0c19b0330..893259c3f 100644 --- a/lib/groups/handleJoinRequest.js +++ b/lib/groups/handleJoinRequest.js @@ -1,3 +1,5 @@ +const RobloxAPIError = require('../util/apiError.js') + // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func @@ -36,13 +38,8 @@ function handleJoinRequest (group, userId, accept, jar, xcsrf) { return http(httpOpt) .then(function (res) { - const responseData = JSON.parse(res.body) if (res.statusCode !== 200) { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } else { resolve() } diff --git a/lib/groups/leaveGroup.js b/lib/groups/leaveGroup.js index 75d2eefd6..d05cd131f 100644 --- a/lib/groups/leaveGroup.js +++ b/lib/groups/leaveGroup.js @@ -2,6 +2,7 @@ const http = require('../util/http.js').func const getCurrentUser = require('../util/getCurrentUser').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group'] @@ -36,13 +37,8 @@ function leaveGroup (group, jar, xcsrf, userId) { return http(httpOpt) .then(function (res) { - const responseData = JSON.parse(res.body) if (res.statusCode !== 200) { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } else { resolve() } diff --git a/lib/groups/setGroupDescription.js b/lib/groups/setGroupDescription.js index 488bfbfd0..56ba860c0 100644 --- a/lib/groups/setGroupDescription.js +++ b/lib/groups/setGroupDescription.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group'] @@ -41,15 +42,7 @@ function changeGroupDesc (group, description = '', jar, xcsrf) { if (res.statusCode === 200) { resolve(res.body) } else { - const body = res.body || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } else { - reject(new Error(`${res.statusCode} ${res.body}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/groups/setGroupName.js b/lib/groups/setGroupName.js index b477b956c..db8980e8c 100644 --- a/lib/groups/setGroupName.js +++ b/lib/groups/setGroupName.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group', 'name'] @@ -42,15 +43,7 @@ function changeGroupName (group, name, jar, xcsrf) { if (res.statusCode === 200) { resolve(res.body) } else { - const body = res.body || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } else { - reject(new Error(`${res.statusCode} ${res.body}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/groups/setRank.js b/lib/groups/setRank.js index b836c2622..8704244de 100644 --- a/lib/groups/setRank.js +++ b/lib/groups/setRank.js @@ -2,6 +2,7 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func const getRole = require('./getRole.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group', 'target', 'rank'] @@ -44,13 +45,7 @@ function setRank (jar, xcsrf, group, target, role) { if (res.statusCode === 200) { resolve(role) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/groups/shout.js b/lib/groups/shout.js index f9487c350..cd9de606d 100644 --- a/lib/groups/shout.js +++ b/lib/groups/shout.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group'] @@ -41,15 +42,7 @@ function shoutOnGroup (group, message = '', jar, xcsrf) { if (res.statusCode === 200) { resolve(res.body) } else { - const body = res.body || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } else { - reject(new Error(`${res.statusCode} ${res.body}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/inventory/getOwnership.js b/lib/inventory/getOwnership.js index cf346f5ba..a305d60de 100644 --- a/lib/inventory/getOwnership.js +++ b/lib/inventory/getOwnership.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId', 'itemTargetId'] @@ -34,15 +35,7 @@ function getOwnership (userId, itemTargetId, itemType) { const body = JSON.parse(res.body) resolve(body.data.length > 0) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } else { - reject(new Error(`${res.statusCode} ${res.body}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/premiumfeatures/getPremium.js b/lib/premiumfeatures/getPremium.js index 6db6141d3..73a728ca6 100644 --- a/lib/premiumfeatures/getPremium.js +++ b/lib/premiumfeatures/getPremium.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -33,13 +34,7 @@ function getPremium (jar, userId) { if (res.statusCode === 200) { resolve(res.body === 'true') } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/presence/getPresences.js b/lib/presence/getPresences.js index a27b6c750..3cf889f9f 100644 --- a/lib/presence/getPresences.js +++ b/lib/presence/getPresences.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userIds'] @@ -41,11 +42,7 @@ function getPresences (userIds, jar, xcsrf) { .then(function (res) { const responseData = JSON.parse(res.body) if (res.statusCode !== 200) { - let error = 'An unknown error has occurred.' - if (responseData && responseData.errors) { - error = responseData.errors.map((e) => e.message).join('\n') - } - reject(new Error(error)) + reject(new RobloxAPIError(res)) } else { resolve(responseData) } diff --git a/lib/privatemessages/getMessages.js b/lib/privatemessages/getMessages.js index 801c50f87..8eec1a57b 100644 --- a/lib/privatemessages/getMessages.js +++ b/lib/privatemessages/getMessages.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = [] exports.optional = ['pageNumber', 'pageSize', 'messageTab', 'jar'] @@ -34,13 +35,7 @@ function getMessages (jar, pageNumber, pageSize, messageTab) { if (res.statusCode === 200) { resolve(JSON.parse(res.body)) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/privatemessages/message.js b/lib/privatemessages/message.js index 9870c2026..4b0fec2b0 100644 --- a/lib/privatemessages/message.js +++ b/lib/privatemessages/message.js @@ -4,6 +4,7 @@ const queue = require('../internal/queue.js') const getGeneralToken = require('../util/getGeneralToken.js').func const getHash = require('../util/getHash.js').func const getSenderId = require('../util/getSenderUserId.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['recipient', 'subject', 'body'] @@ -53,13 +54,7 @@ function message (jar, token, senderId, recipient, subject, body, replyMessageId if (res.statusCode === 200) { resolve(JSON.parse(res.body)) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }) }) diff --git a/lib/thumbnails/getLogo.js b/lib/thumbnails/getLogo.js index 7dfdc810b..0c305b5d4 100644 --- a/lib/thumbnails/getLogo.js +++ b/lib/thumbnails/getLogo.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['group'] @@ -30,22 +31,17 @@ function getLogo (group, size, circular, format) { format: format || 'Png', isCircular: circular }, - json: true + json: true, + resolveWithFullResponse: true } } return http(httpOpt) - .then(function (body) { - const error = body.errors && body.errors[0] - - if (error) { - if (error.message === 'NotFound') { - throw new Error('An invalid UserID or GroupID was provided.') - } else { - throw new Error(error.message) - } + .then(function (res) { + if (res.statusCode !== 200) { + throw new RobloxAPIError(res) } - const thumbnailData = body.data[0] + const thumbnailData = res.body.data[0] if (thumbnailData.state !== 'Completed') { throw new Error('The requested image has not been approved. Status: ' + thumbnailData.state) diff --git a/lib/thumbnails/getPlayerThumbnail.js b/lib/thumbnails/getPlayerThumbnail.js index 832bffd2b..8a2529154 100644 --- a/lib/thumbnails/getPlayerThumbnail.js +++ b/lib/thumbnails/getPlayerThumbnail.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const { thumbnail: settings } = require('../../settings.json') +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userIds'] @@ -85,9 +86,9 @@ function getPlayerThumbnail (userIds, size, format = 'png', isCircular = false, followRedirect: true } }) - .then(async ({ statusCode, body }) => { - let { data, errors } = JSON.parse(body) - if (statusCode === 200) { + .then(async (res) => { + let { data } = JSON.parse(res.body) + if (res.statusCode === 200) { if (retryCount > 0) { const pendingThumbnails = data.filter(obj => { return obj.state === 'Pending' }).map(obj => obj.targetId) // Get 'Pending' thumbnails as array of userIds if (pendingThumbnails.length > 0) { @@ -104,10 +105,8 @@ function getPlayerThumbnail (userIds, size, format = 'png', isCircular = false, return obj }) return data - } else if (statusCode === 400) { - throw new Error(`Error Code ${errors.code}: ${errors.message} | endpoint: ${endpoint}, userIds: ${userIds.join(',')}, size: ${size}, isCircular: ${!!isCircular}`) } else { - throw new Error(`An unknown error occurred with getPlayerThumbnail() | endpoint: ${endpoint}, userIds: ${userIds.join(',')}, size: ${size}, isCircular: ${!!isCircular}`) + throw new RobloxAPIError(res) } }) } diff --git a/lib/thumbnails/getThumbnails.js b/lib/thumbnails/getThumbnails.js index 6c11c4f66..4af13df9e 100644 --- a/lib/thumbnails/getThumbnails.js +++ b/lib/thumbnails/getThumbnails.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const { thumbnail: settings } = require('../../settings.json') +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['thumbnailRequests'] @@ -58,9 +59,9 @@ function getThumbnails (requests, retryCount = settings.maxRetries) { followRedirect: true } }) - .then(async ({ statusCode, body }) => { - let { data, errors } = body - if (statusCode === 200) { + .then(async (res) => { + let { data } = res.body + if (res.statusCode === 200) { if (retryCount > 0) { const pendingThumbnails = data.filter(obj => { return obj.state === 'Pending' }).map(obj => obj.targetId) // Get 'Pending' thumbnails as array of userIds if (pendingThumbnails.length > 0) { @@ -76,10 +77,8 @@ function getThumbnails (requests, retryCount = settings.maxRetries) { return obj }) return data - } else if (statusCode === 400) { - throw new Error(`Error Code ${errors.code}: ${errors.message} | requests: ${JSON.stringify(requests)}`) } else { - throw new Error(`An unknown error occurred with getThumbnails() | requests: ${JSON.stringify(requests)}`) + throw new RobloxAPIError(res) } }) } diff --git a/lib/trades/acceptTrade.js b/lib/trades/acceptTrade.js index d34ba9785..67c542107 100644 --- a/lib/trades/acceptTrade.js +++ b/lib/trades/acceptTrade.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['tradeId'] @@ -36,14 +37,7 @@ function acceptTrade (tradeId, jar, xcsrf) { if (res.statusCode === 200) { resolve() } else { - const body = JSON.parse(res.body) || {} - - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/trades/canTradeWith.js b/lib/trades/canTradeWith.js index 376d504f6..98bb27f79 100644 --- a/lib/trades/canTradeWith.js +++ b/lib/trades/canTradeWith.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -31,13 +32,7 @@ function canTradeWith (jar, userId) { if (res.statusCode === 200) { resolve(JSON.parse(res.body)) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/trades/counterTrade.js b/lib/trades/counterTrade.js index 6e24c34ed..29df6e9ad 100644 --- a/lib/trades/counterTrade.js +++ b/lib/trades/counterTrade.js @@ -2,6 +2,7 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func const getCurrentUser = require('../util/getCurrentUser.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['tradeId', 'targetUserId', 'sendingOffer', 'receivingOffer'] @@ -59,14 +60,7 @@ function counterTrade (tradeId, targetUserId, sendingOffer, receivingOffer, jar, if (res.statusCode === 200) { resolve(JSON.parse(res.body)) } else { - const body = JSON.parse(res.body) || {} - - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/trades/declineTrade.js b/lib/trades/declineTrade.js index ef132b144..4b7dfbce7 100644 --- a/lib/trades/declineTrade.js +++ b/lib/trades/declineTrade.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['tradeId'] @@ -36,14 +37,7 @@ function declineTrade (tradeId, jar, xcsrf) { if (res.statusCode === 200) { resolve() } else { - const body = JSON.parse(res.body) || {} - - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/trades/getTradeInfo.js b/lib/trades/getTradeInfo.js index a49c7d037..a65bd1392 100644 --- a/lib/trades/getTradeInfo.js +++ b/lib/trades/getTradeInfo.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['tradeId'] @@ -35,13 +36,7 @@ const getTradeInfo = (jar, tradeId) => { resolve(body) } else { - const body = JSON.parse(res.body) || {} - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/trades/sendTrade.js b/lib/trades/sendTrade.js index 40706d5f8..ec285de43 100644 --- a/lib/trades/sendTrade.js +++ b/lib/trades/sendTrade.js @@ -2,6 +2,7 @@ const http = require('../util/http.js').func const getGeneralToken = require('../util/getGeneralToken.js').func const getCurrentUser = require('../util/getCurrentUser.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['targetUserId', 'sendingOffer', 'receivingOffer'] @@ -57,14 +58,7 @@ function sendTrade (targetUserId, sendingOffer, receivingOffer, jar, xcsrf, logg if (res.statusCode === 200) { resolve(JSON.parse(res.body)) } else { - const body = JSON.parse(res.body) || {} - - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) - } + reject(new RobloxAPIError(res)) } }).catch(error => reject(error)) }) diff --git a/lib/users/getBlurb.js b/lib/users/getBlurb.js index ccae641fb..2793a3f4c 100644 --- a/lib/users/getBlurb.js +++ b/lib/users/getBlurb.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -30,7 +31,7 @@ exports.func = function (args) { const parsedBody = JSON.parse(res.body) return parsedBody.description } else { - throw new Error('User does not exist') + throw new RobloxAPIError(res) } }) } diff --git a/lib/users/getIdFromUsername.js b/lib/users/getIdFromUsername.js index d3729fa3a..95ad3ab29 100644 --- a/lib/users/getIdFromUsername.js +++ b/lib/users/getIdFromUsername.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const cache = require('../cache') +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['username'] @@ -27,12 +28,14 @@ function getIdFromUsername (usernames) { json: { usernames, excludeBannedUsers: false - } + }, + resolveWithFullResponse: true } } return http(httpOpt) - .then(function (body) { - const data = body.data + .then(function (res) { + if (res.statusCode !== 200) throw new RobloxAPIError(res) + const data = res.body.data let results = usernames.map((username) => { return data.find((result) => result.requestedUsername === username) diff --git a/lib/users/getUserInfo.js b/lib/users/getUserInfo.js index df1b35cb3..44dfbe2a3 100644 --- a/lib/users/getUserInfo.js +++ b/lib/users/getUserInfo.js @@ -1,5 +1,6 @@ // Includes const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['userId'] @@ -25,7 +26,7 @@ exports.func = function (args) { } return http(httpOpt).then(function (res) { - if (res.statusCode !== 200) { throw new Error(`Failed to fetch user information: ${res.body?.errors?.at(0)?.message}`) } + if (res.statusCode !== 200) { throw new RobloxAPIError(res) } res.body.created = new Date(res.body.created) diff --git a/lib/users/getUsernameFromId.js b/lib/users/getUsernameFromId.js index cb2f2d969..7e7c515e7 100644 --- a/lib/users/getUsernameFromId.js +++ b/lib/users/getUsernameFromId.js @@ -1,6 +1,7 @@ // Includes const http = require('../util/http.js').func const cache = require('../cache') +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['id'] @@ -31,7 +32,7 @@ function getUsernameFromId (id) { const json = JSON.parse(res.body) return json.name } else { - throw new Error('User does not exist') + throw new RobloxAPIError(res) } }) } diff --git a/lib/util/apiError.js b/lib/util/apiError.js index 91796211f..045eb2587 100644 --- a/lib/util/apiError.js +++ b/lib/util/apiError.js @@ -26,6 +26,15 @@ class RobloxAPIError extends Error { return obj } else return { code: 0, message: this.responseBody } // Roblox did a funny (i.e. the platform is down) } + + get caller () { + try { + // https://stackoverflow.com/a/57023880 + return (new Error()).stack?.split('\n')[2]?.trim().split(' ').at(1) ?? null + } catch { + return null + } + } } function getResponseBody (data) { @@ -38,4 +47,4 @@ function getResponseBody (data) { } } -export default RobloxAPIError +module.exports = RobloxAPIError From 11f45ec662fa7eaffea7fb6726c8d0c8b8cf8ac5 Mon Sep 17 00:00:00 2001 From: Josh Muir Date: Fri, 1 Aug 2025 23:44:39 +0100 Subject: [PATCH 05/28] First attempt at new release system --- .github/workflows/npmpublish.yml | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml index 133306eff..431901995 100644 --- a/.github/workflows/npmpublish.yml +++ b/.github/workflows/npmpublish.yml @@ -4,22 +4,24 @@ on: pull_request: branches: - master + - next push: branches: - master + - next jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up node - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: Install yarn run: npm install -g yarn @@ -35,18 +37,8 @@ jobs: if: github.event_name == 'push' runs-on: ubuntu-latest steps: - - name: Checkout code with ADMIN_TOKEN - if: github.event.pull_request.head.repo.full_name == github.repository - uses: actions/checkout@v2 - with: - token: ${{ secrets.ADMIN_TOKEN }} - - name: Checkout code - if: github.event.pull_request.head.repo.full_name != github.repository - uses: actions/checkout@v2 - - - name: Prepare repository - run: git fetch --unshallow --tags + uses: actions/checkout@v4 - name: Set up node uses: actions/setup-node@v1 @@ -60,7 +52,7 @@ jobs: run: yarn install --frozen-lockfile - name: Cache node modules - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: node_modules key: yarn-deps-${{ hashFiles('yarn.lock') }} @@ -68,8 +60,8 @@ jobs: yarn-deps-${{ hashFiles('yarn.lock') }} - name: Create Release + run: npx semantic-release@21.0.2 env: - token: ${{ secrets.ADMIN_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} - run: | - npx auto shipit + From 3f36ca613aa05d479ea4508d27302adf77335e35 Mon Sep 17 00:00:00 2001 From: Josh Muir Date: Fri, 1 Aug 2025 23:49:17 +0100 Subject: [PATCH 06/28] BREAKING CHANGE: Document new versioning --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b1b776ac0..b0c6b5500 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ This NPM package enables operations froms the [Roblox website](https://www.roblo If you are looking for more information on how to create something like this, check out [our sister library, `noblox.js-server`](https://github.com/noblox/noblox.js-server) or [our YouTube series](https://www.youtube.com/playlist?list=PLEW4K4VqMUb_VMA3Yp9LI4gReRyVWGTnU). Keep in mind that these resources may not always be up to date, so it is **highly** encouraged that you learn to use the `noblox.js` library directly. + +Note: We use the semantic release plugin. You should treat npm as the source of truth regarding package versions - the package.json version in this repository will not be up to date. --- ## Prerequisites From 138cf4facc517c2e4fa8df9c18fc708850c178e0 Mon Sep 17 00:00:00 2001 From: Josh Muir Date: Fri, 1 Aug 2025 23:57:21 +0100 Subject: [PATCH 07/28] fix: add release branches --- package.json | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index fd3bf8e8c..94be2b22b 100644 --- a/package.json +++ b/package.json @@ -67,12 +67,8 @@ "lib/*/on*.js" ] }, - "auto": { - "plugins": [ - "npm", - "conventional-commits" - ], - "onlyPublishWithReleaseLabel": true + "release": { + "branches": ["master", "next"] }, "engines": { "node": ">=18.18" From 78ee5897b304732424d4a9532c379144f0bb4874 Mon Sep 17 00:00:00 2001 From: Josh Muir Date: Sat, 2 Aug 2025 00:02:11 +0100 Subject: [PATCH 08/28] ci: Add new release workflow BREAKING CHANGE: Trigger new major release to cover previously made major changes --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index a07694c58..5d1bc82ca 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -22,8 +22,8 @@ Steps to reproduce the behavior: **Expected behavior** A clear and concise description of what you expected to happen. -**Screenshots** -If applicable, add screenshots to help explain your problem. +**Error messages** +If applicable, add error messages to help explain your problem. **Additional context** Add any other context about the problem here. From fbe035a3c62b5ed0ac9df84fba537a7af63c5212 Mon Sep 17 00:00:00 2001 From: Regalijan <72576136+Regalijan@users.noreply.github.com> Date: Fri, 8 Aug 2025 08:43:05 -0400 Subject: [PATCH 09/28] fix: Update user block/unblock endpoints (#869) --- lib/accountsettings/block.js | 2 +- lib/accountsettings/unblock.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/accountsettings/block.js b/lib/accountsettings/block.js index fe3123b44..d88136e99 100644 --- a/lib/accountsettings/block.js +++ b/lib/accountsettings/block.js @@ -23,7 +23,7 @@ exports.optional = ['jar'] function block (jar, token, userId) { return new Promise((resolve, reject) => { const httpOpt = { - url: `//accountsettings.roblox.com/v1/users/${userId}/block`, + url: `https://apis.roblox.com/user-blocking-api/v1/users/${userId}/block-user`, options: { method: 'POST', jar, diff --git a/lib/accountsettings/unblock.js b/lib/accountsettings/unblock.js index c43937827..daecaf3e6 100644 --- a/lib/accountsettings/unblock.js +++ b/lib/accountsettings/unblock.js @@ -23,7 +23,7 @@ exports.optional = ['jar'] function unblock (jar, token, userId) { return new Promise((resolve, reject) => { const httpOpt = { - url: `//accountsettings.roblox.com/v1/users/${userId}/unblock`, + url: `https://apis.roblox.com/user-blocking-api/v1/users/${userId}/unblock-user`, options: { method: 'POST', jar, From 68602564fdb81451f9bcd3610bad5b2de78507f6 Mon Sep 17 00:00:00 2001 From: Commonly <51011212+commonly-ts@users.noreply.github.com> Date: Sat, 9 Aug 2025 00:50:43 +1200 Subject: [PATCH 10/28] feat: UserNotification Cloud API (#851) * Create new function * Updated and added typings * Fixed linting * Fixed linting electric boogaloo linting better be happy now... * Linting final touches of grace * Linting obliteration of an unworthy comma * Added typings to jsDocs * Fix error handling * Add test case for sendUserNotification * Remove extra await * Migrate to new error class --------- Co-authored-by: Regalijan <72576136+Regalijan@users.noreply.github.com> --- lib/games/sendUserNotification.js | 65 +++++++++++++++++++++++++++++++ lib/index.js | 1 + test/games.test.js | 19 ++++++++- typings/index.d.ts | 14 ++++++- typings/jsDocs.ts | 12 ++++++ 5 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 lib/games/sendUserNotification.js diff --git a/lib/games/sendUserNotification.js b/lib/games/sendUserNotification.js new file mode 100644 index 000000000..e8220b68c --- /dev/null +++ b/lib/games/sendUserNotification.js @@ -0,0 +1,65 @@ +const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') + +exports.required = ['universeId', 'userId', 'assetId', 'parameters'] +exports.optional = ['jar'] + +// Docs +/** + * ☁️ Send a universe notification to a user. + * @category Game + * @alias sendUserNotification + * @param {number} universeId - The id of the universe. + * @param {number} userId - The id of the target player. + * @param {string} assetId - The asset id of the notification. + * @param {UserNotificationPayloadParameters} parameters - The notification parameters. + * @returns {Promise} + * @example const noblox = require("noblox.js") + * // Set API key + * const parameters = { + * myMessage: { stringValue: "Hello world!" } + * } + * const assetId = "774d62e5-3414-b84a-89dd-77d96f1a3d33" + * noblox.sendUserNotification(4434277335, 1210019099, assetId, parameters) + **/ + +function sendUserNotification (universeId, userId, assetId, parameters, jar) { + return new Promise((resolve, reject) => { + const httpOpt = { + url: `//apis.roblox.com/cloud/v2/users/${userId}/notifications`, + options: { + json: true, + resolveWithFullResponse: true, + jar, + method: 'POST', + body: { + source: { + universe: `universes/${universeId}` + }, + payload: { + type: 'MOMENT', + messageId: assetId, + parameters + } + }, + headers: { + 'Content-Type': 'application/json' + } + } + } + + return http(httpOpt) + .then(function (res) { + if (res.statusCode === 200) { + resolve(true) + } else { + reject(new RobloxAPIError(res)) + } + }) + .catch(reject) + }) +} + +exports.func = function (args) { + return sendUserNotification(args.universeId, args.userId, args.assetId, args.parameters, args.jar) +} diff --git a/lib/index.js b/lib/index.js index 073544ab1..fc1f89f4e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -98,6 +98,7 @@ noblox.getGameSocialLinks = require('./games/getGameSocialLinks.js') noblox.getGroupGames = require('./games/getGroupGames.js') noblox.getPlaceInfo = require('./games/getPlaceInfo.js') noblox.getUniverseInfo = require('./games/getUniverseInfo.js') +noblox.sendUserNotification = require('./games/sendUserNotification.js') noblox.publishToTopic = require('./games/publishToTopic.js') noblox.updateDeveloperProduct = require('./games/updateDeveloperProduct.js') noblox.changeRank = require('./groups/changeRank.js') diff --git a/test/games.test.js b/test/games.test.js index ac4ee8c96..dc9a85123 100644 --- a/test/games.test.js +++ b/test/games.test.js @@ -1,6 +1,8 @@ -const { addDeveloperProduct, checkDeveloperProductName, getGroupGames, configureGamePass, getGameInstances, getGamePasses, getGameSocialLinks, getUniverseInfo, setCookie } = require('../lib') +const { addDeveloperProduct, checkDeveloperProductName, getAuthenticatedUser, getGroupGames, configureGamePass, getGameInstances, getGamePasses, getGameSocialLinks, getUniverseInfo, sendUserNotification, setAPIKey, setCookie } = require('../lib') beforeAll(() => { + setAPIKey(process.env.API_KEY) + return new Promise(resolve => { setCookie(process.env.COOKIE).then(() => { resolve() @@ -193,6 +195,21 @@ it('getPlaceInfo() should return an array of information about places', () => { }) }) + it('sendUserNotification() should successfully send a user notification', async () => { + const { id: userId } = await getAuthenticatedUser() + + return await expect( + sendUserNotification({ + universeId: 2152417643, + userId, + assetId: "", + parameters: { + test: "test" + } + }) + ).resolves.not.toThrow() + }) + // Dependency on getDeveloperProducts() which is broken as of 4.14.0 // eslint-disable-next-line jest/no-commented-out-tests // it('updateDeveloperProduct() should update a developer product with new information', () => { diff --git a/typings/index.d.ts b/typings/index.d.ts index efbc1c7d6..fa959f847 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -11,6 +11,7 @@ declare module "noblox.js" { */ interface CookieJar { session?: string; + apiKey?: string; } /** @@ -571,6 +572,12 @@ declare module "noblox.js" { } /// Game + type UserNotificationPayloadParameter = { stringValue: string } | { int64Value: number }; + + interface UserNotificationPayloadParameters { + [key: string]: UserNotificationPayloadParameter + } + interface GameInstance { id: string; maxPlayers: number; @@ -1900,6 +1907,11 @@ declare module "noblox.js" { */ function publishToTopic(universeId: number, topic: string, data: (Object | string), jar?: CookieJar): Promise; + /** +* ☁️ Send a universe notification to a user. +*/ + function sendUserNotification(universeId: number, userId: number, assetId: string, parameters: UserNotificationPayloadParameters, jar?: CookieJar): Promise; + /** * 🔐 Returns information about the place(s) in question, such as name, description, etc. */ @@ -2230,7 +2242,7 @@ declare module "noblox.js" { * 🔐 Get the current authenticated user. */ function getAuthenticatedUser(jar?: CookieJar): Promise - + /** * 🔐 Gets the current user logged into `jar` and returns an `option` if specified or all options if not. */ diff --git a/typings/jsDocs.ts b/typings/jsDocs.ts index c6f3ad2c2..042f03a6a 100644 --- a/typings/jsDocs.ts +++ b/typings/jsDocs.ts @@ -711,6 +711,18 @@ type OnUserTypingChatEvent = { } /// Game +/** + * @typedef + */ +type UserNotificationPayloadParameter = { stringValue: string } | { int64Value: number }; + +/** + * @typedef + */ +type UserNotificationPayloadParameters = { + [key: string]: UserNotificationPayloadParameter +} + /** * @typedef */ From 5bb6344ddb8afa81780852e9d577af3252bddf42 Mon Sep 17 00:00:00 2001 From: Regalijan <72576136+Regalijan@users.noreply.github.com> Date: Fri, 8 Aug 2025 08:53:20 -0400 Subject: [PATCH 11/28] feat: Add group ban methods (#872) * Create banFromGroup method * Create unbanFromGroup * Change return type of banFromGroup * Create getGroupBans * Add GroupBan type * Add function types for new methods * We need to actually return the response body * Change limit type to number getPageResults will accept numbers higher than 100 and will paginate * getPageResults only returns the data property * Change function type to accept number for limit * Add test cases for banFromGroup, getGroupBans, and unbanFromGroup --- lib/groups/banFromGroup.js | 47 ++++++++++++++++++++++++++ lib/groups/getGroupBans.js | 37 +++++++++++++++++++++ lib/groups/unbanFromGroup.js | 41 +++++++++++++++++++++++ test/groups.test.js | 64 +++++++++++++++++++++++++++++++++++- typings/index.d.ts | 25 ++++++++++++++ typings/jsDocs.ts | 12 +++++++ 6 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 lib/groups/banFromGroup.js create mode 100644 lib/groups/getGroupBans.js create mode 100644 lib/groups/unbanFromGroup.js diff --git a/lib/groups/banFromGroup.js b/lib/groups/banFromGroup.js new file mode 100644 index 000000000..e62cc55b8 --- /dev/null +++ b/lib/groups/banFromGroup.js @@ -0,0 +1,47 @@ +// Includes +const http = require('../util/http.js') +const getGeneralToken = require('../util/getGeneralToken.js') +const RobloxAPIError = require('../util/apiError.js') + +// Args +exports.required = ['groupId', 'userId'] +exports.optional = ['jar'] + +// Docs +/** + * 🔐 Bans a user from the specified group. + * @alias banFromGroup + * @param {number} groupId - The ID of the group + * @param {number} userId - The ID of the target user + * @returns {Promise} + * @example const noblox = require("noblox.js") + * // Log in + * await noblox.banFromGroup(1, 2) +**/ + +// Define +exports.func = async function (args) { + const { groupId, jar, userId } = args + const token = await getGeneralToken({ jar }) + + const response = await http({ + url: `//groups.roblox.com/v1/groups/${groupId}/bans/${userId}`, + options: { + method: 'POST', + jar, + headers: { + 'x-csrf-token': token + }, + resolveWithFullResponse: true + } + }) + + if (response.statusCode !== 200) { + throw new RobloxAPIError(response) + } + + const body = JSON.parse(response.body) + body.created = new Date(body.created) + + return body +} diff --git a/lib/groups/getGroupBans.js b/lib/groups/getGroupBans.js new file mode 100644 index 000000000..007d2bc4c --- /dev/null +++ b/lib/groups/getGroupBans.js @@ -0,0 +1,37 @@ +// Includes +const getPageResults = require('../util/getPageResults.js').func + +// Args +exports.required = ['groupId'] +exports.optional = ['limit', 'sortOrder', 'pageCursor', 'jar'] + +// Docs +/** + * 🔐 Gets bans of a group + * @category Group + * @alias getGroupBans + * @param {number} groupId - The ID of the group + * @param {number=} limit - The number of bans to fetch maximum per page + * @param {SortOrder=} sortOrder - The order to sort the bans + * @param {string=} pageCursor - The next or previous page's cursor + * @returns {Promise} + * @example const noblox = require("noblox.js") + * await noblox.getGroupBans({ groupId: 1, limit: 100, sortOrder: "Desc" }) +**/ + +// Define +exports.func = async function (args) { + let { groupId, jar, limit, pageCursor, sortOrder } = args + limit ||= 100 + sortOrder ||= 'Desc' + + return await getPageResults({ + url: `//groups.roblox.com/v1/groups/${groupId}/bans`, + query: { + sortOrder + }, + pageCursor, + limit, + jar + }) +} diff --git a/lib/groups/unbanFromGroup.js b/lib/groups/unbanFromGroup.js new file mode 100644 index 000000000..49243d6d9 --- /dev/null +++ b/lib/groups/unbanFromGroup.js @@ -0,0 +1,41 @@ +// Includes +const http = require('../util/http.js') +const getGeneralToken = require('../util/getGeneralToken.js') +const RobloxAPIError = require('../util/apiError.js') + +// Args +exports.required = ['groupId', 'userId'] +exports.optional = ['jar'] + +// Docs +/** + * 🔐 Unbans a user from the specified group. + * @alias unbanFromGroup + * @param {number} groupId - The ID of the group + * @param {number} userId - The ID of the target user + * @returns {Promise} + * @example const noblox = require("noblox.js") + * // Log in + * await noblox.unbanFromGroup(1, 2) +**/ + +// Define +exports.func = async function (args) { + const { groupId, jar, userId } = args + const token = await getGeneralToken({ jar }) + + const response = await http({ + url: `//groups.roblox.com/v1/groups/${groupId}/bans/${userId}`, + options: { + method: 'DELETE', + jar, + headers: { + 'x-csrf-token': token + } + } + }) + + if (response.statusCode !== 200) { + throw new RobloxAPIError(response) + } +} diff --git a/test/groups.test.js b/test/groups.test.js index 2ad99d008..9f45b405e 100644 --- a/test/groups.test.js +++ b/test/groups.test.js @@ -1,4 +1,4 @@ -const { changeRank, demote, getAuditLog, getGroup, getGroups, getGroupSocialLinks, getJoinRequests, getPlayers, getRankInGroup, getRankNameInGroup, getRole, getRolePermissions, getRoles, getShout, getWall, promote, searchGroups, setRank, shout, setCookie } = require('../lib') +const { banFromGroup, changeRank, demote, getAuditLog, getGroup, getGroupBans, getGroups, getGroupSocialLinks, getJoinRequests, getPlayers, getRankInGroup, getRankNameInGroup, getRole, getRolePermissions, getRoles, getShout, getWall, promote, searchGroups, setRank, shout, setCookie, unbanFromGroup } = require('../lib') beforeAll(() => { return new Promise(resolve => { @@ -308,6 +308,68 @@ describe('Groups Methods', () => { }) }) }) + + it('banFromGroup() should ban a user from the specified group', () => { + return banFromGroup(4591072, 1).then((res) => { + return expect(res).toMatchObject({ + user: { + hasVerifiedBadge: expect.any(Boolean), + userId: expect.any(Number), + username: expect.any(String), + displayName: expect.any(String) + }, + actingUser: { + user: { + hasVerifiedBadge: expect.any(Boolean), + userId: expect.any(Number), + username: expect.any(String), + displayName: expect.any(String) + }, + role: { + id: expect.any(Number), + name: expect.any(String), + rank: expect.any(Number) + } + }, + created: expect.any(Date) + }) + }) + }) + + it('getGroupBans() should retrieve a page of group bans', () => { + return getGroupBans(4591072, 10, 'Asc').then((res) => { + return expect(res).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + user: { + hasVerifiedBadge: expect.any(Boolean), + userId: expect.any(Number), + username: expect.any(String), + displayName: expect.any(String) + }, + actingUser: { + user: { + hasVerifiedBadge: expect.any(Boolean), + userId: expect.any(Number), + username: expect.any(String), + displayName: expect.any(String) + }, + role: { + id: expect.any(Number), + name: expect.any(String), + rank: expect.any(Number) + } + }, + created: expect.any(Date) + }) + ]) + ) + }) + }) + + it('unbanFromGroup() should unban a user from a group', async () => { + await expect(unbanFromGroup(4591072, 1)).resolves.not.toThrow() + }) }) it('setRank() should set a player\'s rank to the specified rank', () => { diff --git a/typings/index.d.ts b/typings/index.d.ts index fa959f847..0cfea6eeb 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -961,6 +961,14 @@ declare module "noblox.js" { data: WallPost[]; } + interface GroupBan { + user: UserEntry; + actingUser: { + user: UserEntry; + }, + created: Date; + } + /// Party interface PartyData { @@ -1086,6 +1094,7 @@ declare module "noblox.js" { // interface UserEntry { + hasVerifiedBadge: boolean; userId: number; name: string; displayName: string; @@ -1923,6 +1932,12 @@ declare module "noblox.js" { function updateDeveloperProduct(universeId: number, productId: number, priceInRobux: number, name?: string, description?: string, jar?: CookieJar): Promise; /// Groups + + /** + * 🔐 Bans a user from the specified group. + */ + function banFromGroup(groupId: number, userId: number, jar?: CookieJar): Promise; + /** * 🔐 Moves the user with userId `target` up or down the list of ranks in `group` by `change`. For example `changeRank(group, target, 1)` would promote the user 1 rank and `changeRank(group, target, -1)` would demote them down 1. Note that this simply follows the list, ignoring ambiguous ranks. The full `newRole` as well as the user's original `oldRole` is returned. */ @@ -1958,6 +1973,11 @@ declare module "noblox.js" { */ function getGroup(groupId: number): Promise; + /** + * 🔐 Gets a list of the group's bans. + */ + function getGroupBans(groupId: number, limit?: number, sortOrder?: SortOrder, pageCursor?: string, jar?: CookieJar): Promise<{ previousPageCursor?: string, nextPageCursor?: string, data: GroupBan[] }>; + /** * ✅ Gets the groups a player is in. */ @@ -2072,6 +2092,11 @@ declare module "noblox.js" { */ function shout(group: number, message: string, jar?: CookieJar): Promise; + /** + * 🔐 Unbans a user from the specified group. + */ + function unbanFromGroup(groupId: number, userId: number, jar?: CookieJar): Promise; + /// Inventory /** diff --git a/typings/jsDocs.ts b/typings/jsDocs.ts index 042f03a6a..0674ea9f3 100644 --- a/typings/jsDocs.ts +++ b/typings/jsDocs.ts @@ -1337,6 +1337,17 @@ type WallPostPage = { data: Array; } +/** + * @typedef +*/ +type GroupBan = { + user: UserEntry; + actingUser: { + user: UserEntry; + } + created: Date; +} + /// Party /** @@ -1498,6 +1509,7 @@ type PrivateMessagesPage = { * @typedef */ type UserEntry = { + hasVerifiedBadge: boolean; userId: number; name: string; displayName: string; From 29b53c4488b026996f75a62a5394712eb919a77b Mon Sep 17 00:00:00 2001 From: Regalijan <72576136+Regalijan@users.noreply.github.com> Date: Mon, 11 Aug 2025 01:03:02 -0400 Subject: [PATCH 12/28] fix: Update user agent per Roblox's request (#870) * fix: Update user agent per Roblox's request * fix: Change header check for request forwarding UA enforcement means there will always be headers now * Add lock_ua setting * lock_ua -> use_noblox_ua * Set ua in request defaults if enabled * Le lint * Add user-agent documentation * Correct documentation --------- Co-authored-by: Josh Muir --- lib/util/http.js | 12 +++++++++--- settings.json | 2 ++ typings/index.d.ts | 7 +++++++ typings/jsDocs.ts | 23 +++++++++++++++-------- 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/lib/util/http.js b/lib/util/http.js index d38c7dd42..8675fad78 100644 --- a/lib/util/http.js +++ b/lib/util/http.js @@ -7,6 +7,7 @@ const options = require('../options.js') const settings = require('../../settings.json') const cache = require('../cache') const getHash = require('./getHash.js').func +const { version } = require('../../package.json') // Args exports.required = ['url'] @@ -20,7 +21,12 @@ request = request.defaults({ }, simple: false, gzip: true, - timeout: settings.timeout + timeout: settings.timeout, + headers: settings.use_noblox_ua + ? { + 'user-agent': `noblox-${version}` + } + : undefined }) // Docs @@ -73,9 +79,9 @@ function http (url, opt) { This is a problem as Roblox locks sessions to a particular region Therefore, we intercept the request during testing and send it to the forwarder (which has a static ip address), the Roblox hostname is set as the Destination-Host header The ID token is for Google Cloud and allows authenticating with the Cloud Run service that acts as our forwarder - Requests without headers do not go through the forwarder as they are unauthenticated and do not need forwarding + Requests without auth headers do not go through the forwarder as they are unauthenticated and do not need forwarding */ - if (process?.env.CI && process.env.FORWARDER_HOSTNAME && opt.headers) { + if (process?.env.CI && process.env.FORWARDER_HOSTNAME && (opt.headers.cookie || opt.headers['x-api-key'])) { const urlObj = new URL(url) const { hostname } = urlObj diff --git a/settings.json b/settings.json index 355526523..1daa550cd 100644 --- a/settings.json +++ b/settings.json @@ -1,4 +1,6 @@ { + "use_noblox_ua": true, + "show_deprecation_warnings": true, "show_deprecation_warnings_desc": "Prints console warnings for functions that are being polyfilled by newer methods due to upstream Roblox API changes", diff --git a/typings/index.d.ts b/typings/index.d.ts index 0cfea6eeb..b8f3b34fe 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -24,6 +24,13 @@ declare module "noblox.js" { /** Minimizes data usage and speed up requests by only saving session cookies, disable if you need other cookies to be saved as well. (Default: true) */ session_only: boolean; + /** + * Whether to send the noblox.js library user agent. Defaults to true. + * When enabled, noblox will set the user-agent on requests to 'noblox@{version}' where {version} is your library version. + * This allows Roblox to track endpoint and library usage. If you are privacy or aggregation conscious set this to false. + */ + use_noblox_ua: boolean; + /** This is usually used for functions that have to receive a lot of pages at once. Only this amount will be queued up as to preserve memory, make this as high as possible for fastest responses (although it will be somewhat limited by maxSockets). (Default: 50) */ max_threads: number; diff --git a/typings/jsDocs.ts b/typings/jsDocs.ts index 0674ea9f3..d33c25587 100644 --- a/typings/jsDocs.ts +++ b/typings/jsDocs.ts @@ -17,6 +17,13 @@ type NobloxOptions = { /** Prints console warnings for functions that are being polyfilled by newer methods due to upstream Roblox API changes */ show_deprecation_warnings: boolean; + /** + * Whether to send the noblox.js library user agent. Defaults to true. + * When enabled, noblox will set the user-agent on requests to 'noblox@{version}' where {version} is your library version. + * This allows Roblox to track endpoint and library usage. If you are privacy or aggregation conscious, set this to false. + */ + use_noblox_ua: boolean; + /** Minimizes data usage and speed up requests by only saving session cookies, disable if you need other cookies to be saved as well. (Default: true) */ session_only: boolean; @@ -804,7 +811,7 @@ type PlaceInformation = { price: number; imageToken: string; } - + /** * @typedef */ @@ -915,7 +922,7 @@ type UniverseSettings = { universeAnimationType?: "Standard" | "PlayerChoice"; universeCollisionType?: "InnerBox" | "OuterBox"; universeJointPositioningType?: "Standard" | "ArtistIntent"; - + isArchived?: boolean; isFriendsOnly?: boolean; @@ -929,7 +936,7 @@ type UniverseSettings = { isForSale?: boolean; price?: number; - + universeAvatarMinScales?: AvatarScale; universeAvatarMaxScales?: AvatarScale; @@ -957,7 +964,7 @@ type UniverseSettings = { universeAnimationType?: "Standard" | "PlayerChoice"; universeCollisionType?: "InnerBox" | "OuterBox"; universeJointPositioningType?: "Standard" | "ArtistIntent"; - + isArchived?: boolean; isFriendsOnly?: boolean; @@ -971,7 +978,7 @@ type UniverseSettings = { isForSale?: boolean; price?: number; - + universeAvatarMinScales?: AvatarScale; universeAvatarMaxScales?: AvatarScale; @@ -1901,7 +1908,7 @@ type GetLatestResponse = { /** * @typedef */ -type Datastore = { +type Datastore = { name: string; createdTime: Date; } @@ -1909,7 +1916,7 @@ type Datastore = { /** * @typedef */ -type DatastoresResult = { +type DatastoresResult = { datastores: Datastore[]; nextPageCursor?: string; } @@ -1917,7 +1924,7 @@ type DatastoresResult = { /** * @typedef */ -type EntryKey = { +type EntryKey = { scope: string; key: string; } From d02659739c9cbd7aab5b914c2fe18f10121493cf Mon Sep 17 00:00:00 2001 From: supercoolspy <66487448+supercoolspy@users.noreply.github.com> Date: Mon, 11 Aug 2025 00:16:06 -0700 Subject: [PATCH 13/28] feat: Add getGroupPayoutEligibility (#834) * Add canpay function * Add PayoutAllowedList type to jsdocs * Fix formatting * canPay -> getGroupPayoutEligibility * Only log first error message from endpoint (as if Roblox ever returned more than one) * Fix type for PayoutAllowedList * Add function type for getGroupPayoutEligibility * Document jar param in getGroupPayoutEligibility * Update example * Move getGroupPayoutEligibility to economy * Update import for getGroupPayoutEligibility * Move function type to economy section * Add test case Arguments will most likely need to be adjusted but that's easy to do * Allow checking multiple users * Update typings part 1 * Update jsdoc * Migrate error handling --------- Co-authored-by: Regalijan <72576136+Regalijan@users.noreply.github.com> --- lib/economy/getGroupPayoutEligibility.js | 55 ++++++++++++++++++++++++ lib/index.js | 1 + test/economy.test.js | 8 +++- typings/index.d.ts | 11 +++++ typings/jsDocs.ts | 9 ++++ 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 lib/economy/getGroupPayoutEligibility.js diff --git a/lib/economy/getGroupPayoutEligibility.js b/lib/economy/getGroupPayoutEligibility.js new file mode 100644 index 000000000..2210946de --- /dev/null +++ b/lib/economy/getGroupPayoutEligibility.js @@ -0,0 +1,55 @@ +// Includes +const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') + +// Args +exports.required = ['group', 'members'] +exports.optional = ['jar'] + +// Docs +/** + * 🔐 Get if a user can be paid from group funds. + * @category Group + * @alias getGroupPayoutEligibility + * @param {number} group - The id of the group. + * @param {number[]} members - The members to check payout status for. + * @returns {Promise} + * @example const noblox = require("noblox.js") + * // Log in with cookie + * const payoutStatus = await noblox.getGroupPayoutEligibility(1, [2]) + **/ + +// Define +function getGroupPayoutEligibility (group, members, jar) { + const memberList = [] + + for (const member of members) { + memberList.push(`userIds=${member}`) + } + + return new Promise((resolve, reject) => { + const httpOpt = { + url: `https://economy.roblox.com/v1/groups/${group}/users-payout-eligibility?${memberList.join('&')}`, + options: { + method: 'GET', + resolveWithFullResponse: true, + jar + } + } + + return http(httpOpt) + .then(function (res) { + const responseData = JSON.parse(res.body) + if (res.statusCode !== 200) { + reject(new RobloxAPIError(res)) + } else { + resolve(responseData) + } + }) + .catch(error => reject(error)) + }) +} + +exports.func = function (args) { + return getGroupPayoutEligibility(args.group, args.members, args.jar) +} diff --git a/lib/index.js b/lib/index.js index fc1f89f4e..97c5389ca 100644 --- a/lib/index.js +++ b/lib/index.js @@ -108,6 +108,7 @@ noblox.demote = require('./groups/demote.js') noblox.exile = require('./groups/exile.js') noblox.getAuditLog = require('./groups/getAuditLog.js') noblox.getGroup = require('./groups/getGroup.js') +noblox.getGroupPayoutEligibility = require('./economy/getGroupPayoutEligibility.js') noblox.getGroupSocialLinks = require('./groups/getGroupSocialLinks.js') noblox.getGroups = require('./groups/getGroups.js') noblox.getJoinRequest = require('./groups/getJoinRequest.js') diff --git a/test/economy.test.js b/test/economy.test.js index 06dfefc75..ebe009197 100644 --- a/test/economy.test.js +++ b/test/economy.test.js @@ -1,4 +1,4 @@ -const { buy, getGroupFunds, getGroupRevenueSummary, getGroupTransactions, getResaleData, getResellers, getUserTransactions, setCookie } = require('../lib') +const { buy, getGroupFunds, getGroupPayoutEligibility, getGroupRevenueSummary, getGroupTransactions, getResaleData, getResellers, getUserTransactions, setCookie } = require('../lib') beforeAll(() => { return new Promise(resolve => { @@ -24,6 +24,12 @@ describe('Economy Methods', () => { }) }) + it('getGroupPayoutEligibility() returns a list of payout statuses for specified users', () => { + return getGroupPayoutEligibility({ group: 9997719, member: 55549140 }).then((res) => { + return expect(res).toMatchObject({ [expect.any(String)]: expect.any(String) }) + }) + }) + it('getGroupRevenueSummary() returns a revenue summary for a group', () => { return getGroupRevenueSummary(9997719).then((res) => { return expect(res).toMatchObject({ diff --git a/typings/index.d.ts b/typings/index.d.ts index b8f3b34fe..766bff836 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -868,6 +868,12 @@ declare module "noblox.js" { updated: Date; } + interface PayoutAllowedList { + usersGroupPayoutEligibility: { + [K: string]: string; + } + } + interface GroupDescriptionResult { newDescription: string } @@ -1774,6 +1780,11 @@ declare module "noblox.js" { * 🔓 Gets the amount of Robux in a group. */ function getGroupFunds(group: number): Promise; + + /** + * 🔐 Gets the payout eligibility status of a group member. + */ + function getGroupPayoutEligibility(groupId: number, userIds: number[], jar?: CookieJar): Promise; /** * 🔐 Gets recent Robux revenue summary for a group; shows pending Robux. | Requires "Spend group funds" permissions. diff --git a/typings/jsDocs.ts b/typings/jsDocs.ts index d33c25587..1823ffb73 100644 --- a/typings/jsDocs.ts +++ b/typings/jsDocs.ts @@ -1202,6 +1202,15 @@ type GroupShout = { updated: Date; } +/** + * @typedef +*/ +type PayoutAllowedList = { + usersGroupPayoutEligibility: { + [k: string]: string; + } +} + /** * @typedef */ From 89cb7262ce464408216b5861c7c8309e98b553e1 Mon Sep 17 00:00:00 2001 From: Regalijan <72576136+Regalijan@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:41:39 -0400 Subject: [PATCH 14/28] fix: Final touches of error migration (#873) * Don't import buy.js * Apparently autocomplete didn't catch this one * Oh and migrate this too --- lib/avatar/currentlyWearing.js | 2 +- lib/index.js | 1 - lib/util/getPageResults.js | 9 +++------ 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/avatar/currentlyWearing.js b/lib/avatar/currentlyWearing.js index a6110fd4b..d8a8ef0d4 100644 --- a/lib/avatar/currentlyWearing.js +++ b/lib/avatar/currentlyWearing.js @@ -1,5 +1,5 @@ const http = require('../util/http.js').func -const RobloxAPIError = require('../util') +const RobloxAPIError = require('../util/apiError.js') exports.required = ['userId'] diff --git a/lib/index.js b/lib/index.js index 97c5389ca..352ee854e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -65,7 +65,6 @@ noblox.canManage = require('./develop/canManage.js') noblox.configureItem = require('./develop/configureItem.js') noblox.updateUniverse = require('./develop/updateUniverse.js') noblox.updateUniverseAccess = require('./develop/updateUniverseAccess.js') -noblox.buy = require('./economy/buy.js') noblox.getGroupFunds = require('./economy/getGroupFunds.js') noblox.getGroupRevenueSummary = require('./economy/getGroupRevenueSummary.js') noblox.getGroupTransactions = require('./economy/getGroupTransactions.js') diff --git a/lib/util/getPageResults.js b/lib/util/getPageResults.js index 11c78b06a..bc56094f9 100644 --- a/lib/util/getPageResults.js +++ b/lib/util/getPageResults.js @@ -1,5 +1,6 @@ // Includes const http = require('./http.js').func +const RobloxAPIError = require('./apiError.js') // Args exports.required = ['url', 'query', 'limit'] @@ -46,12 +47,8 @@ function getPageResults (jar, url, query, sortOrder, limit, pageCursor, results) const data = body.data - if (body.errors && body.errors.length > 0) { - const errors = body.errors.map((e) => { - return e.message - }) - - return reject(new Error(`${res.statusCode} ${errors.join(', ')}`)) + if (res.statusCode !== 200) { + return reject(new RobloxAPIError(res)) } results = results ? results.concat(data) : data From e7e83dcac01bde078ea47809615a2d4c311d9075 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 20:06:42 +0000 Subject: [PATCH 15/28] Bump handlebars from 4.7.8 to 4.7.9 (#886) Bumps [handlebars](https://github.com/handlebars-lang/handlebars.js) from 4.7.8 to 4.7.9. - [Release notes](https://github.com/handlebars-lang/handlebars.js/releases) - [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/v4.7.9/release-notes.md) - [Commits](https://github.com/handlebars-lang/handlebars.js/compare/v4.7.8...v4.7.9) --- updated-dependencies: - dependency-name: handlebars dependency-version: 4.7.9 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 383 +++++++++++++++++++----------------------------------- 1 file changed, 134 insertions(+), 249 deletions(-) diff --git a/yarn.lock b/yarn.lock index 55ddcb0df..5e8b61799 100644 --- a/yarn.lock +++ b/yarn.lock @@ -141,7 +141,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.1.tgz" integrity sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A== -"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.7.5", "@babel/core@^7.8.0": +"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.7.5": version "7.27.1" resolved "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz" integrity sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ== @@ -700,14 +700,6 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.25" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" @@ -716,6 +708,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@jsdoc/salty@^0.2.1": version "0.2.9" resolved "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz" @@ -742,7 +742,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -762,7 +762,7 @@ dependencies: "@octokit/types" "^6.0.3" -"@octokit/core@^3.5.0", "@octokit/core@^3.5.1", "@octokit/core@>=2", "@octokit/core@>=3": +"@octokit/core@^3.5.1": version "3.6.0" resolved "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz" integrity sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q== @@ -950,6 +950,11 @@ resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== +"@types/babel-types@*", "@types/babel-types@^7.0.0": + version "7.0.16" + resolved "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.16.tgz" + integrity sha512-5QXs9GBFTNTmilLlWBhnsprqpjfrotyrnzUdwDrywEL/DA4LuCWQT300BTOXA3Y9ngT9F2uvmCoIxI6z8DlJEA== + "@types/babel__core@^7.1.14": version "7.20.5" resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz" @@ -983,11 +988,6 @@ dependencies: "@babel/types" "^7.20.7" -"@types/babel-types@*", "@types/babel-types@^7.0.0": - version "7.0.16" - resolved "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.16.tgz" - integrity sha512-5QXs9GBFTNTmilLlWBhnsprqpjfrotyrnzUdwDrywEL/DA4LuCWQT300BTOXA3Y9ngT9F2uvmCoIxI6z8DlJEA== - "@types/babylon@^6.16.2": version "6.16.9" resolved "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.9.tgz" @@ -1041,7 +1041,7 @@ resolved "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz" integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== -"@types/markdown-it@*", "@types/markdown-it@^14.1.1": +"@types/markdown-it@^14.1.1": version "14.1.2" resolved "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz" integrity sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog== @@ -1098,6 +1098,14 @@ resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== +JSONStream@^1.0.4, JSONStream@^1.3.1: + version "1.3.5" + resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" @@ -1134,21 +1142,16 @@ acorn@^3.1.0: resolved "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz" integrity sha512-OLUyIIZ7mF5oaAUT1w0TFqQS81q3saT46x8t7ukpPjMNk+nbs4ZHhs7ToV8EWnLYLepjETXd4XaCE4uxkMeqUw== -acorn@^4.0.4: +acorn@^4.0.4, acorn@~4.0.2: version "4.0.13" resolved "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz" integrity sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug== -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: version "8.14.1" resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz" integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== -acorn@~4.0.2: - version "4.0.13" - resolved "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz" - integrity sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug== - add-stream@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz" @@ -1204,14 +1207,7 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0: - version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -1253,12 +1249,7 @@ array-back@^3.0.1, array-back@^3.1.0: resolved "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz" integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== -array-back@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz" - integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== - -array-back@^4.0.2: +array-back@^4.0.1, array-back@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz" integrity sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg== @@ -1381,12 +1372,12 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" -assert-plus@^1.0.0, assert-plus@1.0.0: +assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== -ast-types@^0.12.2: +ast-types@0.12.4, ast-types@^0.12.2: version "0.12.4" resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.12.4.tgz" integrity sha512-ky/YVYCbtVAS8TdMIaTiPFHwEpRB5z1hctepJplTr3UW5q8TDrpIMCILyk8pmLxGtn2KCtC/lSn7zOsaI7nzDw== @@ -1398,11 +1389,6 @@ ast-types@^0.14.2: dependencies: tslib "^2.0.1" -ast-types@0.12.4: - version "0.12.4" - resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.12.4.tgz" - integrity sha512-ky/YVYCbtVAS8TdMIaTiPFHwEpRB5z1hctepJplTr3UW5q8TDrpIMCILyk8pmLxGtn2KCtC/lSn7zOsaI7nzDw== - async-function@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz" @@ -1615,7 +1601,7 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0, "browserslist@>= 4.21.0": +browserslist@^4.24.0: version "4.24.5" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz" integrity sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw== @@ -1742,16 +1728,7 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chalk@^2.3.2: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^2.4.1: +chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -1760,24 +1737,7 @@ chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0: - version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1899,16 +1859,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" @@ -2023,8 +1983,8 @@ conventional-commits-parser@^3.1.0, conventional-commits-parser@^3.2.0: resolved "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz" integrity sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q== dependencies: - is-text-path "^1.0.1" JSONStream "^1.0.4" + is-text-path "^1.0.1" lodash "^4.17.15" meow "^8.0.0" split2 "^3.0.0" @@ -2040,12 +2000,12 @@ core-js@^2.4.0: resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -core-util-is@~1.0.0, core-util-is@1.0.2: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== -cosmiconfig@>=6, cosmiconfig@7.0.0: +cosmiconfig@7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz" integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== @@ -2148,6 +2108,13 @@ de-indent@^1.0.2: resolved "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz" integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: + version "4.4.0" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + debug@^3.2.7: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" @@ -2155,13 +2122,6 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@4: - version "4.4.0" - resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== - dependencies: - ms "^2.1.3" - decamelize-keys@^1.1.0: version "1.1.1" resolved "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz" @@ -2304,16 +2264,16 @@ domutils@^3.0.1, domutils@^3.1.0: domelementtype "^2.3.0" domhandler "^5.0.3" -dotenv@^8.0.0: - version "8.6.0" - resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz" - integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== - dotenv@16.4.5: version "16.4.5" resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== +dotenv@^8.0.0: + version "8.6.0" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz" + integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== + dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz" @@ -2371,17 +2331,7 @@ enquirer@^2.3.4: ansi-colors "^4.1.1" strip-ansi "^6.0.1" -entities@^4.2.0: - version "4.5.0" - resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -entities@^4.4.0: - version "4.5.0" - resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" - integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== - -entities@^4.5.0: +entities@^4.2.0, entities@^4.4.0, entities@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -2588,7 +2538,7 @@ eslint-plugin-es@^4.1.0: eslint-utils "^2.0.0" regexpp "^3.0.0" -eslint-plugin-import@^2.25.2, eslint-plugin-import@^2.27.5: +eslint-plugin-import@^2.27.5: version "2.31.0" resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz" integrity sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A== @@ -2613,7 +2563,7 @@ eslint-plugin-import@^2.25.2, eslint-plugin-import@^2.27.5: string.prototype.trimend "^1.0.8" tsconfig-paths "^3.15.0" -"eslint-plugin-n@^15.0.0 || ^16.0.0 ", eslint-plugin-n@^15.7.0: +eslint-plugin-n@^15.7.0: version "15.7.0" resolved "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz" integrity sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q== @@ -2627,12 +2577,12 @@ eslint-plugin-import@^2.25.2, eslint-plugin-import@^2.27.5: resolve "^1.22.1" semver "^7.3.8" -eslint-plugin-promise@^6.0.0, eslint-plugin-promise@^6.1.1: +eslint-plugin-promise@^6.1.1: version "6.6.0" resolved "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.6.0.tgz" integrity sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ== -eslint-plugin-react@^7.28.0, eslint-plugin-react@^7.36.1: +eslint-plugin-react@^7.36.1: version "7.37.5" resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz" integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== @@ -2693,7 +2643,7 @@ eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -"eslint@^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9", "eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^7.0.0 || ^8.0.0 || ^9.0.0", eslint@^8.0.1, eslint@^8.41.0, eslint@^8.8.0, eslint@>=4.19.1, eslint@>=5, eslint@>=7.0.0: +eslint@^8.41.0: version "8.57.1" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz" integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== @@ -2830,7 +2780,7 @@ extend@~3.0.2: resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -extsprintf@^1.2.0, extsprintf@1.3.0: +extsprintf@1.3.0, extsprintf@^1.2.0: version "1.3.0" resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== @@ -2985,7 +2935,7 @@ forever-agent@~0.6.1: resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== -fp-ts@^2.5.0, fp-ts@^2.5.3: +fp-ts@^2.5.3: version "2.16.10" resolved "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.10.tgz" integrity sha512-vuROzbNVfCmUkZSUbnWSltR1sbheyQbTzug7LB/46fEa1c0EucLeBaCEUE0gF3ZGUGBt9lVUiziGOhhj6K1ORA== @@ -3000,6 +2950,11 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" @@ -3221,9 +3176,9 @@ graphemer@^1.4.0: integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== handlebars@^4.7.7: - version "4.7.8" - resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz" - integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== + version "4.7.9" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.9.tgz#6f139082ab58dc4e5a0e51efe7db5ae890d56a0f" + integrity sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ== dependencies: minimist "^1.2.5" neo-async "^2.6.2" @@ -3344,7 +3299,7 @@ human-signals@^2.1.0: resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -iconv-lite@^0.6.3, iconv-lite@0.6.3: +iconv-lite@0.6.3, iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -3356,12 +3311,7 @@ ignore@^3.3.5: resolved "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz" integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== -ignore@^5.1.1: - version "5.3.2" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - -ignore@^5.2.0: +ignore@^5.1.1, ignore@^5.2.0: version "5.3.2" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== @@ -3414,7 +3364,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.3, inherits@~2.0.3, inherits@2: +inherits@2, inherits@^2.0.3, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3987,7 +3937,7 @@ jest-resolve-dependencies@^29.7.0: jest-regex-util "^29.6.3" jest-snapshot "^29.7.0" -jest-resolve@*, jest-resolve@^29.7.0: +jest-resolve@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz" integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA== @@ -4131,7 +4081,7 @@ jest-worker@^29.7.0: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.7.0, jest@>=27.2.5: +jest@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz" integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw== @@ -4256,14 +4206,6 @@ jsonparse@^1.2.0: resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -JSONStream@^1.0.4, JSONStream@^1.3.1: - version "1.3.5" - resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - jsprim@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz" @@ -4452,7 +4394,7 @@ longest@^1.0.1: resolved "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz" integrity sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -4515,7 +4457,7 @@ markdown-it-anchor@^8.6.7: resolved "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz" integrity sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA== -markdown-it@*, markdown-it@^14.1.0: +markdown-it@^14.1.0: version "14.1.0" resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz" integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg== @@ -4667,7 +4609,7 @@ node-dir@^0.1.10: dependencies: minimatch "^3.0.2" -node-fetch@^2.6.7, node-fetch@2.6.7: +node-fetch@2.6.7, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -4684,17 +4626,7 @@ node-releases@^2.0.19: resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz" integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== -normalize-package-data@^2.3.2: - version "2.5.0" - resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-package-data@^2.5.0: +normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -4856,14 +4788,7 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0: - version "2.3.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^2.2.0: +p-limit@^2.0.0, p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== @@ -5145,7 +5070,7 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.5.9, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -5348,15 +5273,6 @@ react-docgen@^5.4.0: node-dir "^0.1.10" strip-indent "^3.0.0" -"react-dom@^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0", react-dom@^17.0.2, "react-dom@>= 16.3": - version "17.0.2" - resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz" - integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler "^0.20.2" - react-frame-component@^5.2.1: version "5.2.7" resolved "https://registry.npmjs.org/react-frame-component/-/react-frame-component-5.2.7.tgz" @@ -5372,14 +5288,6 @@ react-is@^18.0.0: resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -"react@^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0", react@^17.0.2, "react@>= 16.3", react@17.0.2: - version "17.0.2" - resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz" - integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz" @@ -5416,7 +5324,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@^3.0.0, readable-stream@3: +readable-stream@3, readable-stream@^3.0.0: version "3.6.2" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -5651,30 +5559,17 @@ safe-regex-test@^1.1.0: es-errors "^1.3.0" is-regex "^1.2.1" -safer-buffer@^2.0.2, safer-buffer@^2.1.0, "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -scheduler@^0.20.2: - version "0.20.2" - resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz" - integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - -semver@^6.0.0: - version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^6.3.0: - version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +"semver@2 || 3 || 4 || 5": + version "5.7.2" + resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.3.1: +semver@^6.0.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -5684,11 +5579,6 @@ semver@^7.0.0, semver@^7.3.4, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: resolved "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz" integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== -"semver@2 || 3 || 4 || 5": - version "5.7.2" - resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - set-cookie-parser@^2.4.8: version "2.7.1" resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz" @@ -5806,14 +5696,6 @@ slash@^3.0.0: resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -source-map-support@^0.5.17: - version "0.5.21" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - source-map-support@0.5.13: version "0.5.13" resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" @@ -5822,6 +5704,14 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" +source-map-support@^0.5.17: + version "0.5.21" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" @@ -5858,13 +5748,6 @@ spdx-license-ids@^3.0.0: resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz" integrity sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg== -split@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/split/-/split-1.0.1.tgz" - integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== - dependencies: - through "2" - split2@^3.0.0: version "3.2.2" resolved "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz" @@ -5872,6 +5755,13 @@ split2@^3.0.0: dependencies: readable-stream "^3.0.0" +split@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/split/-/split-1.0.1.tgz" + integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== + dependencies: + through "2" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" @@ -5931,20 +5821,6 @@ stream-length@^1.0.2: dependencies: bluebird "^2.6.2" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -6021,6 +5897,20 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -6136,11 +6026,6 @@ text-table@^0.2.0: resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -"through@>=2.2.7 <3", through@2: - version "2.3.8" - resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - through2@^2.0.0: version "2.0.5" resolved "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz" @@ -6156,6 +6041,11 @@ through2@^4.0.0: dependencies: readable-stream "3" +through@2, "through@>=2.2.7 <3": + version "2.3.8" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + tinycolor2@^1.4.1: version "1.6.0" resolved "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz" @@ -6208,7 +6098,7 @@ ts-map@^1.0.3: resolved "https://registry.npmjs.org/ts-map/-/ts-map-1.0.3.tgz" integrity sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w== -ts-node@^10.9.1, ts-node@>=9.0.0: +ts-node@^10.9.1: version "10.9.2" resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== @@ -6249,7 +6139,12 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2, tslib@^2.0.1, tslib@2.1.0: +tslib@1.10.0: + version "1.10.0" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + +tslib@2.1.0, tslib@^2, tslib@^2.0.1: version "2.1.0" resolved "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== @@ -6259,11 +6154,6 @@ tslib@^2.5.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -tslib@1.10.0: - version "1.10.0" - resolved "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz" - integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" @@ -6371,11 +6261,6 @@ typescript@^4.5.4: resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== -typescript@>=2.7: - version "5.8.3" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== - typical@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz" @@ -6673,16 +6558,16 @@ word-wrap@^1.2.5: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -wordwrap@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" - integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== - wordwrap@0.0.2: version "0.0.2" resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz" integrity sha512-xSBsCeh+g+dinoBv3GAOWM4LcVVO68wLXRanibtBSdUvkGWQRGeE9P7IwU9EmDDi4jA6L44lz15CGMwdw9N5+Q== +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + wordwrapjs@^4.0.0: version "4.0.1" resolved "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz" From 81d3e848eeac880858b96ba2d53a93a5cdbe0ab1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 20:08:32 +0000 Subject: [PATCH 16/28] Bump undici from 6.21.2 to 6.24.1 (#883) Bumps [undici](https://github.com/nodejs/undici) from 6.21.2 to 6.24.1. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v6.21.2...v6.24.1) --- updated-dependencies: - dependency-name: undici dependency-version: 6.24.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5e8b61799..c9a045041 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6317,9 +6317,9 @@ undici-types@~6.21.0: integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== undici@^6.19.5: - version "6.21.2" - resolved "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz" - integrity sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g== + version "6.24.1" + resolved "https://registry.yarnpkg.com/undici/-/undici-6.24.1.tgz#9df1425cede20b836d95634347946f79578b7e71" + integrity sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA== universal-user-agent@^6.0.0: version "6.0.1" From e0c53f4ec3f39dddf2d0efba160271327900561f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 20:08:39 +0000 Subject: [PATCH 17/28] Bump minimatch from 3.1.2 to 3.1.5 (#881) Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.5. - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5) --- updated-dependencies: - dependency-name: minimatch dependency-version: 3.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c9a045041..4dbf72c6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4547,9 +4547,9 @@ min-indent@^1.0.0: integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + version "3.1.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== dependencies: brace-expansion "^1.1.7" From 42965bba51e981e5b66ea3a76faabfdd43286484 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 20:08:49 +0000 Subject: [PATCH 18/28] Bump lodash from 4.17.21 to 4.17.23 (#879) Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23) --- updated-dependencies: - dependency-name: lodash dependency-version: 4.17.23 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4dbf72c6d..2ae4d8769 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4377,9 +4377,9 @@ lodash.merge@^4.6.2: integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4: - version "4.17.21" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + version "4.17.23" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" + integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== log-symbols@^4.0.0: version "4.1.0" From 98217904f1e91fba1013073fb6afe0cd63758285 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 20:09:06 +0000 Subject: [PATCH 19/28] Bump picomatch from 2.3.1 to 2.3.2 (#885) Bumps [picomatch](https://github.com/micromatch/picomatch) from 2.3.1 to 2.3.2. - [Release notes](https://github.com/micromatch/picomatch/releases) - [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2) --- updated-dependencies: - dependency-name: picomatch dependency-version: 2.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2ae4d8769..9254ed52d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4947,9 +4947,9 @@ picocolors@^1.1.1: integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + version "2.3.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601" + integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== pify@^2.3.0: version "2.3.0" From 3d8bd785619b966f4a53c4d5fd9e017f7390fd3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 20:09:17 +0000 Subject: [PATCH 20/28] Bump flatted from 3.3.3 to 3.4.2 (#884) Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.2. - [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.2) --- updated-dependencies: - dependency-name: flatted dependency-version: 3.4.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9254ed52d..8a4d2a91c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2911,9 +2911,9 @@ flat-cache@^3.0.4: rimraf "^3.0.2" flatted@^3.2.9: - version "3.3.3" - resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz" - integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + version "3.4.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== for-each@^0.3.3, for-each@^0.3.5: version "0.3.5" From c3e9c90f0d990a152097263f598103dc794c5e9e Mon Sep 17 00:00:00 2001 From: Ethan <39052179+realCartar@users.noreply.github.com> Date: Sat, 28 Mar 2026 06:17:44 +1000 Subject: [PATCH 21/28] feat: Implement handleJoinRequests function for batch user join requests (#880) * feat: Implement handleJoinRequests function for batch user join request management * fix: Add missing newline at end of handleJoinRequests.js --------- Co-authored-by: realCartar --- lib/groups/handleJoinRequests.js | 61 ++++++++++++++++++++++++++++++++ lib/index.js | 1 + test/groups.test.js | 2 ++ typings/index.d.ts | 5 +++ 4 files changed, 69 insertions(+) create mode 100644 lib/groups/handleJoinRequests.js diff --git a/lib/groups/handleJoinRequests.js b/lib/groups/handleJoinRequests.js new file mode 100644 index 000000000..bd2e68c20 --- /dev/null +++ b/lib/groups/handleJoinRequests.js @@ -0,0 +1,61 @@ +const RobloxAPIError = require('../util/apiError.js') + +// Includes +const http = require('../util/http.js').func +const getGeneralToken = require('../util/getGeneralToken.js').func + +// Args +exports.required = ['group', 'userIds', 'accept'] +exports.optional = ['jar'] + +// Docs +/** + * 🔐 Batch accept/decline multiple users' join requests. + * @category Group + * @alias handleJoinRequest + * @param {number} group - The id of the group. + * @param {Array} userIds - The ids of the users. + * @param {boolean} accept - If the users should be accepted into the group. + * @returns {Promise} + * @example const noblox = require("noblox.js") + * // Login using your cookie + * noblox.handleJoinRequests(1, [1], true) +**/ + +function handleJoinRequests (group, userIds, accept, jar, xcsrf) { + return new Promise((resolve, reject) => { + const httpOpt = { + url: `https://groups.roblox.com/v1/groups/${group}/join-requests`, + options: { + method: accept ? 'POST' : 'DELETE', + resolveWithFullResponse: true, + jar, + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-TOKEN': xcsrf + }, + body: JSON.stringify({ + UserIds: userIds + }) + } + } + + return http(httpOpt) + .then(function (res) { + if (res.statusCode !== 200) { + reject(new RobloxAPIError(res)) + } else { + resolve() + } + }).catch(error => reject(error)) + }) +} + +// Define +exports.func = function (args) { + const jar = args.jar + return getGeneralToken({ jar }) + .then(function (xcsrf) { + return handleJoinRequests(args.group, args.userIds, args.accept, args.jar, xcsrf) + }) +} diff --git a/lib/index.js b/lib/index.js index 352ee854e..cd70edd0f 100644 --- a/lib/index.js +++ b/lib/index.js @@ -122,6 +122,7 @@ noblox.getShout = require('./groups/getShout.js') noblox.getWall = require('./groups/getWall.js') noblox.groupPayout = require('./groups/groupPayout.js') noblox.handleJoinRequest = require('./groups/handleJoinRequest.js') +noblox.handleJoinRequests = require('./groups/handleJoinRequests.js') noblox.leaveGroup = require('./groups/leaveGroup.js') noblox.onAuditLog = require('./groups/onAuditLog.js') noblox.onJoinRequest = require('./groups/onJoinRequest.js') diff --git a/test/groups.test.js b/test/groups.test.js index 9f45b405e..3699f5827 100644 --- a/test/groups.test.js +++ b/test/groups.test.js @@ -258,6 +258,8 @@ describe('Groups Methods', () => { // PASS: handleJoinRequest, would require being able to request to join a group + // PASS: handleJoinRequests, would require being able to request to join a group + // PASS: leaveGroup, would require being able to request to join a group it('searchGroups() returns groups that match the query', () => { diff --git a/typings/index.d.ts b/typings/index.d.ts index 766bff836..f86902be7 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -2070,6 +2070,11 @@ declare module "noblox.js" { */ function handleJoinRequest(group: number, userId: number, accept: boolean, jar?: CookieJar): Promise; + /** + * 🔐 Batch accept/decline multiple users' join requests. + */ + function handleJoinRequests(group: number, userIds: number[], accept: boolean, jar?: CookieJar): Promise; + /** * 🔐 Leaves the group with id `group`. Unless `useCache` is enabled the function will not cache because errors will occur if joining or leaving the same group multiple times, you can enable it if you are only joining or leaving a group once or many differenct groups once. */ From 9350d0c0c7e335afa43e3b1dc6c0ff69b934ea49 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 27 Mar 2026 20:27:05 +0000 Subject: [PATCH 22/28] Update deprecated GitHub Actions to latest versions (#889) Bump actions/checkout from v2 to v4, actions/setup-node from v1 to v4, and actions/cache from v1 to v4 across doc-publish and npmpublish workflows. Co-authored-by: Claude Sonnet 4.6 --- .github/workflows/doc-publish.yml | 4 ++-- .github/workflows/npmpublish.yml | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/doc-publish.yml b/.github/workflows/doc-publish.yml index 29a5a73ec..fe5c6e5df 100644 --- a/.github/workflows/doc-publish.yml +++ b/.github/workflows/doc-publish.yml @@ -10,10 +10,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up node - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: 20 diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml index 133306eff..2c2c923ef 100644 --- a/.github/workflows/npmpublish.yml +++ b/.github/workflows/npmpublish.yml @@ -14,10 +14,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Set up node - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: 20 @@ -37,19 +37,19 @@ jobs: steps: - name: Checkout code with ADMIN_TOKEN if: github.event.pull_request.head.repo.full_name == github.repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: token: ${{ secrets.ADMIN_TOKEN }} - name: Checkout code if: github.event.pull_request.head.repo.full_name != github.repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Prepare repository run: git fetch --unshallow --tags - name: Set up node - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: 20 @@ -60,7 +60,7 @@ jobs: run: yarn install --frozen-lockfile - name: Cache node modules - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: node_modules key: yarn-deps-${{ hashFiles('yarn.lock') }} From 46dbb512cee2c85f87ae1f630c9db4701948a8a1 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 27 Mar 2026 20:44:34 +0000 Subject: [PATCH 23/28] Switch npm publish to on dispatch only (#890) * Switch npm publish to on dispatch only * Add new release workflow v6.3.0 * Add new release workflow v6.3.0 * Add permissions * Switch to trusted publishing --- .github/workflows/doc-publish.yml | 2 +- .github/workflows/npmpublish.yml | 12 +- .github/workflows/npmpublishonrelease.yml | 25 ++ .github/workflows/run-tests.yml | 2 +- package.json | 2 +- yarn.lock | 302 ++++++++++++---------- 6 files changed, 194 insertions(+), 151 deletions(-) create mode 100644 .github/workflows/npmpublishonrelease.yml diff --git a/.github/workflows/doc-publish.yml b/.github/workflows/doc-publish.yml index fe5c6e5df..9cd4d0af6 100644 --- a/.github/workflows/doc-publish.yml +++ b/.github/workflows/doc-publish.yml @@ -15,7 +15,7 @@ jobs: - name: Set up node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Install yarn run: npm install -g yarn diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml index 2c2c923ef..84d2f80b1 100644 --- a/.github/workflows/npmpublish.yml +++ b/.github/workflows/npmpublish.yml @@ -1,13 +1,7 @@ name: Test & Publish on: - pull_request: - branches: - - master - - push: - branches: - - master + workflow_dispatch jobs: test: @@ -19,7 +13,7 @@ jobs: - name: Set up node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Install yarn run: npm install -g yarn @@ -51,7 +45,7 @@ jobs: - name: Set up node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Install yarn run: npm install -g yarn diff --git a/.github/workflows/npmpublishonrelease.yml b/.github/workflows/npmpublishonrelease.yml new file mode 100644 index 000000000..abff67d20 --- /dev/null +++ b/.github/workflows/npmpublishonrelease.yml @@ -0,0 +1,25 @@ +# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created +# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages + +name: save-server + +on: + release: + types: [created] + +permissions: + contents: read + packages: read + id-token: write + +jobs: + publish-npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 24 + registry-url: https://registry.npmjs.org/ + - run: yarn install --frozen-lockfile + - run: yarn publish --non-interactive \ No newline at end of file diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index c9d839694..773b3f357 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -17,7 +17,7 @@ jobs: - name: Set up node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Enable Corepack run: corepack enable diff --git a/package.json b/package.json index fd3bf8e8c..ba9b5877b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "noblox.js", - "version": "6.2.0", + "version": "6.3.0", "description": "A Node.js API wrapper for Roblox.", "main": "lib/index.js", "types": "typings/index.d.ts", diff --git a/yarn.lock b/yarn.lock index 8a4d2a91c..bb385c841 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,17 +10,17 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@auto-it/bot-list@11.3.0": - version "11.3.0" - resolved "https://registry.npmjs.org/@auto-it/bot-list/-/bot-list-11.3.0.tgz" - integrity sha512-+izoqAyOSiDVt3WcjVkSvLBV9c82VXLSf3oSWWcCeoxW/YDQ2AoInQ3M3EEyuBP+Yw9KQwGTTYHqpR7ZFkZpDQ== +"@auto-it/bot-list@11.3.6": + version "11.3.6" + resolved "https://registry.npmjs.org/@auto-it/bot-list/-/bot-list-11.3.6.tgz" + integrity sha512-BtFOhGv+V47TAYr77uiFz0l807g655Wcui/1KOJpqUYvF07MjG3+D8Ob4sMl8ewDQfgtAMTJKJF3S7AUdKPQng== "@auto-it/conventional-commits@^11.2.0": - version "11.3.0" - resolved "https://registry.npmjs.org/@auto-it/conventional-commits/-/conventional-commits-11.3.0.tgz" - integrity sha512-+1j2Yz8SoyxV+ioeivT8GIYLSlegfvoV36OfI4cphwGB35RyuDAG58340ymhW0v7I3QWHCKSLGTagyJTwcpqoA== + version "11.3.6" + resolved "https://registry.npmjs.org/@auto-it/conventional-commits/-/conventional-commits-11.3.6.tgz" + integrity sha512-da3krzspv7QdZRHrpaX9bjCq6C+0U/TE38mRHeKA+iEqwlDQvZTu+UKXl3Eaayidwmm+efcsi2s1UszG9D+0RA== dependencies: - "@auto-it/core" "11.3.0" + "@auto-it/core" "11.3.6" array.prototype.flatmap "^1.2.2" conventional-changelog-core "^4.2.0" conventional-changelog-preset-loader "^2.3.4" @@ -30,12 +30,12 @@ io-ts "^2.1.2" tslib "2.1.0" -"@auto-it/core@11.3.0": - version "11.3.0" - resolved "https://registry.npmjs.org/@auto-it/core/-/core-11.3.0.tgz" - integrity sha512-3i7ooAhQJulVDG3gmdOioTXLhpFoS75Z/OsLV8ZkrEaEH/sfxlslqFx20VjWva7gMLl2iO8IjbRnlLhkXy5geg== +"@auto-it/core@11.3.6": + version "11.3.6" + resolved "https://registry.npmjs.org/@auto-it/core/-/core-11.3.6.tgz" + integrity sha512-OE7feN8pbpcSCiWaOHYah+oXsinhJ6OfDrsLyPywNSfEPjal4q18ss2iQX1zTmrFIEjDSmptdjqlmwx+dm/8wQ== dependencies: - "@auto-it/bot-list" "11.3.0" + "@auto-it/bot-list" "11.3.6" "@endemolshinegroup/cosmiconfig-typescript-loader" "^3.0.2" "@octokit/core" "^3.5.1" "@octokit/plugin-enterprise-compatibility" "1.3.0" @@ -76,18 +76,18 @@ typescript-memoize "^1.0.0-alpha.3" url-join "^4.0.0" -"@auto-it/npm@11.3.0": - version "11.3.0" - resolved "https://registry.npmjs.org/@auto-it/npm/-/npm-11.3.0.tgz" - integrity sha512-II7u1trzi2hSd1Vww635DmvHqHlgtVPqr4VPJlq1M7zqPwi9+FcaMW5J/DSqlwJgWRWviWqepIhasUQhj69p0A== +"@auto-it/npm@11.3.6": + version "11.3.6" + resolved "https://registry.npmjs.org/@auto-it/npm/-/npm-11.3.6.tgz" + integrity sha512-yKYvlBcIRIOlFLJFpAq2ipdGihqyxAf5KZ9YeDxF/xN54XUV5A5kQqohSpAckxpWKBwxPIKODdLyY5Y0Tm0qfQ== dependencies: - "@auto-it/core" "11.3.0" - "@auto-it/package-json-utils" "11.3.0" + "@auto-it/core" "11.3.6" + "@auto-it/package-json-utils" "11.3.6" await-to-js "^3.0.0" endent "^2.1.0" env-ci "^5.0.1" fp-ts "^2.5.3" - get-monorepo-packages "^1.1.0" + get-monorepo-packages "^1.3.0" io-ts "^2.1.2" registry-url "^5.1.0" semver "^7.0.0" @@ -96,32 +96,32 @@ url-join "^4.0.0" user-home "^2.0.0" -"@auto-it/package-json-utils@11.3.0": - version "11.3.0" - resolved "https://registry.npmjs.org/@auto-it/package-json-utils/-/package-json-utils-11.3.0.tgz" - integrity sha512-wZQLfxYCzqNTlqgYhgm1mZaasA35tuOhGl0npWMZlq0HJ4rbNvUYnjb8bXlyfm/dxTYtYp70IhoV5kv1NmPX8Q== +"@auto-it/package-json-utils@11.3.6": + version "11.3.6" + resolved "https://registry.npmjs.org/@auto-it/package-json-utils/-/package-json-utils-11.3.6.tgz" + integrity sha512-j1GT9alk4kJoyyuAxqqHD7okHQjJuJXeAMTMRt4ZpjDjElPuPbxvzjM+QZNuxaMTyjxRoT2YbFyx0uIt7pqXlQ== dependencies: parse-author "^2.0.0" parse-github-url "1.0.2" -"@auto-it/released@11.3.0": - version "11.3.0" - resolved "https://registry.npmjs.org/@auto-it/released/-/released-11.3.0.tgz" - integrity sha512-8Aw8WGuTi3giKU9+KEutebLhhX+4eNVa7SmVLaRIFECUxI/+PS20yMbWsYjsyk5qju1MdpEQGPOW/4U5OZ6Bdw== +"@auto-it/released@11.3.6": + version "11.3.6" + resolved "https://registry.npmjs.org/@auto-it/released/-/released-11.3.6.tgz" + integrity sha512-mhe1QjEylUjONE24LUij3A0BCXvadABhbyPS+Jxq6hFS5Rh/gkOmxSuCa1HpO6DSsVdM0gOSF8ZYhUJ/9vHT8A== dependencies: - "@auto-it/bot-list" "11.3.0" - "@auto-it/core" "11.3.0" + "@auto-it/bot-list" "11.3.6" + "@auto-it/core" "11.3.6" deepmerge "^4.0.0" fp-ts "^2.5.3" io-ts "^2.1.2" tslib "2.1.0" -"@auto-it/version-file@11.3.0": - version "11.3.0" - resolved "https://registry.npmjs.org/@auto-it/version-file/-/version-file-11.3.0.tgz" - integrity sha512-+ax5/oXKLc5moXrSJuGm3eC10YFapWFwS5MEVwdspPM2YJn1ImuhagXOq5FJ1XK8aeHILZI+2iA+YB5wI1bcLA== +"@auto-it/version-file@11.3.6": + version "11.3.6" + resolved "https://registry.npmjs.org/@auto-it/version-file/-/version-file-11.3.6.tgz" + integrity sha512-dtf2T02ryIdX1GWQ9jeLW7EQj1IOBNFQX4BGS2uAddjszsd65hYNoRFMzQ+T/tr7j5Fn9RDaxXLamwYfaSPn9Q== dependencies: - "@auto-it/core" "11.3.0" + "@auto-it/core" "11.3.6" fp-ts "^2.5.3" io-ts "^2.1.2" semver "^7.0.0" @@ -880,32 +880,6 @@ dependencies: "@octokit/openapi-types" "^12.11.0" -"@postman/form-data@~3.1.1": - version "3.1.1" - resolved "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz" - integrity sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -"@postman/tough-cookie@~4.1.3-postman.1": - version "4.1.3-postman.1" - resolved "https://registry.npmjs.org/@postman/tough-cookie/-/tough-cookie-4.1.3-postman.1.tgz" - integrity sha512-txpgUqZOnWYnUHZpHjkfb0IwVH4qJmyq77pPnJLlfhMtdCLMFTEeQHlzQiK906aaNCe4NEB5fGJHo9uzGbFMeA== - dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.2.0" - url-parse "^1.5.3" - -"@postman/tunnel-agent@^0.6.4": - version "0.6.4" - resolved "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.4.tgz" - integrity sha512-CJJlq8V7rNKhAw4sBfjixKpJW00SHqebqNUQKxMoepgeWZIbdPcD+rguRcivGhS4N12PymDcKgUgSD4rVC+RjQ== - dependencies: - safe-buffer "^5.0.1" - "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz" @@ -1164,10 +1138,10 @@ agent-base@6: dependencies: debug "4" -ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== +ajv@^6.12.3, ajv@^6.12.4: + version "6.14.0" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz" + integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== dependencies: fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" @@ -1405,14 +1379,14 @@ author-regex@^1.0.0: integrity sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g== auto@^11.2.0: - version "11.3.0" - resolved "https://registry.npmjs.org/auto/-/auto-11.3.0.tgz" - integrity sha512-7FWjxrfsVKaToAcjxsijdpL8prbffZk5ovPCTVDk6c0Yq3pNKd2AMm5fkPR5lDbnYNeoU7lbm+0wVtJSoTQhpw== - dependencies: - "@auto-it/core" "11.3.0" - "@auto-it/npm" "11.3.0" - "@auto-it/released" "11.3.0" - "@auto-it/version-file" "11.3.0" + version "11.3.6" + resolved "https://registry.npmjs.org/auto/-/auto-11.3.6.tgz" + integrity sha512-Db13X4WVNPzPtWKSoiOzhfW2g3zNUTDR1X3wkQPdIW21xtajwm5Taqg3BXWR2u7/45W0bU+6YykVgioNRwrwIw== + dependencies: + "@auto-it/core" "11.3.6" + "@auto-it/npm" "11.3.6" + "@auto-it/released" "11.3.6" + "@auto-it/version-file" "11.3.6" await-to-js "^3.0.0" chalk "^4.0.0" command-line-application "^0.10.1" @@ -1439,7 +1413,7 @@ aws-sign2@~0.7.0: resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== -aws4@^1.12.0: +aws4@^1.8.0: version "1.13.2" resolved "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz" integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== @@ -1582,9 +1556,9 @@ bottleneck@^2.15.3: integrity sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw== brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + version "1.1.13" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz" + integrity sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" @@ -1869,7 +1843,7 @@ color-name@~1.1.4: resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -2204,9 +2178,9 @@ diff-sequences@^29.6.3: integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== diff@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + version "4.0.4" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz" + integrity sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ== dir-glob@^2.0.0: version "2.2.2" @@ -2912,7 +2886,7 @@ flat-cache@^3.0.4: flatted@^3.2.9: version "3.4.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz" integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== for-each@^0.3.3, for-each@^0.3.5: @@ -2935,6 +2909,15 @@ forever-agent@~0.6.1: resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== +form-data@~2.3.2: + version "2.3.3" + resolved "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.6" + mime-types "^2.1.12" + fp-ts@^2.5.3: version "2.16.10" resolved "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.10.tgz" @@ -2952,7 +2935,7 @@ fs.realpath@^1.0.0: fsevents@^2.3.2: version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.2: @@ -3003,7 +2986,7 @@ get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@ hasown "^2.0.2" math-intrinsics "^1.1.0" -get-monorepo-packages@^1.1.0: +get-monorepo-packages@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/get-monorepo-packages/-/get-monorepo-packages-1.3.0.tgz" integrity sha512-A/s881nNcKhoM7RgkvYFTOtGO+dy4EWbyRaatncPEhhlJAaZRlpfHwuT68p5GJenEt81nnjJOwGg0WKLkR5ZdQ== @@ -3177,7 +3160,7 @@ graphemer@^1.4.0: handlebars@^4.7.7: version "4.7.9" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.9.tgz#6f139082ab58dc4e5a0e51efe7db5ae890d56a0f" + resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz" integrity sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ== dependencies: minimist "^1.2.5" @@ -3187,6 +3170,19 @@ handlebars@^4.7.7: optionalDependencies: uglify-js "^3.1.4" +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz" + integrity sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q== + +har-validator@~5.1.3: + version "5.1.5" + resolved "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz" @@ -3277,14 +3273,14 @@ htmlparser2@^9.1.0: domutils "^3.1.0" entities "^4.5.0" -http-signature@~1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz" - integrity sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg== +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz" + integrity sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ== dependencies: assert-plus "^1.0.0" - jsprim "^2.0.2" - sshpk "^1.18.0" + jsprim "^1.2.2" + sshpk "^1.7.0" https-proxy-agent@^5.0.0: version "5.0.1" @@ -4102,17 +4098,17 @@ js-stringify@^1.0.1: integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + version "3.14.2" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz" + integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg== dependencies: argparse "^1.0.7" esprima "^4.0.0" js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + version "4.1.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== dependencies: argparse "^2.0.1" @@ -4206,10 +4202,10 @@ jsonparse@^1.2.0: resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== -jsprim@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz" - integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" @@ -4378,7 +4374,7 @@ lodash.merge@^4.6.2: lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.4: version "4.17.23" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.23.tgz#f113b0378386103be4f6893388c73d0bde7f2c5a" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz" integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== log-symbols@^4.0.0: @@ -4458,9 +4454,9 @@ markdown-it-anchor@^8.6.7: integrity sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA== markdown-it@^14.1.0: - version "14.1.0" - resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz" - integrity sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg== + version "14.1.1" + resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz" + integrity sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA== dependencies: argparse "^2.0.1" entities "^4.4.0" @@ -4529,7 +4525,7 @@ mime-db@1.52.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.35: +mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -4548,7 +4544,7 @@ min-indent@^1.0.0: minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz" integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== dependencies: brace-expansion "^1.1.7" @@ -4941,6 +4937,11 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + picocolors@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" @@ -4948,7 +4949,7 @@ picocolors@^1.1.1: picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz" integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== pify@^2.3.0: @@ -5000,29 +5001,37 @@ possible-typed-array-names@^1.0.0: integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== postman-request@^2.88.1-postman.34: - version "2.88.1-postman.42" - resolved "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.42.tgz" - integrity sha512-lepCE8QU0izagxxA31O/MHj8IUguwLlpqeVK7A8vHK401FPvN/PTIzWHm29c/L3j3kTUE7dhZbq8vvbyQ7S2Bw== + version "2.88.1-postman.8-beta.1" + resolved "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.8-beta.1.tgz" + integrity sha512-deC5UZlM1VimFhQdPN1NcbQMvLEtpUCTHZHMXWNv6vyNW7H98O3MJGTlk2xTlzB9BOpU2MCCgXNOPeNP2SU6iA== dependencies: - "@postman/form-data" "~3.1.1" - "@postman/tough-cookie" "~4.1.3-postman.1" - "@postman/tunnel-agent" "^0.6.4" aws-sign2 "~0.7.0" - aws4 "^1.12.0" + aws4 "^1.8.0" caseless "~0.12.0" combined-stream "~1.0.6" extend "~3.0.2" forever-agent "~0.6.1" - http-signature "~1.4.0" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" - mime-types "^2.1.35" + mime-types "~2.1.19" oauth-sign "~0.9.0" - qs "~6.5.3" + performance-now "^2.1.0" + postman-url-encoder "1.0.1" + qs "~6.5.2" safe-buffer "^5.1.2" stream-length "^1.0.2" - uuid "^8.3.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +postman-url-encoder@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-1.0.1.tgz" + integrity sha512-ned2lpcMpEG+n3ce2LEoGqUJeZsKNRYkViqKfJXe7rUQhLxjrrcp/lQ8TLycvX74lQZm52gkNVVgczmcJBOJ8w== prelude-ls@^1.2.1: version "1.2.1" @@ -5084,7 +5093,7 @@ pseudomap@^1.0.2: resolved "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz" integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== -psl@^1.1.33: +psl@^1.1.28, psl@^1.1.33: version "1.15.0" resolved "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz" integrity sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w== @@ -5216,10 +5225,10 @@ q@^1.5.1: resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz" integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== -qs@~6.5.3: - version "6.5.3" - resolved "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz" - integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== +qs@~6.5.2: + version "6.5.5" + resolved "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz" + integrity sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ== querystringify@^2.1.1: version "2.2.0" @@ -5767,7 +5776,7 @@ sprintf-js@~1.0.2: resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== -sshpk@^1.18.0: +sshpk@^1.7.0: version "1.18.0" resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz" integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== @@ -5995,9 +6004,9 @@ table-layout@^1.0.2: wordwrapjs "^4.0.0" tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + version "2.3.2" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz" + integrity sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA== terminal-link@^2.1.1: version "2.1.1" @@ -6083,6 +6092,14 @@ tough-cookie@^4.0.0: universalify "^0.2.0" url-parse "^1.5.3" +tough-cookie@~2.5.0: + version "2.5.0" + resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz" + integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== + dependencies: + psl "^1.1.28" + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" @@ -6154,6 +6171,13 @@ tslib@^2.5.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" @@ -6307,9 +6331,9 @@ unbox-primitive@^1.1.0: which-boxed-primitive "^1.1.1" underscore@^1.13.2, underscore@~1.13.2: - version "1.13.7" - resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz" - integrity sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g== + version "1.13.8" + resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz" + integrity sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ== undici-types@~6.21.0: version "6.21.0" @@ -6318,7 +6342,7 @@ undici-types@~6.21.0: undici@^6.19.5: version "6.24.1" - resolved "https://registry.yarnpkg.com/undici/-/undici-6.24.1.tgz#9df1425cede20b836d95634347946f79578b7e71" + resolved "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz" integrity sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA== universal-user-agent@^6.0.0: @@ -6371,10 +6395,10 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== v8-compile-cache-lib@^3.0.1: version "3.0.1" @@ -6639,9 +6663,9 @@ yallist@^4.0.0: integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml@^1.10.0: - version "1.10.2" - resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + version "1.10.3" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz" + integrity sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA== yargs-parser@^20.2.2, yargs-parser@^20.2.3, yargs-parser@^20.2.9: version "20.2.9" From 81fc85a332162504454bc07402e64324ae31c67b Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 27 Mar 2026 20:51:31 +0000 Subject: [PATCH 24/28] v7.4.0 (#891) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba9b5877b..b589a48ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "noblox.js", - "version": "6.3.0", + "version": "7.4.0", "description": "A Node.js API wrapper for Roblox.", "main": "lib/index.js", "types": "typings/index.d.ts", From be759c8e2c959787bfa4440f427343d4c3551400 Mon Sep 17 00:00:00 2001 From: Josh Muir Date: Fri, 27 Mar 2026 20:57:44 +0000 Subject: [PATCH 25/28] Update action --- .github/workflows/npmpublish.yml | 5 +++++ .github/workflows/npmpublishonrelease.yml | 25 ----------------------- 2 files changed, 5 insertions(+), 25 deletions(-) delete mode 100644 .github/workflows/npmpublishonrelease.yml diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml index 3bbf24ba5..86d215ec8 100644 --- a/.github/workflows/npmpublish.yml +++ b/.github/workflows/npmpublish.yml @@ -11,6 +11,11 @@ on: - master - next +permissions: + contents: read + packages: read + id-token: write + jobs: test: runs-on: ubuntu-latest diff --git a/.github/workflows/npmpublishonrelease.yml b/.github/workflows/npmpublishonrelease.yml deleted file mode 100644 index abff67d20..000000000 --- a/.github/workflows/npmpublishonrelease.yml +++ /dev/null @@ -1,25 +0,0 @@ -# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created -# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages - -name: save-server - -on: - release: - types: [created] - -permissions: - contents: read - packages: read - id-token: write - -jobs: - publish-npm: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 24 - registry-url: https://registry.npmjs.org/ - - run: yarn install --frozen-lockfile - - run: yarn publish --non-interactive \ No newline at end of file From 457ec39bef8a170a125912574405488dd6a7f190 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 27 Mar 2026 21:28:28 +0000 Subject: [PATCH 26/28] Copy master over next (#895) * Next (#892) * First attempt at new release system * BREAKING CHANGE: Document new versioning * fix: add release branches * ci: Add new release workflow BREAKING CHANGE: Trigger new major release to cover previously made major changes * fix: Update user block/unblock endpoints (#869) * feat: UserNotification Cloud API (#851) * Create new function * Updated and added typings * Fixed linting * Fixed linting electric boogaloo linting better be happy now... * Linting final touches of grace * Linting obliteration of an unworthy comma * Added typings to jsDocs * Fix error handling * Add test case for sendUserNotification * Remove extra await * Migrate to new error class --------- Co-authored-by: Regalijan <72576136+Regalijan@users.noreply.github.com> * feat: Add group ban methods (#872) * Create banFromGroup method * Create unbanFromGroup * Change return type of banFromGroup * Create getGroupBans * Add GroupBan type * Add function types for new methods * We need to actually return the response body * Change limit type to number getPageResults will accept numbers higher than 100 and will paginate * getPageResults only returns the data property * Change function type to accept number for limit * Add test cases for banFromGroup, getGroupBans, and unbanFromGroup * fix: Update user agent per Roblox's request (#870) * fix: Update user agent per Roblox's request * fix: Change header check for request forwarding UA enforcement means there will always be headers now * Add lock_ua setting * lock_ua -> use_noblox_ua * Set ua in request defaults if enabled * Le lint * Add user-agent documentation * Correct documentation --------- Co-authored-by: Josh Muir * feat: Add getGroupPayoutEligibility (#834) * Add canpay function * Add PayoutAllowedList type to jsdocs * Fix formatting * canPay -> getGroupPayoutEligibility * Only log first error message from endpoint (as if Roblox ever returned more than one) * Fix type for PayoutAllowedList * Add function type for getGroupPayoutEligibility * Document jar param in getGroupPayoutEligibility * Update example * Move getGroupPayoutEligibility to economy * Update import for getGroupPayoutEligibility * Move function type to economy section * Add test case Arguments will most likely need to be adjusted but that's easy to do * Allow checking multiple users * Update typings part 1 * Update jsdoc * Migrate error handling --------- Co-authored-by: Regalijan <72576136+Regalijan@users.noreply.github.com> * fix: Final touches of error migration (#873) * Don't import buy.js * Apparently autocomplete didn't catch this one * Oh and migrate this too * feat: Implement handleJoinRequests function for batch user join requests (#880) * feat: Implement handleJoinRequests function for batch user join request management * fix: Add missing newline at end of handleJoinRequests.js --------- Co-authored-by: realCartar * Update action --------- Co-authored-by: Regalijan <72576136+Regalijan@users.noreply.github.com> Co-authored-by: Commonly <51011212+commonly-ts@users.noreply.github.com> Co-authored-by: supercoolspy <66487448+supercoolspy@users.noreply.github.com> Co-authored-by: Ethan <39052179+realCartar@users.noreply.github.com> Co-authored-by: realCartar * require lts (#893) * Update perms (#894) * Upgrade sem-rel: use trusted publishing --------- Co-authored-by: Regalijan <72576136+Regalijan@users.noreply.github.com> Co-authored-by: Commonly <51011212+commonly-ts@users.noreply.github.com> Co-authored-by: supercoolspy <66487448+supercoolspy@users.noreply.github.com> Co-authored-by: Ethan <39052179+realCartar@users.noreply.github.com> Co-authored-by: realCartar --- .github/workflows/npmpublish.yml | 7 ++++--- package.json | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml index 86d215ec8..485713843 100644 --- a/.github/workflows/npmpublish.yml +++ b/.github/workflows/npmpublish.yml @@ -12,9 +12,10 @@ on: - next permissions: - contents: read - packages: read + contents: write + packages: write id-token: write + issues: write jobs: test: @@ -65,7 +66,7 @@ jobs: yarn-deps-${{ hashFiles('yarn.lock') }} - name: Create Release - run: npx semantic-release@21.0.2 + run: npx semantic-release@25.0.3 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/package.json b/package.json index 243f5430b..c661ba209 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "branches": ["master", "next"] }, "engines": { - "node": ">=18.18" + "node": ">=20.0" }, "packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" } From b9a75e822bddda36edd6ceee136f057ae312749f Mon Sep 17 00:00:00 2001 From: Regalijan <72576136+Regalijan@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:10:17 -0400 Subject: [PATCH 27/28] fix: Update endpoint for getGamePasses (#875) * Update endpoint * Move away from getPageResults * Remove now-unused limit param --- lib/games/getGamePasses.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/games/getGamePasses.js b/lib/games/getGamePasses.js index 2bd1f2dd2..ad04d9d2a 100644 --- a/lib/games/getGamePasses.js +++ b/lib/games/getGamePasses.js @@ -1,9 +1,9 @@ // Includes -const getPageResults = require('../util/getPageResults.js').func +const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') // Args exports.required = ['universeId'] -exports.optional = ['limit'] // Docs /** @@ -11,29 +11,31 @@ exports.optional = ['limit'] * @category Game * @alias getGamePasses * @param {number} universeId - The id of the universe. - * @param {Limit=} limit - The max number of game passes to return. * @returns {Promise} * @example const noblox = require("noblox.js") * const gamePasses = await noblox.getGamePasses(1686885941) **/ // Define -const getGamePasses = async (universeId, limit) => { - return getPageResults({ - url: `//games.roblox.com/v1/games/${universeId}/game-passes`, - limit - }).catch(err => { - if (err.message === '404 The requested universe does not exist.') { - err.message += '\n\nYou are possibly providing a placeId instead of a universeId.\nUse getPlaceInfo() to retrieve the universeId: https://noblox.js.org/global.html#getPlaceInfo\n' - throw err +const getGamePasses = async (universeId) => { + const res = await http({ + url: `//apis.roblox.com/game-passes/v1/universes/${universeId}/game-passes?passView=Full`, + options: { + json: true, + resolveWithFullResponse: true } - throw err }) + + if (res.statusCode !== 200) { + throw new RobloxAPIError(res) + } + + return res.body.gamePasses } -exports.func = function ({ universeId, limit }) { +exports.func = function ({ universeId }) { if (isNaN(universeId)) { throw new Error('The provided universe ID is not a number.') } - return getGamePasses(universeId, limit) + return getGamePasses(universeId) } From b8c6f30132f8f599da2f908ce72f526ccfda12b1 Mon Sep 17 00:00:00 2001 From: Regalijan <72576136+Regalijan@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:11:04 -0400 Subject: [PATCH 28/28] BREAKING CHANGE: Groups methods overhaul (#876) * getGroups -> getUserGroups and other typing updates * Add multigetPartialGroups * Update imports for the new things * Include locked groups * Add tests * indent fix * Add getPrimaryGroup * Import getPrimaryGroup * Return the body, not the whole response * Allow fetching multiple groups with getLogo * me when no file extension * Fix jsdoc * Make sure types match * Add optional color property to Role interface * Add optional color property to role type jsdoc * Remove isLocked property Roblox no longer returns it * Add PrimaryGroup type --- lib/groups/getGroups.js | 82 ------------------- lib/groups/getPrimaryGroup.js | 37 +++++++++ lib/groups/getUserGroups.js | 37 +++++++++ lib/groups/multigetPartialGroups.js | 44 +++++++++++ lib/index.js | 4 +- lib/thumbnails/getLogo.js | 117 +++++++++++++++------------- test/groups.test.js | 93 +++++++++++++++++----- typings/index.d.ts | 70 ++++++++++++----- typings/jsDocs.ts | 48 +++++++++--- 9 files changed, 344 insertions(+), 188 deletions(-) delete mode 100644 lib/groups/getGroups.js create mode 100644 lib/groups/getPrimaryGroup.js create mode 100644 lib/groups/getUserGroups.js create mode 100644 lib/groups/multigetPartialGroups.js diff --git a/lib/groups/getGroups.js b/lib/groups/getGroups.js deleted file mode 100644 index 70e2d08e8..000000000 --- a/lib/groups/getGroups.js +++ /dev/null @@ -1,82 +0,0 @@ -// Includes -const http = require('../util/http.js').func -const RobloxAPIError = require('../util/apiError.js') - -// Args -exports.required = ['userId'] -exports.optional = [] - -// Docs -/** - * ✅ Get the groups of a user. - * @category User - * @alias getGroups - * @param {number} userId - The id of the user. - * @returns {Promise} - * @example const noblox = require("noblox.js") - * let groups = await noblox.getGroups(123456) -**/ - -// Define -function getGroups (userId) { - return new Promise((resolve, reject) => { - const requests = [ - constructRequest(`//groups.roblox.com/v2/users/${userId}/groups/roles`), - constructRequest(`//groups.roblox.com/v1/users/${userId}/groups/primary/role`) - ].map(promise => promise.then( - val => ({ status: 'fulfilled', value: val }), - rej => ({ status: 'rejected', reason: rej }) - )) - - const result = [] - - Promise.all(requests).then(async (promiseResponses) => { - let responses = promiseResponses.map(response => response.value) - const failedResponse = (responses[0].statusCode !== 200 || !responses[0].body) // we only check the first request because the second one errors if a primary is not set - - if (failedResponse) { - reject(new RobloxAPIError(responses[0])) - } - - responses = responses.map(r => r.body) - - const groupRoleData = responses[0].data - if (groupRoleData) { - const primaryGroupId = responses[1] && responses[1].group && responses[1].group.id - - const groupThumbails = await constructRequest(`https://thumbnails.roblox.com/v1/groups/icons?groupIds=${groupRoleData.map(data => data.group.id).join(',')}&size=150x150&format=Png&isCircular=false`) - - groupRoleData.forEach(data => { - const insertion = { - Id: data.group.id, - Name: data.group.name, - MemberCount: data.group.memberCount, - IsPrimary: data.group.id === primaryGroupId, - Rank: data.role.rank, - Role: data.role.name, - RoleId: data.role.id, - EmblemUrl: groupThumbails.body.data.find(thumbnail => thumbnail.targetId === data.group.id).imageUrl - } - result.push(insertion) - }) - } - - resolve(result) - }) - }) -} - -function constructRequest (url) { - return http({ - url, - options: { - resolveWithFullResponse: true, - followRedirect: false, - json: true - } - }) -} - -exports.func = function (args) { - return getGroups(args.userId) -} diff --git a/lib/groups/getPrimaryGroup.js b/lib/groups/getPrimaryGroup.js new file mode 100644 index 000000000..8498c94b9 --- /dev/null +++ b/lib/groups/getPrimaryGroup.js @@ -0,0 +1,37 @@ +// Includes +const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') + +// Args +exports.required = ['userId'] +exports.optional = [] + +// Docs +/** + * ✅Gets the specified user's primary group. + * @category User + * @alias getPrimaryGroup + * @param {number} userId - The ID of the user. + * @returns {Promise} + * @example const noblox = require("noblox.js") + * const primaryGroup = await noblox.getPrimaryGroup(1) +**/ + +// Define +exports.func = async function (args) { + const { userId } = args + + const response = await http({ + url: `https://groups.roblox.com/v1/users/${userId}/groups/primary/role`, + options: { + json: true, + resolveWithFullResponse: true + } + }) + + if (response.statusCode !== 200) { + throw new RobloxAPIError(response) + } + + return response.body +} diff --git a/lib/groups/getUserGroups.js b/lib/groups/getUserGroups.js new file mode 100644 index 000000000..68fb388d5 --- /dev/null +++ b/lib/groups/getUserGroups.js @@ -0,0 +1,37 @@ +// Includes +const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') + +// Args +exports.required = ['userId'] +exports.optional = [] + +// Docs +/** + * ✅ Get the groups of a user. + * @category User + * @alias getUserGroups + * @param {number} userId - The id of the user. + * @returns {Promise} + * @example const noblox = require("noblox.js") + * let groups = await noblox.getUserGroups(123456) +**/ + +// Define +exports.func = async function (args) { + const { userId } = args + + const response = await http({ + url: `https://groups.roblox.com/v2/users/${userId}/groups/roles?includeLocked=true`, + options: { + json: true, + resolveWithFullResponse: true + } + }) + + if (response.statusCode !== 200) { + throw new RobloxAPIError(response) + } + + return response.body.data +} diff --git a/lib/groups/multigetPartialGroups.js b/lib/groups/multigetPartialGroups.js new file mode 100644 index 000000000..25951c8cd --- /dev/null +++ b/lib/groups/multigetPartialGroups.js @@ -0,0 +1,44 @@ +// Includes +const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') + +// Args +exports.required = ['groupIds'] +exports.optional = [] + +// Docs +/** + * ✅ Gets partial info of multiple groups. + * @category Group + * @alias multigetPartialGroups + * @param {number[]} groupIds - Array of group IDs. + * @returns {Promise} + * @example const noblox = require("noblox.js") + * const groupsInfo = await noblox.multigetPartialGroups([1,2,3]) +**/ + +exports.func = async function (args) { + const { groupIds } = args + + if (!Array.isArray(groupIds)) throw TypeError('Group IDs must be an array') + + const response = await http({ + url: `https://groups.roblox.com/v2/groups?groupIds=${groupIds.join(',')}`, + options: { + json: true, + resolveWithFullResponse: true + } + }) + + if (response.statusCode !== 200) { + throw new RobloxAPIError(response) + } + + const { data } = response.body + + for (let i = 0, len = data.length; i < len; i++) { + data[i].created = new Date(data[i].created) + } + + return data +} diff --git a/lib/index.js b/lib/index.js index cd70edd0f..ca42d895a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -109,21 +109,23 @@ noblox.getAuditLog = require('./groups/getAuditLog.js') noblox.getGroup = require('./groups/getGroup.js') noblox.getGroupPayoutEligibility = require('./economy/getGroupPayoutEligibility.js') noblox.getGroupSocialLinks = require('./groups/getGroupSocialLinks.js') -noblox.getGroups = require('./groups/getGroups.js') noblox.getJoinRequest = require('./groups/getJoinRequest.js') noblox.getJoinRequests = require('./groups/getJoinRequests.js') noblox.getPlayers = require('./groups/getPlayers.js') +noblox.getPrimaryGroup = require('./groups/getPrimaryGroup.js') noblox.getRankInGroup = require('./groups/getRankInGroup.js') noblox.getRankNameInGroup = require('./groups/getRankNameInGroup.js') noblox.getRole = require('./groups/getRole.js') noblox.getRolePermissions = require('./groups/getRolePermissions.js') noblox.getRoles = require('./groups/getRoles.js') noblox.getShout = require('./groups/getShout.js') +noblox.getUserGroups = require('./groups/getUserGroups.js') noblox.getWall = require('./groups/getWall.js') noblox.groupPayout = require('./groups/groupPayout.js') noblox.handleJoinRequest = require('./groups/handleJoinRequest.js') noblox.handleJoinRequests = require('./groups/handleJoinRequests.js') noblox.leaveGroup = require('./groups/leaveGroup.js') +noblox.multigetPartialGroups = require('./groups/multigetPartialGroups.js') noblox.onAuditLog = require('./groups/onAuditLog.js') noblox.onJoinRequest = require('./groups/onJoinRequest.js') noblox.onJoinRequestHandle = require('./groups/onJoinRequestHandle.js') diff --git a/lib/thumbnails/getLogo.js b/lib/thumbnails/getLogo.js index 0c305b5d4..3a8547251 100644 --- a/lib/thumbnails/getLogo.js +++ b/lib/thumbnails/getLogo.js @@ -1,56 +1,61 @@ -// Includes -const http = require('../util/http.js').func -const RobloxAPIError = require('../util/apiError.js') - -// Args -exports.required = ['group'] -exports.optional = ['size', 'circular', 'format'] - -// Docs -/** - * ✅ Get the group's logo. - * @category Group - * @alias getLogo - * @param {number} group - The id of the group. - * @param {GroupIconSize=} [size=150x150] - The size of the logo. - * @param {boolean=} [circular=false] - Get the circular version of the logo. - * @param {GroupIconFormat=} [format=Png] - The file format of the logo. - * @returns {Promise} - * @example const noblox = require("noblox.js") - * const logo = await noblox.getLogo(1) -**/ - -// Define -function getLogo (group, size, circular, format) { - const httpOpt = { - url: '//thumbnails.roblox.com/v1/groups/icons', - options: { - qs: { - groupIds: group, - size: size || '150x150', - format: format || 'Png', - isCircular: circular - }, - json: true, - resolveWithFullResponse: true - } - } - return http(httpOpt) - .then(function (res) { - if (res.statusCode !== 200) { - throw new RobloxAPIError(res) - } - - const thumbnailData = res.body.data[0] - - if (thumbnailData.state !== 'Completed') { - throw new Error('The requested image has not been approved. Status: ' + thumbnailData.state) - } - - return thumbnailData.imageUrl - }) -} - -exports.func = function (args) { - return getLogo(args.group) -} +// Includes +const http = require('../util/http.js').func +const RobloxAPIError = require('../util/apiError.js') + +// Args +exports.required = ['group'] +exports.optional = ['size', 'circular', 'format'] + +// Docs +/** + * ✅ Get the group's logo. + * @category Group + * @alias getLogo + * @param {number || number[]} group - The id(s) of the group. + * @param {GroupIconSize=} [size=150x150] - The size of the logo. + * @param {boolean=} [circular=false] - Get the circular version of the logo. + * @param {GroupIconFormat=} [format=Png] - The file format of the logo. + * @returns {Promise || Promise} + * @example const noblox = require("noblox.js") + * const logo = await noblox.getLogo(1) +**/ + +// Define +function getLogo (group, size, circular, format) { + const groupIds = Array.isArray(group) ? group.join(',') : group + const httpOpt = { + url: '//thumbnails.roblox.com/v1/groups/icons', + options: { + qs: { + groupIds, + size: size || '150x150', + format: format || 'Png', + isCircular: circular + }, + json: true, + resolveWithFullResponse: true + } + } + return http(httpOpt) + .then(function (res) { + if (res.statusCode !== 200) { + throw new RobloxAPIError(res) + } + + const urls = [] + + for (const thumb of res.body.data) { + if (thumb.state !== 'Completed') { + throw new Error(`The requested image for group ${thumb.targetId} has not been approved. State: ${thumb.state}`) + } + + urls.push(thumb.imageUrl) + } + + return urls.length > 1 ? urls : urls.at(0) + }) +} + +exports.func = function (args) { + return getLogo(args.group) +} diff --git a/test/groups.test.js b/test/groups.test.js index 3699f5827..ab11ee1b0 100644 --- a/test/groups.test.js +++ b/test/groups.test.js @@ -1,4 +1,4 @@ -const { banFromGroup, changeRank, demote, getAuditLog, getGroup, getGroupBans, getGroups, getGroupSocialLinks, getJoinRequests, getPlayers, getRankInGroup, getRankNameInGroup, getRole, getRolePermissions, getRoles, getShout, getWall, promote, searchGroups, setRank, shout, setCookie, unbanFromGroup } = require('../lib') +const { banFromGroup, changeRank, demote, getAuditLog, getGroup, getGroupBans, getGroupSocialLinks, getJoinRequests, getPlayers, getPrimaryGroup, getRankInGroup, getRankNameInGroup, getRole, getRolePermissions, getRoles, getShout, getUserGroups, getWall, multigetPartialGroups, promote, searchGroups, setRank, shout, setCookie, unbanFromGroup } = require('../lib') beforeAll(() => { return new Promise(resolve => { @@ -115,25 +115,6 @@ describe('Groups Methods', () => { }) }) - it('getGroups() should return groups the specified user is in', async () => { - return getGroups(55549140).then((res) => { - return expect(res).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - Id: expect.any(Number), - Name: expect.any(String), - EmblemUrl: expect.any(String), - MemberCount: expect.any(Number), - Rank: expect.any(Number), - Role: expect.any(String), - RoleId: expect.any(Number), - IsPrimary: expect.any(Boolean) - }) - ]) - ) - }) - }) - it('getGroupSocialLinks() should return social link information of a game, given universeId', () => { return getGroupSocialLinks(9997719).then((res) => { return expect(res).toEqual( @@ -174,6 +155,36 @@ describe('Groups Methods', () => { }) }) + it('getPrimaryGroup() returns the specified user\'s primary group', () => { + return getPrimaryGroup(55549140).then((res) => { + return expect(res).toEqual( + expect.objectContaining({ + group: expect.objectContaining({ + id: expect.any(Number), + name: expect.any(String), + description: expect.any(String), + owner: expect.objectContaining({ + hasVerifiedBadge: expect.any(Boolean), + userId: expect.any(Number), + username: expect.any(String), + displayName: expect.any(String) + }), + shout: expect.toBeNull(), + isBuildersClubOnly: expect.any(Boolean), + publicEntryAllowed: expect.any(Boolean), + hasVerifiedBadge: expect.any(Boolean), + hasSocialModules: expect.any(Boolean), + }), + role: expect.objectContaining({ + id: expect.any(Number), + name: expect.any(String), + rank: expect.any(Number) + }) + }) + ) + }) + }) + it('getRankInGroup() returns a number reflecting a user\'s rank in a group (0-255)', () => { return getRankInGroup(4591072, 55549140).then((res) => { return expect(res).toEqual(expect.any(Number)) @@ -236,6 +247,28 @@ describe('Groups Methods', () => { }) }) + it('getUserGroups() should return groups the specified user is in', async () => { + return getUserGroups(55549140).then((res) => { + return expect(res).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + group: expect.objectContaining({ + id: expect.any(Number), + name: expect.any(String), + memberCount: expect.any(Number), + hasVerifiedBadge: expect.any(Boolean) + }), + role: expect.objectContaining({ + id: expect.any(Number), + name: expect.any(String), + rank: expect.any(Number) + }) + }) + ]) + ) + }) + }) + it('getWall() returns the latest messages on the group wall', () => { return getWall(4591072).then((res) => { return expect(res).toMatchObject({ @@ -262,6 +295,26 @@ describe('Groups Methods', () => { // PASS: leaveGroup, would require being able to request to join a group + it('multigetPartialGroups() returns partial information of multiple groups', () => { + return multigetPartialGroups([4591072]).then((res) => { + return expect(res).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(Number), + name: expect.any(String), + description: expect.any(String), + owner: expect.objectContaining({ + id: expect.any(Number), + name: expect.any(String) + }), + created: expect.any(Date), + hasVerifiedBadge: expect.any(Boolean) + }) + ]) + ) + }) + }) + it('searchGroups() returns groups that match the query', () => { return searchGroups('noblox.js').then((res) => { return expect(res).toEqual( diff --git a/typings/index.d.ts b/typings/index.d.ts index f86902be7..e2736d169 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -171,15 +171,18 @@ declare module "noblox.js" { HasVerifiedBadge: boolean; } - interface IGroupPartial { - Name: string, - Id: number, - EmblemUrl: string, - MemberCount: number, - Rank: number, - Role: string, - RoleId: number, - IsPrimary: boolean, + interface GroupMemberInfo { + group: { + id: number; + name: string; + memberCount: number; + hasVerifiedBadge: boolean; + }; + role: { + id: number; + name: string; + rank: number; + } } interface GroupGameInfo { @@ -767,6 +770,7 @@ declare module "noblox.js" { memberCount?: number; rank: number; id: number; + color?: number; } interface RoleWithDescription { @@ -834,7 +838,8 @@ declare module "noblox.js" { memberCount: number; isBuildersClubOnly: boolean; publicEntryAllowed: boolean; - isLocked: boolean; + hasVerifiedBadge: boolean; + hasSocialModules: boolean; } interface GroupSearchItem { @@ -868,6 +873,23 @@ declare module "noblox.js" { updated: Date; } + interface GroupMultigetPartial { + id: number; + name: string; + description: string; + owner: { + id: number; + type: string; + }; + created: Date; + hasVerifiedBadge: boolean; + } + + interface PrimaryGroup { + group: Group; + role: Role; + } + interface PayoutAllowedList { usersGroupPayoutEligibility: { [K: string]: string; @@ -1996,11 +2018,6 @@ declare module "noblox.js" { */ function getGroupBans(groupId: number, limit?: number, sortOrder?: SortOrder, pageCursor?: string, jar?: CookieJar): Promise<{ previousPageCursor?: string, nextPageCursor?: string, data: GroupBan[] }>; - /** - * ✅ Gets the groups a player is in. - */ - function getGroups(userId: number): Promise - /** * 🔐 Get the social link data associated with a group. */ @@ -2022,8 +2039,13 @@ declare module "noblox.js" { function getPlayers(group: number, rolesetId: number[] | number, sortOrder?: SortOrder, limit?: number, jar?: CookieJar): Promise; /** - * ✅ Gets `rank` of user with `userId` in `group` and caches according to settings. - */ + * ✅Gets the specified user's primary group. + */ + function getPrimaryGroup(userId: number): Promise; + + /** + * ✅ Gets `rank` of user with `userId` in `group` and caches according to settings. + */ function getRankInGroup(group: number, userId: number): Promise; /** @@ -2051,6 +2073,11 @@ declare module "noblox.js" { */ function getShout(group: number, jar?: CookieJar): Promise; + /** + * ✅ Gets the groups a player is in. + */ + function getUserGroups(userId: number): Promise; + /** * 🔓 Gets posts on the `group` wall. Parameter `page` may be a number or array where negative numbers indicate trailing pages, if it is not specified all pages of the wall will be retrieved. * The body of the post is in `content` and the `id` and `name` of the poster are stored in the `author` object. The `id` is the unique ID of the wall post that is internally used by ROBLOX. This serves no real use other than reporting it (although it can be used indirectly to track down specific posts). @@ -2080,6 +2107,11 @@ declare module "noblox.js" { */ function leaveGroup(group: number, jar?: CookieJar): Promise; + /** + * ✅ Gets partial info of multiple groups. + */ + function multigetPartialGroups(groupIds: number[]): Promise; + /** * 🔐 Alias of `changeRank(group, target, 1)`. */ @@ -2180,9 +2212,9 @@ declare module "noblox.js" { /// Thumbnails /** - * ✅ Gets the logo of the specified group. + * ✅ Gets the logo of the specified group(s). */ - function getLogo(groupId: number, size?: GroupIconSize, circular?: boolean, format?: GroupIconFormat): Promise; + function getLogo(groupIds: number | number[], size?: GroupIconSize, circular?: boolean, format?: GroupIconFormat): Promise | Promise; /** * ✅ Gets the thumbnail of an array of users. diff --git a/typings/jsDocs.ts b/typings/jsDocs.ts index 1823ffb73..ca0b73d76 100644 --- a/typings/jsDocs.ts +++ b/typings/jsDocs.ts @@ -1026,6 +1026,7 @@ type Role = { memberCount?: number; rank: number; id: number; + color?: number; } /** @@ -1120,7 +1121,23 @@ type Group = { memberCount: number; isBuildersClubOnly: boolean; publicEntryAllowed: boolean; - isLocked: boolean; + hasVerifiedBadge: boolean; + hasSocialModules: boolean; +} + +/** + * @typedef +*/ +type GroupMultigetPartial = { + id: number; + name: string; + description: string; + owner: { + id: number; + type: string; + }; + created: Date; + hasVerifiedBadge: boolean; } /** @@ -1161,17 +1178,28 @@ type GroupAssetInfo = { /** * @typedef */ -type IGroupPartial = { - Name: string; - Id: number; - EmblemUrl: string; - MemberCount: number; - Rank: number; - Role: string; - RoleId: number; - IsPrimary: boolean; +type GroupMemberInfo = { + group: { + id: number; + name: string; + memberCount: number; + hasVerifiedBadge: boolean; + }; + role: { + id: number; + name: string; + rank: number; + } } + /** + * @typedef + */ + type PrimaryGroup = { + group: Group; + role: Role; + } + /** * @typedef */