Bot checks inventory when taking/submitting
This commit is contained in:
parent
10d93041fc
commit
e8c41bb821
@ -3,7 +3,7 @@ const { parse } = require("csv-parse/sync");
|
|||||||
const fs = require('node:fs');
|
const fs = require('node:fs');
|
||||||
const noblox = require("noblox.js");
|
const noblox = require("noblox.js");
|
||||||
const axios = require("axios").default;
|
const axios = require("axios").default;
|
||||||
const { submissions, commands } = require("../config/config.js");
|
const { submissions, commands, cookies } = require("../config/config.js");
|
||||||
|
|
||||||
async function robloxUserFromDiscord(id) {
|
async function robloxUserFromDiscord(id) {
|
||||||
if (isNaN(id)) return undefined;
|
if (isNaN(id)) return undefined;
|
||||||
@ -45,8 +45,17 @@ async function execute(interaction) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const id = interaction.options.getInteger("asset_id");
|
const id = interaction.options.getInteger("asset_id");
|
||||||
|
await noblox.setCookie(cookies[game]);
|
||||||
|
|
||||||
|
// Check that the bot owns this model
|
||||||
|
if (!(await noblox.getOwnership((await noblox.getCurrentUser()).UserID, id, "Asset"))) {
|
||||||
|
const msg = `The ${game} maptest bot's inventory does not contain this asset (id: ${id}). You must use the /take command first.`;
|
||||||
|
await interaction.reply({content: msg, ephemeral: true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const info = await getProductInfo(id);
|
const info = await noblox.getProductInfo(id);
|
||||||
if (info.AssetTypeId != 10) {
|
if (info.AssetTypeId != 10) {
|
||||||
await interaction.reply({content: `(id: ${id}) is not a valid model ID.`, ephemeral: true});
|
await interaction.reply({content: `(id: ${id}) is not a valid model ID.`, ephemeral: true});
|
||||||
return;
|
return;
|
||||||
@ -54,11 +63,16 @@ async function execute(interaction) {
|
|||||||
if (info.Creator.Id != userId) {
|
if (info.Creator.Id != userId) {
|
||||||
const assetUsername = await robloxUsernameFromId(info.Creator.Id);
|
const assetUsername = await robloxUsernameFromId(info.Creator.Id);
|
||||||
const interactionUsername = await robloxUsernameFromId(userId);
|
const interactionUsername = await robloxUsernameFromId(userId);
|
||||||
const msg = `The account linked to your Discord (${interactionUsername}) is not the owner of this model (${assetUsername}), so you cannot submit it.`
|
const msg = `The account linked to your Discord (${interactionUsername}) is not the owner of this model (${assetUsername}), so you cannot submit it.`;
|
||||||
await interaction.reply({content: msg, ephemeral: true});
|
await interaction.reply({content: msg, ephemeral: true});
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Roblox only lets you call the product info API like once per 30 seconds for some reason...
|
||||||
|
if (error.message.startsWith("429")) {
|
||||||
|
await interaction.reply({content: `The maptest bot is being rate-limited by Roblox, please wait a minute before doing this command again.`, ephemeral: true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log(error);
|
console.log(error);
|
||||||
await interaction.reply({content: `There is a problem with this asset ID (id: ${id}).`, ephemeral: true});
|
await interaction.reply({content: `There is a problem with this asset ID (id: ${id}).`, ephemeral: true});
|
||||||
return;
|
return;
|
||||||
@ -84,7 +98,6 @@ async function execute(interaction) {
|
|||||||
fs.writeFileSync(fname, s);
|
fs.writeFileSync(fname, s);
|
||||||
|
|
||||||
await interaction.reply(`Map (id: ${id}) successfully submitted.`);
|
await interaction.reply(`Map (id: ${id}) successfully submitted.`);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -102,37 +115,4 @@ module.exports = {
|
|||||||
.setRequired(true))
|
.setRequired(true))
|
||||||
,
|
,
|
||||||
execute
|
execute
|
||||||
};
|
};
|
||||||
|
|
||||||
function getProductInfo (asset) {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
const httpOpt = {
|
|
||||||
url: `//economy.roblox.com/v2/assets/${asset}/details`,
|
|
||||||
options: {
|
|
||||||
resolveWithFullResponse: true,
|
|
||||||
method: 'GET'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await noblox.http(httpOpt);
|
|
||||||
if (res.statusCode === 200) {
|
|
||||||
resolve(JSON.parse(res.body));
|
|
||||||
} else {
|
|
||||||
// Sourced from: https://stackoverflow.com/a/32278428
|
|
||||||
const isAnObject = (val_1) => !!(val_1 instanceof Array || val_1 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}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return reject(error);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
181
commands/take.js
181
commands/take.js
@ -9,24 +9,37 @@ async function execute(interaction) {
|
|||||||
await interaction.reply({content: "Invalid game specified!", ephemeral: true});
|
await interaction.reply({content: "Invalid game specified!", ephemeral: true});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const id = interaction.options.getInteger("asset_id");
|
const id = interaction.options.getInteger("asset_id");
|
||||||
await noblox.setCookie(cookie).then(async () => {
|
await noblox.setCookie(cookie);
|
||||||
let info;
|
// Check that the bot doesn't already own this asset
|
||||||
// validate that this is a model
|
if (await noblox.getOwnership(await noblox.getCurrentUser("UserID"), id, "Asset")) {
|
||||||
try {
|
const msg = `The ${game} maptest bot already has this model (id: ${id})`;
|
||||||
info = await getProductInfo(id);
|
await interaction.reply({content: msg, ephemeral: true});
|
||||||
if (info.AssetTypeId != 10) {
|
return;
|
||||||
await interaction.reply({content: `(id: ${id}) is not a valid model ID.`, ephemeral: true});
|
}
|
||||||
return;
|
let info;
|
||||||
}
|
// Validate that this is a model
|
||||||
} catch (error) {
|
try {
|
||||||
console.log(error);
|
info = await noblox.getProductInfo(id);
|
||||||
await interaction.reply({content: `There is a problem with this asset ID (id: ${id}).`, ephemeral: true});
|
if (info.AssetTypeId != 10) {
|
||||||
|
await interaction.reply({content: `(id: ${id}) is not a valid model ID.`, ephemeral: true});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Roblox only lets you call the product info API like once per 30 seconds for some reason...
|
||||||
|
if (error.message.startsWith("429")) {
|
||||||
|
await interaction.reply({content: `The maptest bot is being rate-limited by Roblox, please wait a minute before doing this command again.`, ephemeral: true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log(error);
|
||||||
|
await interaction.reply({content: `There is a problem with this asset ID (id: ${id}).`, ephemeral: true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
buy(undefined, await noblox.getGeneralToken(), info, 0).then(async () => {
|
try {
|
||||||
await interaction.reply(
|
await buy(info);
|
||||||
|
await interaction.reply(
|
||||||
`
|
`
|
||||||
Now that your map (id: ${id}) has been taken by the ${game} maptest bot you can load it into the ${game} maptest place. To load your map, join the game and say
|
Now that your map (id: ${id}) has been taken by the ${game} maptest bot you can load it into the ${game} maptest place. To load your map, join the game and say
|
||||||
\`\`\`
|
\`\`\`
|
||||||
@ -34,18 +47,49 @@ Now that your map (id: ${id}) has been taken by the ${game} maptest bot you can
|
|||||||
\`\`\`Read what it says. If your map successfully loaded type !rtv and then choose your map.
|
\`\`\`Read what it says. If your map successfully loaded type !rtv and then choose your map.
|
||||||
If it did not load successfully, you can expand the chat to view the full error message by clicking and dragging on the edge of the chat.
|
If it did not load successfully, you can expand the chat to view the full error message by clicking and dragging on the edge of the chat.
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
})
|
} catch (error) {
|
||||||
.catch(async (error) => {
|
if (error.message == "You already own this item.") {
|
||||||
if (error.message == "You already own this item.") {
|
await interaction.reply({content: "The bot has already taken this model!", ephemeral: true});
|
||||||
await interaction.reply({content: "The bot has already taken this model!", ephemeral: true});
|
} else {
|
||||||
} else {
|
await interaction.reply({content: `An error occured trying to take the model (id: ${id}). Make sure it is uncopylocked!`, ephemeral: true});
|
||||||
await interaction.reply({content: `An error occured trying to take the model (id: ${id}). Make sure it is uncopylocked!`, ephemeral: true});
|
console.log(`Could not take asset ID ${id}: `);
|
||||||
console.log(`Could not take asset ID ${id}: `);
|
console.log(error);
|
||||||
console.log(error);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function buy (product) {
|
||||||
|
const productId = product.ProductId;
|
||||||
|
const httpOpt = {
|
||||||
|
url: '//economy.roblox.com/v1/purchases/products/' + productId,
|
||||||
|
options: {
|
||||||
|
method: 'POST',
|
||||||
|
jar: undefined,
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': await noblox.getGeneralToken()
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
expectedCurrency: 1,
|
||||||
|
expectedPrice: 0,
|
||||||
|
expectedSellerId: product.Creator.Id
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const json = await noblox.http(httpOpt);
|
||||||
|
console.log(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;
|
||||||
|
} else {
|
||||||
|
throw new Error(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -63,89 +107,4 @@ module.exports = {
|
|||||||
.setRequired(true))
|
.setRequired(true))
|
||||||
,
|
,
|
||||||
execute
|
execute
|
||||||
};
|
};
|
||||||
|
|
||||||
async 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: jar,
|
|
||||||
headers: {
|
|
||||||
'X-CSRF-TOKEN': token
|
|
||||||
},
|
|
||||||
json: {
|
|
||||||
expectedCurrency: 1,
|
|
||||||
expectedPrice: robux,
|
|
||||||
expectedSellerId: product.Creator.Id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const json = await noblox.http(httpOpt);
|
|
||||||
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 getProductInfo (asset) {
|
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
const httpOpt = {
|
|
||||||
url: `//economy.roblox.com/v2/assets/${asset}/details`,
|
|
||||||
options: {
|
|
||||||
resolveWithFullResponse: true,
|
|
||||||
method: 'GET'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await noblox.http(httpOpt);
|
|
||||||
if (res.statusCode === 200) {
|
|
||||||
resolve(JSON.parse(res.body));
|
|
||||||
} else {
|
|
||||||
// Sourced from: https://stackoverflow.com/a/32278428
|
|
||||||
const isAnObject = (val_1) => !!(val_1 instanceof Array || val_1 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}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return reject(error);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -6,7 +6,7 @@
|
|||||||
"csv-parse": "^5.0.4",
|
"csv-parse": "^5.0.4",
|
||||||
"discord-api-types": "^0.32.1",
|
"discord-api-types": "^0.32.1",
|
||||||
"discord.js": "^13.6.0",
|
"discord.js": "^13.6.0",
|
||||||
"noblox.js": "^4.13.1",
|
"noblox.js": "^4.15.1",
|
||||||
"node-csv": "^0.1.2"
|
"node-csv": "^0.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user