noblox-asset.js 7.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
- package/.github/workflows/doc-publish.yml +33 -0
- package/.github/workflows/npmpublish.yml +72 -0
- package/.github/workflows/run-tests.yml +49 -0
- package/CODE_OF_CONDUCT.md +76 -0
- package/LICENSE +21 -0
- package/README.md +182 -0
- package/eslint.config.mjs +34 -0
- package/examples/cleanPlayers.js +130 -0
- package/examples/cleanWall.js +110 -0
- package/examples/revertRanks.js +100 -0
- package/examples/savePlayers.js +119 -0
- package/examples/saveWall.js +96 -0
- package/img/moderatedThumbnails/moderatedThumbnail_100x100.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_110x110.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_140x140.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_150x150.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_150x200.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_180x180.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_250x250.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_30x30.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_352x352.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_420x420.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_48x48.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_50x50.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_60x60.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_720x720.png +0 -0
- package/img/moderatedThumbnails/moderatedThumbnail_75x75.png +0 -0
- package/img/noblox-js-small.png +0 -0
- package/img/noblox-js.png +0 -0
- package/img/thumbnailSizes.png +0 -0
- package/jsDocsConfig.json +55 -0
- package/lib/accountinformation/getUserSocialLinks.js +40 -0
- package/lib/accountsettings/block.js +53 -0
- package/lib/accountsettings/unblock.js +53 -0
- package/lib/asset/deleteFromInventory.js +56 -0
- package/lib/asset/getGamePassProductInfo.js +50 -0
- package/lib/asset/getProductInfo.js +46 -0
- package/lib/asset/uploadAnimation.js +103 -0
- package/lib/asset/uploadItem.js +83 -0
- package/lib/asset/uploadModel.js +90 -0
- package/lib/avatar/avatarRules.js +39 -0
- package/lib/avatar/currentlyWearing.js +33 -0
- package/lib/avatar/getAvatar.js +36 -0
- package/lib/avatar/getCurrentAvatar.js +38 -0
- package/lib/avatar/getRecentItems.js +36 -0
- package/lib/avatar/outfitDetails.js +33 -0
- package/lib/avatar/outfits.js +38 -0
- package/lib/avatar/redrawAvatar.js +47 -0
- package/lib/avatar/removeAssetId.js +51 -0
- package/lib/avatar/setAvatarBodyColors.js +61 -0
- package/lib/avatar/setAvatarScales.js +61 -0
- package/lib/avatar/setPlayerAvatarType.js +51 -0
- package/lib/avatar/setWearingAssets.js +51 -0
- package/lib/avatar/wearAssetId.js +51 -0
- package/lib/badges/getAwardedTimestamps.js +49 -0
- package/lib/badges/getBadgeInfo.js +44 -0
- package/lib/badges/getGameBadges.js +63 -0
- package/lib/badges/getPlayerBadges.js +28 -0
- package/lib/badges/updateBadgeInfo.js +75 -0
- package/lib/cache/add.js +14 -0
- package/lib/cache/addIf.js +26 -0
- package/lib/cache/clear.js +8 -0
- package/lib/cache/get.js +28 -0
- package/lib/cache/index.js +17 -0
- package/lib/cache/new.js +12 -0
- package/lib/cache/wrap.js +25 -0
- package/lib/chat/addUsersToConversation.js +58 -0
- package/lib/chat/chatSettings.js +34 -0
- package/lib/chat/getChatMessages.js +41 -0
- package/lib/chat/getConversations.js +44 -0
- package/lib/chat/getRolloutSettings.js +36 -0
- package/lib/chat/getUnreadConversationCount.js +34 -0
- package/lib/chat/getUnreadMessages.js +39 -0
- package/lib/chat/getUserConversations.js +38 -0
- package/lib/chat/markChatAsRead.js +53 -0
- package/lib/chat/markChatAsSeen.js +51 -0
- package/lib/chat/multiGetLatestMessages.js +38 -0
- package/lib/chat/onNewConversation.js +50 -0
- package/lib/chat/onNewMessage.js +53 -0
- package/lib/chat/onNewMessageBySelf.js +50 -0
- package/lib/chat/onUserOnline.js +50 -0
- package/lib/chat/onUserTyping.js +54 -0
- package/lib/chat/removeFromGroupConversation.js +59 -0
- package/lib/chat/renameGroupConversation.js +58 -0
- package/lib/chat/sendChatMessage.js +58 -0
- package/lib/chat/setChatUserTyping.js +58 -0
- package/lib/chat/start121Conversation.js +51 -0
- package/lib/chat/startCloudEditConversation.js +51 -0
- package/lib/chat/startGroupConversation.js +59 -0
- package/lib/client/onNotification.js +86 -0
- package/lib/client/setAPIKey.js +18 -0
- package/lib/client/setCookie.js +38 -0
- package/lib/datastores/deleteDatastoreEntry.js +56 -0
- package/lib/datastores/getDatastoreEntry.js +88 -0
- package/lib/datastores/getDatastoreEntryVersions.js +73 -0
- package/lib/datastores/getDatastoreKeys.js +63 -0
- package/lib/datastores/getDatastores.js +62 -0
- package/lib/datastores/incrementDatastoreEntry.js +83 -0
- package/lib/datastores/setDatastoreEntry.js +80 -0
- package/lib/develop/canManage.js +42 -0
- package/lib/develop/configureItem.js +210 -0
- package/lib/develop/updateUniverse.js +52 -0
- package/lib/develop/updateUniverseAccess.js +54 -0
- package/lib/economy/getGroupFunds.js +42 -0
- package/lib/economy/getGroupPayoutEligibility.js +55 -0
- package/lib/economy/getGroupRevenueSummary.js +42 -0
- package/lib/economy/getGroupTransactions.js +32 -0
- package/lib/economy/getResaleData.js +48 -0
- package/lib/economy/getResellers.js +35 -0
- package/lib/economy/getUserFunds.js +43 -0
- package/lib/economy/getUserTransactions.js +34 -0
- package/lib/economy/onGroupTransaction.js +74 -0
- package/lib/friends/acceptFriendRequest.js +54 -0
- package/lib/friends/declineAllFriendRequests.js +52 -0
- package/lib/friends/declineFriendRequest.js +54 -0
- package/lib/friends/getFollowerCount.js +35 -0
- package/lib/friends/getFollowers.js +56 -0
- package/lib/friends/getFollowingCount.js +35 -0
- package/lib/friends/getFollowings.js +56 -0
- package/lib/friends/getFriendCount.js +35 -0
- package/lib/friends/getFriendRequests.js +51 -0
- package/lib/friends/getFriends.js +48 -0
- package/lib/friends/onFriendRequest.js +58 -0
- package/lib/friends/removeFriend.js +53 -0
- package/lib/friends/sendFriendRequest.js +54 -0
- package/lib/friends/unfollow.js +53 -0
- package/lib/games/addDeveloperProduct.js +53 -0
- package/lib/games/configureGamePass.js +126 -0
- package/lib/games/getDeveloperProducts.js +44 -0
- package/lib/games/getGameInstances.js +31 -0
- package/lib/games/getGamePasses.js +39 -0
- package/lib/games/getGameRevenue.js +48 -0
- package/lib/games/getGameSocialLinks.js +42 -0
- package/lib/games/getGroupGames.js +30 -0
- package/lib/games/getPlaceInfo.js +47 -0
- package/lib/games/getUniverseInfo.js +50 -0
- package/lib/games/publishToTopic.js +52 -0
- package/lib/games/sendUserNotification.js +65 -0
- package/lib/games/updateDeveloperProduct.js +56 -0
- package/lib/groups/banFromGroup.js +47 -0
- package/lib/groups/changeRank.js +59 -0
- package/lib/groups/deleteWallPost.js +59 -0
- package/lib/groups/deleteWallPostsByUser.js +55 -0
- package/lib/groups/demote.js +25 -0
- package/lib/groups/exile.js +55 -0
- package/lib/groups/getAuditLog.js +64 -0
- package/lib/groups/getGroup.js +50 -0
- package/lib/groups/getGroupBans.js +37 -0
- package/lib/groups/getGroupSocialLinks.js +41 -0
- package/lib/groups/getGroups.js +82 -0
- package/lib/groups/getJoinRequest.js +47 -0
- package/lib/groups/getJoinRequests.js +53 -0
- package/lib/groups/getPlayers.js +103 -0
- package/lib/groups/getRankInGroup.js +45 -0
- package/lib/groups/getRankNameInGroup.js +44 -0
- package/lib/groups/getRole.js +64 -0
- package/lib/groups/getRolePermissions.js +48 -0
- package/lib/groups/getRoles.js +53 -0
- package/lib/groups/getShout.js +47 -0
- package/lib/groups/getWall.js +56 -0
- package/lib/groups/groupPayout.js +103 -0
- package/lib/groups/handleJoinRequest.js +57 -0
- package/lib/groups/handleJoinRequests.js +61 -0
- package/lib/groups/leaveGroup.js +56 -0
- package/lib/groups/onAuditLog.js +62 -0
- package/lib/groups/onJoinRequest.js +63 -0
- package/lib/groups/onJoinRequestHandle.js +105 -0
- package/lib/groups/onShout.js +57 -0
- package/lib/groups/onWallPost.js +58 -0
- package/lib/groups/promote.js +25 -0
- package/lib/groups/searchGroups.js +32 -0
- package/lib/groups/setGroupDescription.js +58 -0
- package/lib/groups/setGroupName.js +59 -0
- package/lib/groups/setRank.js +74 -0
- package/lib/groups/setRoleInfo.js +84 -0
- package/lib/groups/shout.js +58 -0
- package/lib/groups/unbanFromGroup.js +41 -0
- package/lib/index.js +212 -0
- package/lib/internal/levelOneCopy.js +16 -0
- package/lib/internal/queue.js +61 -0
- package/lib/internal/timeout.js +30 -0
- package/lib/internal/wrap.js +78 -0
- package/lib/inventory/getCollectibles.js +31 -0
- package/lib/inventory/getInventory.js +32 -0
- package/lib/inventory/getInventoryById.js +31 -0
- package/lib/inventory/getOwnership.js +47 -0
- package/lib/inventory/getUAIDs.js +47 -0
- package/lib/itemconfiguration/getGroupAssets.js +32 -0
- package/lib/options.js +26 -0
- package/lib/party/onPartyDeleted.js +53 -0
- package/lib/party/onPartyInvite.js +53 -0
- package/lib/party/onPartyJoinedGame.js +53 -0
- package/lib/party/onPartyLeftGame.js +53 -0
- package/lib/party/onPartySelfJoined.js +53 -0
- package/lib/party/onPartySelfLeft.js +53 -0
- package/lib/party/onPartyUserJoined.js +53 -0
- package/lib/party/onPartyUserLeft.js +53 -0
- package/lib/premiumfeatures/getPremium.js +46 -0
- package/lib/presence/getPresences.js +60 -0
- package/lib/privatemessages/getMessages.js +55 -0
- package/lib/privatemessages/message.js +75 -0
- package/lib/privatemessages/onMessage.js +89 -0
- package/lib/thumbnails/getLogo.js +56 -0
- package/lib/thumbnails/getPlayerThumbnail.js +120 -0
- package/lib/thumbnails/getThumbnails.js +92 -0
- package/lib/trades/acceptTrade.js +52 -0
- package/lib/trades/canTradeWith.js +43 -0
- package/lib/trades/counterTrade.js +78 -0
- package/lib/trades/declineTrade.js +52 -0
- package/lib/trades/getTradeInfo.js +47 -0
- package/lib/trades/getTrades.js +37 -0
- package/lib/trades/sendTrade.js +76 -0
- package/lib/users/getBlurb.js +37 -0
- package/lib/users/getIdFromUsername.js +56 -0
- package/lib/users/getPlayerInfo.js +128 -0
- package/lib/users/getUserInfo.js +35 -0
- package/lib/users/getUsernameFromId.js +45 -0
- package/lib/users/getUsernameHistory.js +42 -0
- package/lib/users/onBlurbChange.js +46 -0
- package/lib/users/searchUsers.js +41 -0
- package/lib/util/apiError.js +50 -0
- package/lib/util/clearSession.js +32 -0
- package/lib/util/generalRequest.js +61 -0
- package/lib/util/getAction.js +45 -0
- package/lib/util/getAuthenticatedUser.js +41 -0
- package/lib/util/getCurrentUser.js +58 -0
- package/lib/util/getGeneralToken.js +52 -0
- package/lib/util/getHash.js +29 -0
- package/lib/util/getInputs.js +37 -0
- package/lib/util/getPageResults.js +86 -0
- package/lib/util/getSenderUserId.js +30 -0
- package/lib/util/getSession.js +38 -0
- package/lib/util/getVerification.js +60 -0
- package/lib/util/getVerificationInputs.js +31 -0
- package/lib/util/http.js +138 -0
- package/lib/util/jar.js +25 -0
- package/lib/util/refreshCookie.js +52 -0
- package/lib/util/relog.js +81 -0
- package/lib/util/setOptions.js +54 -0
- package/lib/util/shortPoll.js +102 -0
- package/package.json +76 -0
- package/postinstall.mjs +1 -0
- package/settings.json +115 -0
- package/test/accountinformation.test.js +27 -0
- package/test/accountsettings.test.js +27 -0
- package/test/asset.test.js +81 -0
- package/test/assets/Great-White-Shark-Fin.rbxm +0 -0
- package/test/assets/KeyframeSequence.rbxm +0 -0
- package/test/avatar.test.js +164 -0
- package/test/badges.test.js +96 -0
- package/test/chat.test.js +104 -0
- package/test/datastore.test.js +105 -0
- package/test/develop.test.js +53 -0
- package/test/economy.test.js +143 -0
- package/test/friends.test.js +146 -0
- package/test/games.test.js +229 -0
- package/test/groups.test.js +405 -0
- package/test/inventory.test.js +98 -0
- package/test/itemconfiguration.test.js +24 -0
- package/test/premiumfeatures.test.js +17 -0
- package/test/presence.test.js +25 -0
- package/test/privatemessages.test.js +33 -0
- package/test/thumbnails.test.js +53 -0
- package/test/users.test.js +110 -0
- package/tutorials/Authentication.md +75 -0
- package/tutorials/Event Emitters.md +26 -0
- package/tutorials/Promises.md +86 -0
- package/tutorials/VPS Authentication.md +72 -0
- package/typings/index.d.ts +2639 -0
- package/typings/jsDocs.ts +1987 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Dependencies
|
|
2
|
+
const crypto = require('crypto')
|
|
3
|
+
|
|
4
|
+
// Includes
|
|
5
|
+
const getSession = require('./getSession.js').func
|
|
6
|
+
|
|
7
|
+
// Args
|
|
8
|
+
exports.optional = ['jar']
|
|
9
|
+
|
|
10
|
+
// Docs
|
|
11
|
+
/**
|
|
12
|
+
* 🔐 Get a unique hash for the given jar. Used to cache items that depend on session.
|
|
13
|
+
* @category Utility
|
|
14
|
+
* @alias getHash
|
|
15
|
+
* @param {CookieJar} jar - The audit log action row.
|
|
16
|
+
* @returns {string}
|
|
17
|
+
* @example const noblox = require("noblox.js")
|
|
18
|
+
* // Login using your cookie.
|
|
19
|
+
* const hash = noblox.getHash()
|
|
20
|
+
**/
|
|
21
|
+
|
|
22
|
+
// Define
|
|
23
|
+
exports.func = function ({ jar }) {
|
|
24
|
+
if (typeof jar === 'string') {
|
|
25
|
+
jar = { session: jar }
|
|
26
|
+
}
|
|
27
|
+
const session = getSession({ jar })
|
|
28
|
+
return crypto.createHash('md5').update(session).digest('hex')
|
|
29
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Dependencies
|
|
2
|
+
const parser = require('cheerio')
|
|
3
|
+
|
|
4
|
+
// Args
|
|
5
|
+
exports.required = ['html']
|
|
6
|
+
exports.optional = ['find']
|
|
7
|
+
|
|
8
|
+
// Docs
|
|
9
|
+
/**
|
|
10
|
+
* ✅ Get verification inputs on a page.
|
|
11
|
+
* @category Utility
|
|
12
|
+
* @alias getInputs
|
|
13
|
+
* @param {string} html - The html to get the inputs from.
|
|
14
|
+
* @param {Array<string>=} find - The inputs to find on the page
|
|
15
|
+
* @returns {Inputs}
|
|
16
|
+
* @example const noblox = require("noblox.js")
|
|
17
|
+
* const inputs = noblox.getInputs("htmlhere")
|
|
18
|
+
**/
|
|
19
|
+
|
|
20
|
+
// Define
|
|
21
|
+
exports.func = function (args) {
|
|
22
|
+
const $ = parser.load(args.html)
|
|
23
|
+
const inputs = {}
|
|
24
|
+
const find = args.find
|
|
25
|
+
if (find) {
|
|
26
|
+
for (let i = 0; i < find.length; i++) {
|
|
27
|
+
const get = find[i]
|
|
28
|
+
inputs[get] = $('input[name=' + get + ']').val()
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
$('input[name]').each(function (index, element) {
|
|
32
|
+
const here = $(this)
|
|
33
|
+
inputs[here.attr('name')] = here.val()
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
return inputs
|
|
37
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Includes
|
|
2
|
+
const http = require('./http.js').func
|
|
3
|
+
const RobloxAPIError = require('./apiError.js')
|
|
4
|
+
|
|
5
|
+
// Args
|
|
6
|
+
exports.required = ['url', 'query', 'limit']
|
|
7
|
+
exports.optional = ['jar', 'sortOrder']
|
|
8
|
+
|
|
9
|
+
// Docs
|
|
10
|
+
/**
|
|
11
|
+
* ✅ Handle pagination returned by Roblox.
|
|
12
|
+
* @category Utility
|
|
13
|
+
* @alias getPageResults
|
|
14
|
+
* @param {string} url - The url to retrieve the page results from.
|
|
15
|
+
* @param {string} query - Any query parameters to add to the url.
|
|
16
|
+
* @param {SortOrder=} sortOrder - The order to sort the results by.
|
|
17
|
+
* @param {Limit=} limit - The maximum number of results to return. Following 'pages' of results will be requested until
|
|
18
|
+
* this limit of results is reached.
|
|
19
|
+
* @param {string=} pageCursor - Current page index
|
|
20
|
+
* @returns {Promise<Array>}
|
|
21
|
+
* @example const noblox = require("noblox.js")
|
|
22
|
+
* const inventory = await noblox.getPageResults("//inventory.roblox.com/v2/users/1/inventory", "Shirt", "Asc", 100)
|
|
23
|
+
**/
|
|
24
|
+
|
|
25
|
+
// Define
|
|
26
|
+
function getPageResults (jar, url, query, sortOrder, limit, pageCursor, results) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const allowedLimits = [10, 25, 50, 100]
|
|
29
|
+
|
|
30
|
+
const httpOpt = {
|
|
31
|
+
url,
|
|
32
|
+
options: {
|
|
33
|
+
qs: {
|
|
34
|
+
limit: limit <= 100 ? allowedLimits.reduce((prev, curr) => Math.abs(curr - limit) < Math.abs(prev - limit) ? curr : prev) : 100, // Get the most fit page limit within the boundries.
|
|
35
|
+
cursor: pageCursor || '', // Add page cursor.
|
|
36
|
+
...query // Add asset types.
|
|
37
|
+
},
|
|
38
|
+
method: 'GET',
|
|
39
|
+
resolveWithFullResponse: true,
|
|
40
|
+
jar,
|
|
41
|
+
json: true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return http(httpOpt).then((res) => {
|
|
45
|
+
let body = res.body
|
|
46
|
+
if (typeof (body) === 'string') body = JSON.parse(body.trim())
|
|
47
|
+
|
|
48
|
+
const data = body.data
|
|
49
|
+
|
|
50
|
+
if (res.statusCode !== 200) {
|
|
51
|
+
return reject(new RobloxAPIError(res))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
results = results ? results.concat(data) : data
|
|
55
|
+
|
|
56
|
+
if (results.length > limit) {
|
|
57
|
+
results = results.slice(0, limit)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (results.length >= limit || data.length === 0 || !body.nextPageCursor) {
|
|
61
|
+
return resolve(results)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
resolve(getPageResults(jar, url, query, sortOrder, limit, body.nextPageCursor, results))
|
|
65
|
+
})
|
|
66
|
+
.catch(error => reject(error))
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseDates (results) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
if (!results) return resolve([])
|
|
73
|
+
|
|
74
|
+
resolve(results.map(result => {
|
|
75
|
+
if (result.created) result.created = new Date(result.created)
|
|
76
|
+
if (result.updated) result.updated = new Date(result.updated)
|
|
77
|
+
return result
|
|
78
|
+
}))
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
exports.func = function (args) {
|
|
83
|
+
return getPageResults(args.jar, args.url, args.query, args.sortOrder, args.limit).then(results => {
|
|
84
|
+
return parseDates(results)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Includes
|
|
2
|
+
const getCurrentUser = require('./getCurrentUser.js').func
|
|
3
|
+
const getHash = require('./getHash.js').func
|
|
4
|
+
const cache = require('../cache')
|
|
5
|
+
|
|
6
|
+
// Args
|
|
7
|
+
exports.optional = ['jar']
|
|
8
|
+
|
|
9
|
+
// Docs
|
|
10
|
+
/**
|
|
11
|
+
* 🔐 Get the userId of the current user and cache it.
|
|
12
|
+
* @category Utility
|
|
13
|
+
* @alias getSenderUserId
|
|
14
|
+
* @param {CookieJar=} jar - The CookieJar containing the .ROBLOSECURITY cookie.
|
|
15
|
+
* @returns {Promise<number>}
|
|
16
|
+
* @example const noblox = require("noblox.js")
|
|
17
|
+
* // Login using your cookie.
|
|
18
|
+
* const userId = await noblox.getSenderUserId()
|
|
19
|
+
**/
|
|
20
|
+
|
|
21
|
+
// Define
|
|
22
|
+
exports.func = function (args) {
|
|
23
|
+
const jar = args.jar
|
|
24
|
+
return cache.wrap('SenderID', getHash({ jar }), function () {
|
|
25
|
+
return getCurrentUser({ jar })
|
|
26
|
+
.then(function (info) {
|
|
27
|
+
return info.UserID
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Includes
|
|
2
|
+
const settings = require('../../settings.json')
|
|
3
|
+
const options = require('../options.js')
|
|
4
|
+
|
|
5
|
+
// Args
|
|
6
|
+
exports.optional = ['jar']
|
|
7
|
+
|
|
8
|
+
// Docs
|
|
9
|
+
/**
|
|
10
|
+
* 🔐 Get the .ROBLOSECURITY cookie from the jar.
|
|
11
|
+
* @category Utility
|
|
12
|
+
* @alias getSession
|
|
13
|
+
* @param {CookieJar=} jar - The cookie jar containing the .ROBLOSECURITY cookie.
|
|
14
|
+
* @returns {string}
|
|
15
|
+
* @example const noblox = require("noblox.js")
|
|
16
|
+
* // Login using your cookie.
|
|
17
|
+
* const cookie = await noblox.getSession()
|
|
18
|
+
**/
|
|
19
|
+
|
|
20
|
+
// Define
|
|
21
|
+
exports.func = function (args) {
|
|
22
|
+
const jar = args.jar || options.jar
|
|
23
|
+
if (settings.session_only) {
|
|
24
|
+
if (typeof jar === 'string') {
|
|
25
|
+
return jar
|
|
26
|
+
}
|
|
27
|
+
return jar.session
|
|
28
|
+
} else {
|
|
29
|
+
const cookies = jar.getCookies('https://roblox.com')
|
|
30
|
+
for (let i = 0; i < cookies.length; i++) {
|
|
31
|
+
const element = cookies[i]
|
|
32
|
+
if (element.key === '.ROBLOSECURITY') {
|
|
33
|
+
return element.value
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return ''
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Includes
|
|
2
|
+
const http = require('./http.js').func
|
|
3
|
+
const getHash = require('./getHash.js').func
|
|
4
|
+
const getVerificationInputs = require('./getVerificationInputs.js').func
|
|
5
|
+
const cache = require('../cache')
|
|
6
|
+
const URL = require('url').URL
|
|
7
|
+
|
|
8
|
+
// Args
|
|
9
|
+
exports.required = ['url']
|
|
10
|
+
exports.optional = ['ignoreCache', 'getBody', 'jar']
|
|
11
|
+
|
|
12
|
+
// Docs
|
|
13
|
+
/**
|
|
14
|
+
* 🔐 Get the RequestVerificationToken from a url.
|
|
15
|
+
* @category Utility
|
|
16
|
+
* @alias getVerification
|
|
17
|
+
* @param {string} url - The url to get the token from.
|
|
18
|
+
* @param {boolean=} [ignoreCache=false] - Determines whether the cache be ignored or not.
|
|
19
|
+
* @param {boolean=} [getBody=false] - If the body and inputs should be returned in an object
|
|
20
|
+
* @param {CookieJar=} jar - The CookieJar containing the .ROBLOSECURITY cookie.
|
|
21
|
+
* @returns {Promise<GetVerificationResponse>}
|
|
22
|
+
* @example const noblox = require("noblox.js")
|
|
23
|
+
* // Login using your cookie.
|
|
24
|
+
* const verificationTokenInfo = await noblox.getVerification()
|
|
25
|
+
**/
|
|
26
|
+
|
|
27
|
+
// Define
|
|
28
|
+
function getVerification (jar, url, getBody) {
|
|
29
|
+
const httpOpt = {
|
|
30
|
+
url,
|
|
31
|
+
options: {
|
|
32
|
+
resolveWithFullResponse: true,
|
|
33
|
+
jar
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return http(httpOpt)
|
|
37
|
+
.then(function (res) {
|
|
38
|
+
const inputs = getVerificationInputs({ html: res.body })
|
|
39
|
+
let match
|
|
40
|
+
if (res.headers && res.headers['set-cookie']) {
|
|
41
|
+
match = res.headers['set-cookie'].toString().match(/__RequestVerificationToken=(.*?);/)
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
body: (getBody ? res.body : null),
|
|
45
|
+
inputs,
|
|
46
|
+
header: match && match[1]
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
exports.func = function (args) {
|
|
52
|
+
const jar = args.jar
|
|
53
|
+
if (args.ignoreCache) {
|
|
54
|
+
return getVerification(jar, args.url, args.getBody)
|
|
55
|
+
} else {
|
|
56
|
+
return cache.wrap('Verify', new URL(args.url).pathname + getHash({ jar }), function () {
|
|
57
|
+
return getVerification(jar, args.url, args.getBody)
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Dependencies
|
|
2
|
+
const parser = require('cheerio')
|
|
3
|
+
|
|
4
|
+
// Args
|
|
5
|
+
exports.required = [['html', 'selector']]
|
|
6
|
+
|
|
7
|
+
// Docs
|
|
8
|
+
/**
|
|
9
|
+
* ✅ Get the verification inputs from the html.
|
|
10
|
+
* @category Utility
|
|
11
|
+
* @alias getVerificationInputs
|
|
12
|
+
* @param {string | function} html | selector - The html to search or the cheerio selector to use.
|
|
13
|
+
* @returns {Inputs}
|
|
14
|
+
* @example const noblox = require("noblox.js")
|
|
15
|
+
* const inputs = noblox.getVerificationInputs("htmlstuff")
|
|
16
|
+
**/
|
|
17
|
+
|
|
18
|
+
// Define
|
|
19
|
+
exports.func = function (args) {
|
|
20
|
+
let $ = args.selector
|
|
21
|
+
if (!$) {
|
|
22
|
+
$ = parser.load(args.html)
|
|
23
|
+
}
|
|
24
|
+
const inputs = {}
|
|
25
|
+
const find = ['__VIEWSTATE', '__VIEWSTATEGENERATOR', '__EVENTVALIDATION', '__RequestVerificationToken']
|
|
26
|
+
for (let i = 0; i < find.length; i++) {
|
|
27
|
+
const get = find[i]
|
|
28
|
+
inputs[get] = $('input[name=' + get + ']').val()
|
|
29
|
+
}
|
|
30
|
+
return inputs
|
|
31
|
+
}
|
package/lib/util/http.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Dependencies
|
|
2
|
+
const util = require('util')
|
|
3
|
+
let request = util.promisify(require('postman-request'))
|
|
4
|
+
|
|
5
|
+
// Includes
|
|
6
|
+
const options = require('../options.js')
|
|
7
|
+
const settings = require('../../settings.json')
|
|
8
|
+
const cache = require('../cache')
|
|
9
|
+
const getHash = require('./getHash.js').func
|
|
10
|
+
const { version } = require('../../package.json')
|
|
11
|
+
|
|
12
|
+
// Args
|
|
13
|
+
exports.required = ['url']
|
|
14
|
+
exports.optional = ['options', 'ignoreLoginError']
|
|
15
|
+
|
|
16
|
+
// Define
|
|
17
|
+
request = request.defaults({
|
|
18
|
+
forever: true,
|
|
19
|
+
agentOptions: {
|
|
20
|
+
maxSockets: Infinity
|
|
21
|
+
},
|
|
22
|
+
simple: false,
|
|
23
|
+
gzip: true,
|
|
24
|
+
timeout: settings.timeout,
|
|
25
|
+
headers: settings.use_noblox_ua
|
|
26
|
+
? {
|
|
27
|
+
'user-agent': `noblox-${version}`
|
|
28
|
+
}
|
|
29
|
+
: undefined
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// Docs
|
|
33
|
+
/**
|
|
34
|
+
* ✅ Send an http request to url with options.
|
|
35
|
+
* @category Utility
|
|
36
|
+
* @alias http
|
|
37
|
+
* @param {string} url - The url to request to.
|
|
38
|
+
* @param {object} options - The options to send with the request.
|
|
39
|
+
* @param {boolean} ignoreLoginError - If any login errors should be ignored.
|
|
40
|
+
* @returns {Promise<string>}
|
|
41
|
+
* @example const noblox = require("noblox.js")
|
|
42
|
+
* const body = await noblox.http("https://roblox.com/login", { method: "GET" })
|
|
43
|
+
**/
|
|
44
|
+
|
|
45
|
+
function http (url, opt) {
|
|
46
|
+
if (opt?.headers) {
|
|
47
|
+
opt.headers = Object.fromEntries(
|
|
48
|
+
Object.entries(opt.headers).map(([k, v]) => [k.toLowerCase(), v])
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
if (opt && !opt.jar && Object.keys(opt).indexOf('jar') > -1) {
|
|
52
|
+
opt.jar = options.jar
|
|
53
|
+
}
|
|
54
|
+
if (settings.session_only && opt && opt.jar) {
|
|
55
|
+
if (!opt.headers) {
|
|
56
|
+
opt.headers = {}
|
|
57
|
+
}
|
|
58
|
+
opt.headers.cookie = '.ROBLOSECURITY=' + opt.jar.session + ';'
|
|
59
|
+
opt.headers['x-api-key'] = opt.jar.apiKey
|
|
60
|
+
opt.jar = null
|
|
61
|
+
}
|
|
62
|
+
if (opt && opt.verification) {
|
|
63
|
+
if (!opt.headers) {
|
|
64
|
+
opt.headers = {}
|
|
65
|
+
}
|
|
66
|
+
const verify = '__RequestVerificationToken=' + opt.verification + ';'
|
|
67
|
+
if (opt.headers.cookie) {
|
|
68
|
+
opt.headers.cookie += verify
|
|
69
|
+
} else {
|
|
70
|
+
opt.headers.cookie = verify
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (url.indexOf('http') !== 0) {
|
|
74
|
+
url = 'https:' + url
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/*
|
|
78
|
+
In CI, actions does not allow us to use a static ip address (nor guarantees any particular location)
|
|
79
|
+
This is a problem as Roblox locks sessions to a particular region
|
|
80
|
+
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
|
|
81
|
+
The ID token is for Google Cloud and allows authenticating with the Cloud Run service that acts as our forwarder
|
|
82
|
+
Requests without auth headers do not go through the forwarder as they are unauthenticated and do not need forwarding
|
|
83
|
+
*/
|
|
84
|
+
if (process?.env.CI && process.env.FORWARDER_HOSTNAME && (opt.headers.cookie || opt.headers['x-api-key'])) {
|
|
85
|
+
const urlObj = new URL(url)
|
|
86
|
+
const { hostname } = urlObj
|
|
87
|
+
|
|
88
|
+
opt.headers['Destination-Host'] = hostname
|
|
89
|
+
opt.headers['x-serverless-authorization'] = `Bearer ${process.env.ID_TOKEN}`
|
|
90
|
+
urlObj.hostname = process.env.FORWARDER_HOSTNAME
|
|
91
|
+
url = urlObj.href
|
|
92
|
+
}
|
|
93
|
+
return request(url, opt)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
exports.func = function (args) {
|
|
97
|
+
const opt = args.options || {}
|
|
98
|
+
if (typeof opt.jar === 'string') {
|
|
99
|
+
opt.jar = { session: opt.jar }
|
|
100
|
+
}
|
|
101
|
+
const jar = opt.jar
|
|
102
|
+
const depth = args.depth || 0
|
|
103
|
+
const full = opt.resolveWithFullResponse || false
|
|
104
|
+
opt.resolveWithFullResponse = true
|
|
105
|
+
const follow = opt.followRedirect === undefined || opt.followRedirect
|
|
106
|
+
opt.followRedirect = function (res) {
|
|
107
|
+
if (!args.ignoreLoginError && res.headers.location && (res.headers.location.startsWith('https://www.roblox.com/newlogin') || res.headers.location.startsWith('/Login/Default.aspx'))) {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
return follow
|
|
111
|
+
}
|
|
112
|
+
return http(args.url, opt).then(function (res) {
|
|
113
|
+
if (res.statusCode === 403 && res.headers['x-csrf-token'] && Object.hasOwn(opt.headers ?? {}, 'x-csrf-token')) {
|
|
114
|
+
if (depth >= 2) {
|
|
115
|
+
throw new Error('Tried ' + (depth + 1) + ' times and could not refresh XCSRF token successfully')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const token = res.headers['x-csrf-token']
|
|
119
|
+
|
|
120
|
+
if (token) {
|
|
121
|
+
opt.headers['x-csrf-token'] = token
|
|
122
|
+
opt.jar = jar
|
|
123
|
+
args.depth = depth + 1
|
|
124
|
+
return exports.func(args)
|
|
125
|
+
} else {
|
|
126
|
+
throw new Error('Could not refresh X-CSRF-TOKEN')
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
if (depth > 0) {
|
|
130
|
+
cache.add(options.cache, 'XCSRF', getHash({ jar }), opt.headers['x-csrf-token'])
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (res.statusCode === 302 && !args.ignoreLoginError && res.headers.location && (res.headers.location.startsWith('https://www.roblox.com/newlogin') || res.headers.location.startsWith('/Login/Default.aspx'))) {
|
|
134
|
+
throw new Error('You are not logged in')
|
|
135
|
+
}
|
|
136
|
+
return full ? res : res.body
|
|
137
|
+
})
|
|
138
|
+
}
|
package/lib/util/jar.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Dependencies
|
|
2
|
+
const util = require('util')
|
|
3
|
+
const request = util.promisify(require('postman-request'))
|
|
4
|
+
|
|
5
|
+
// Includes
|
|
6
|
+
const settings = require('../../settings.json')
|
|
7
|
+
|
|
8
|
+
// Docs
|
|
9
|
+
/**
|
|
10
|
+
* ✅ Create a jar file based on sessionOnly.
|
|
11
|
+
* @category Utility
|
|
12
|
+
* @alias jar
|
|
13
|
+
* @param {boolean=} sessionOnly - The session to use to create the jar file.
|
|
14
|
+
* @returns {CookieJar}
|
|
15
|
+
* @example const noblox = require("noblox.js")
|
|
16
|
+
* const jar = noblox.jar()
|
|
17
|
+
**/
|
|
18
|
+
|
|
19
|
+
// Define
|
|
20
|
+
exports.func = function (sessionOnly) {
|
|
21
|
+
if (!sessionOnly) {
|
|
22
|
+
sessionOnly = settings.session_only
|
|
23
|
+
}
|
|
24
|
+
return (sessionOnly ? { session: '' } : request.jar())
|
|
25
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Includes
|
|
2
|
+
const options = require('../options.js')
|
|
3
|
+
const getGeneralToken = require('./getGeneralToken.js').func
|
|
4
|
+
const http = require('./http.js').func
|
|
5
|
+
// Args
|
|
6
|
+
exports.required = []
|
|
7
|
+
exports.optional = ['cookie']
|
|
8
|
+
|
|
9
|
+
// Docs
|
|
10
|
+
/**
|
|
11
|
+
* 🔐 Refreshes the stored cookie, stores it, and returns it.
|
|
12
|
+
* @category Utility
|
|
13
|
+
* @deprecated [Retrieving your .ROBLOSECURITY cookie in incognito mode should make a cookie that does not expire.]{@link https://noblox.js.org/tutorial-Authentication.html}
|
|
14
|
+
* @alias refreshCookie
|
|
15
|
+
* @param {string=} cookie - The cookie to refresh.
|
|
16
|
+
* @returns {Promise<string>}
|
|
17
|
+
* @example const noblox = require("noblox.js")
|
|
18
|
+
* const newCookie = await noblox.refreshCookie("COOKIEHERE")
|
|
19
|
+
**/
|
|
20
|
+
|
|
21
|
+
// Refreshes the internally stored cookie, or the cookie provided
|
|
22
|
+
// Stores the new cookie & returns it
|
|
23
|
+
function refreshCookie (cookie) {
|
|
24
|
+
if (cookie) {
|
|
25
|
+
options.jar.session = cookie
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return getGeneralToken({}).then((token) => {
|
|
29
|
+
return http({
|
|
30
|
+
url: 'https://www.roblox.com/authentication/signoutfromallsessionsandreauthenticate',
|
|
31
|
+
options: {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
resolveWithFullResponse: true,
|
|
34
|
+
jar: null,
|
|
35
|
+
headers: {
|
|
36
|
+
'X-CSRF-TOKEN': token
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}).then((res) => {
|
|
40
|
+
const cookies = res.headers['set-cookie']
|
|
41
|
+
if (cookies) {
|
|
42
|
+
const cookie = cookies.toString().match(/\.ROBLOSECURITY=(.*?);/)[1]
|
|
43
|
+
options.jar.session = cookie
|
|
44
|
+
return cookie
|
|
45
|
+
} else {
|
|
46
|
+
throw new Error('Failed to refresh cookie: None returned.')
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = refreshCookie
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Includes
|
|
2
|
+
const options = require('../options.js')
|
|
3
|
+
const getGeneralToken = require('./getGeneralToken.js').func
|
|
4
|
+
const getVerification = require('./getVerification.js').func
|
|
5
|
+
const getCurrentUser = require('./getCurrentUser.js').func
|
|
6
|
+
const http = require('./http.js').func
|
|
7
|
+
const cookieFile = './cookie'
|
|
8
|
+
const fs = require('fs')
|
|
9
|
+
// Args
|
|
10
|
+
exports.required = ['cookie']
|
|
11
|
+
exports.optional = []
|
|
12
|
+
|
|
13
|
+
const day = 86400000
|
|
14
|
+
|
|
15
|
+
// Define
|
|
16
|
+
const relog = (cookie) => {
|
|
17
|
+
if (!cookie) throw new Error('no cookie supplied?')
|
|
18
|
+
options.jar.session = cookie
|
|
19
|
+
return getVerification({ url: 'https://www.roblox.com/my/account#!/security' })
|
|
20
|
+
.then((ver) => {
|
|
21
|
+
if (!ver.header) console.log('Bad cookie.')
|
|
22
|
+
return getGeneralToken({}).then((token) => {
|
|
23
|
+
return http({
|
|
24
|
+
url: 'https://www.roblox.com/authentication/signoutfromallsessionsandreauthenticate',
|
|
25
|
+
options: {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
resolveWithFullResponse: true,
|
|
28
|
+
verification: ver.header,
|
|
29
|
+
jar: null,
|
|
30
|
+
headers: {
|
|
31
|
+
'X-CSRF-TOKEN': token
|
|
32
|
+
},
|
|
33
|
+
form: {
|
|
34
|
+
__RequestVerificationToken: ver.inputs.__RequestVerificationToken
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}).then((res) => {
|
|
38
|
+
const cookies = res.headers['set-cookie']
|
|
39
|
+
if (cookies) {
|
|
40
|
+
options.jar.session = cookies.toString().match(/\.ROBLOSECURITY=(.*?);/)[1]
|
|
41
|
+
|
|
42
|
+
fs.writeFile(cookieFile, JSON.stringify({ cookie: options.jar.session, time: Date.now() }), (err) => {
|
|
43
|
+
if (err) {
|
|
44
|
+
console.error('Failed to write cookie')
|
|
45
|
+
}
|
|
46
|
+
return true
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
module.exports = c
|
|
54
|
+
|
|
55
|
+
async function c (cookie) {
|
|
56
|
+
// Check for file
|
|
57
|
+
if (fs.existsSync(cookieFile)) {
|
|
58
|
+
const json = JSON.parse(fs.readFileSync(cookieFile))
|
|
59
|
+
|
|
60
|
+
// Check its new enough
|
|
61
|
+
if (json.time + day > Date.now()) {
|
|
62
|
+
// Its recent enough. Try it.
|
|
63
|
+
try {
|
|
64
|
+
await relog(json.cookie)
|
|
65
|
+
return getCurrentUser({})
|
|
66
|
+
} catch (e) {
|
|
67
|
+
console.log('Stored relog failed. Trying with given.')
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (cookie) {
|
|
72
|
+
// Try the user's cookie
|
|
73
|
+
try {
|
|
74
|
+
await relog(cookie)
|
|
75
|
+
return getCurrentUser({})
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error(e)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw new Error('No cookie supplied and no cookie file available.')
|
|
81
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const settings = require('../../settings.json')
|
|
2
|
+
|
|
3
|
+
// Docs
|
|
4
|
+
/**
|
|
5
|
+
* ✅ Updates library options. This allows you to modify settings such as time-out, or number of event retries without
|
|
6
|
+
* altering the settings.json file. Objects passed to this function should match the format of the settings.json file.
|
|
7
|
+
* Unknown keys, or malformed options will be rejected with an error.
|
|
8
|
+
* @category Utility
|
|
9
|
+
* @param {Partial<NobloxOptions>} newOptions - The new options to set, structured as per [settings.json](https://github.com/noblox/noblox.js/blob/master/settings.json)
|
|
10
|
+
* @returns void
|
|
11
|
+
* @see [settings.json](https://github.com/noblox/noblox.js/blob/master/settings.json) - default package settings
|
|
12
|
+
* @example const noblox = require("noblox.js")
|
|
13
|
+
* // This example overrides getPlayerThumbnail()'s response URL when a thumbnail is moderated.
|
|
14
|
+
* // You usually want to run this before logging in with your cookie.
|
|
15
|
+
* noblox.setOptions({
|
|
16
|
+
* thumbnail: {
|
|
17
|
+
* failedUrl: {
|
|
18
|
+
* blocked: "https://raw.githubusercontent.com/noblox/noblox.js/master/img/noblox-js.png"
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* })
|
|
22
|
+
*/
|
|
23
|
+
function setOptions (newOptions) {
|
|
24
|
+
return setOptionsLevel(settings, newOptions)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// This function allows key validation to be performed at different "levels" of nesting.
|
|
28
|
+
// Ensures the provided keys already exist, and discards invalid keys.
|
|
29
|
+
function setOptionsLevel (settingsLevel, inputObj) {
|
|
30
|
+
const keys = Object.keys(inputObj)
|
|
31
|
+
|
|
32
|
+
for (const key of keys) {
|
|
33
|
+
const newValue = inputObj[key]
|
|
34
|
+
const currentValue = settingsLevel[key]
|
|
35
|
+
|
|
36
|
+
if (currentValue !== undefined) {
|
|
37
|
+
if (typeof currentValue === 'object') {
|
|
38
|
+
if (typeof inputObj[key] !== 'object') {
|
|
39
|
+
throw new Error(`Tried to set options key ${key}, an object, to a non-object value: ${newValue}`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setOptionsLevel(currentValue, newValue)
|
|
43
|
+
} else {
|
|
44
|
+
// it's not undefined, and it's not a nested object - set the value.
|
|
45
|
+
settingsLevel[key] = newValue
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
// The key doesn't exist
|
|
49
|
+
throw new Error(`Tried to set option "${key}". This option does not exist, or has been nested incorrectly.`)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
exports.func = setOptions
|