392 lines
9.8 KiB
JavaScript
392 lines
9.8 KiB
JavaScript
require("dotenv").config()
|
|
const colors = require("colors")
|
|
const express = require("express")
|
|
const Discord = require("discord.js")
|
|
const discord = new Discord.Client({
|
|
intents: []
|
|
})
|
|
const {
|
|
REST,
|
|
Routes
|
|
} = require('discord.js');
|
|
const { title, send } = require("process");
|
|
const rest = new REST({
|
|
version: '10'
|
|
}).setToken(process.env.DISCORD_TOKEN);
|
|
const codes = require("./codes.json");
|
|
const app = express()
|
|
const fs = require("fs");
|
|
const port = process.env.SERVER_PORT || 3000;
|
|
app.use(express.json());
|
|
app.set("trust proxy", 1);
|
|
|
|
// if blacklist doesnt exist, make the file
|
|
if (!fs.existsSync("./blacklist.json")) {
|
|
fs.writeFileSync("./blacklist.json", "[]");
|
|
}
|
|
|
|
const bulk = async (assetIds, inputCookie) => {
|
|
// Impliment a rate limit of 25 requests per minute
|
|
if (!assetIds || !Array.isArray(assetIds)) {
|
|
return {
|
|
error: "Invalid data format",
|
|
status: "error"
|
|
};
|
|
}
|
|
|
|
// Build the batch request body
|
|
const batchRequests = assetIds.map(item => {
|
|
const assetId = item;
|
|
|
|
if (!assetId) {
|
|
return {
|
|
assetId,
|
|
requestId: assetId,
|
|
status: "failure",
|
|
additional: "Missing assetId."
|
|
};
|
|
}
|
|
|
|
return {
|
|
assetId,
|
|
requestId: assetId
|
|
};
|
|
});
|
|
|
|
// If body.cookie is provided, use it, else use process.env.COOKIE
|
|
const cookie = inputCookie || process.env.COOKIE;
|
|
|
|
const options = {
|
|
method: 'POST',
|
|
headers: {
|
|
authority: 'assetdelivery.roblox.com',
|
|
accept: '',
|
|
'accept-language': 'en-US,en;q=0.9',
|
|
'cache-control': 'no-cache',
|
|
'content-type': 'application/json',
|
|
origin: 'https://create.roblox.com',
|
|
pragma: 'no-cache',
|
|
referer: 'https://create.roblox.com/',
|
|
'roblox-browser-asset-request': 'true',
|
|
'roblox-place-id': '0', // Use a default or placeholder value if needed
|
|
'sec-ch-ua': '"Opera GX";v="105", "Chromium";v="119", "Not?A_Brand";v="24"',
|
|
'sec-ch-ua-mobile': '?0',
|
|
'sec-ch-ua-platform': '"Windows"',
|
|
'sec-fetch-dest': 'empty',
|
|
'sec-fetch-mode': 'cors',
|
|
'sec-fetch-site': 'same-site',
|
|
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0',
|
|
Cookie: `.ROBLOSECURITY=${cookie};`
|
|
},
|
|
body: JSON.stringify(batchRequests)
|
|
};
|
|
|
|
try {
|
|
let response = await fetch("https://assetdelivery.roblox.com/v1/assets/batch", options);
|
|
let json = await response.json();
|
|
//console.log(JSON.stringify(json, null, 2));
|
|
// Build the response object
|
|
const responses = assetIds.reduce((acc, item, index) => {
|
|
const assetId = item;
|
|
if (json[index].errors) {
|
|
const errorCode = json[index].errors[0].code;
|
|
acc[assetId] = {
|
|
status: "failure",
|
|
code: errorCode,
|
|
message: codes[errorCode].message,
|
|
additional: codes[errorCode].description
|
|
};
|
|
} else if (json[index].assetTypeId !== 3) {
|
|
// Return 415, the asset isnt audio
|
|
acc[assetId] = {
|
|
status: "failure",
|
|
code: 415,
|
|
message: codes[415].message,
|
|
additional: codes[415].description
|
|
};
|
|
} else {
|
|
acc[assetId] = {
|
|
status: "success",
|
|
url: json[index].location
|
|
};
|
|
}
|
|
return acc;
|
|
}, {});
|
|
|
|
return {
|
|
type: "batching-response",
|
|
data: responses
|
|
};
|
|
|
|
} catch (error) {
|
|
return {
|
|
type: "batching-response",
|
|
data: assetIds.reduce((acc, item) => {
|
|
acc[item] = {
|
|
status: "failure",
|
|
url: "",
|
|
additional: "Request failed"
|
|
};
|
|
return acc;
|
|
}, {})
|
|
};
|
|
}
|
|
}
|
|
|
|
app.use((req, res, next) => { // Blacklist handler
|
|
const blacklist = JSON.parse(fs.readFileSync("./blacklist.json", "utf-8"));
|
|
if (blacklist.includes(req.ip)) {
|
|
console.log(`[${new Date().toLocaleString()}] ${req.ip} ${req.method} ${req.url} - Blacklisted`);
|
|
return res.status(403).json({
|
|
code: 403,
|
|
message: codes[403].message,
|
|
additional: codes[403].description
|
|
});
|
|
}
|
|
console.log(`[${new Date().toLocaleString()}] ${req.ip} ${req.method} ${req.url}`);
|
|
next();
|
|
})
|
|
|
|
var rateLimits = {};
|
|
|
|
|
|
app.get("/", (req, res) => {
|
|
// load index
|
|
res.sendFile(__dirname + "/static/index.html")
|
|
});
|
|
|
|
|
|
app.post("/", async (req, res) => {
|
|
// Impliment a rate limit of 25 requests per minute
|
|
const ip = req.ip;
|
|
console.log(rateLimits[ip])
|
|
if (!rateLimits[ip]) {
|
|
rateLimits[ip] = {
|
|
count: 0,
|
|
lastRequest: Date.now()
|
|
};
|
|
}
|
|
const limit = rateLimits[ip];
|
|
const now = Date.now();
|
|
if (now - limit.lastRequest > 60000) {
|
|
limit.count = 0;
|
|
limit.lastRequest = now;
|
|
}
|
|
if (limit.count >= 25) {
|
|
return res.status(429).json({
|
|
code: 429,
|
|
error: "Rate limit exceeded",
|
|
status: "error"
|
|
});
|
|
}
|
|
limit.count++;
|
|
|
|
// Validate request body
|
|
if (!req.body.type || req.body.type !== "batching") {
|
|
return res.status(400).json({
|
|
error: "Invalid request type",
|
|
status: "error"
|
|
});
|
|
}
|
|
// Check that the body is valid json, if not throw an error
|
|
|
|
|
|
|
|
if (!req.body.data || !Array.isArray(req.body.data)) {
|
|
return res.status(400).json({
|
|
error: "Invalid data format",
|
|
status: "error"
|
|
});
|
|
}
|
|
|
|
// Build the batch request body
|
|
const batchRequests = req.body.data.map(item => {
|
|
const assetId = item;
|
|
|
|
if (!assetId) {
|
|
return {
|
|
assetId,
|
|
requestId: assetId,
|
|
status: "failure",
|
|
additional: "Missing assetId."
|
|
};
|
|
}
|
|
|
|
return {
|
|
assetId,
|
|
requestId: assetId
|
|
};
|
|
});
|
|
|
|
// If body.cookie is provided, use it, else use process.env.COOKIE
|
|
const cookie = req.body.cookie || process.env.COOKIE;
|
|
|
|
const options = {
|
|
method: 'POST',
|
|
headers: {
|
|
authority: 'assetdelivery.roblox.com',
|
|
accept: '',
|
|
'accept-language': 'en-US,en;q=0.9',
|
|
'cache-control': 'no-cache',
|
|
'content-type': 'application/json',
|
|
origin: 'https://create.roblox.com',
|
|
pragma: 'no-cache',
|
|
referer: 'https://create.roblox.com/',
|
|
'roblox-browser-asset-request': 'true',
|
|
'roblox-place-id': '0', // Use a default or placeholder value if needed
|
|
'sec-ch-ua': '"Opera GX";v="105", "Chromium";v="119", "Not?A_Brand";v="24"',
|
|
'sec-ch-ua-mobile': '?0',
|
|
'sec-ch-ua-platform': '"Windows"',
|
|
'sec-fetch-dest': 'empty',
|
|
'sec-fetch-mode': 'cors',
|
|
'sec-fetch-site': 'same-site',
|
|
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0',
|
|
Cookie: `.ROBLOSECURITY=${cookie};`
|
|
},
|
|
body: JSON.stringify(batchRequests)
|
|
};
|
|
|
|
try {
|
|
let response = await fetch("https://assetdelivery.roblox.com/v1/assets/batch", options);
|
|
let json = await response.json();
|
|
//console.log(JSON.stringify(json, null, 2));
|
|
// Build the response object
|
|
const responses = req.body.data.reduce((acc, item, index) => {
|
|
const assetId = item;
|
|
if (json[index].errors) {
|
|
const errorCode = json[index].errors[0].code;
|
|
acc[assetId] = {
|
|
status: "failure",
|
|
code: errorCode,
|
|
message: codes[errorCode].message,
|
|
additional: codes[errorCode].description
|
|
};
|
|
} else if (json[index].assetTypeId !== 3) {
|
|
// Return 415, the asset isnt audio
|
|
acc[assetId] = {
|
|
status: "failure",
|
|
code: 415,
|
|
message: codes[415].message,
|
|
additional: codes[415].description
|
|
};
|
|
} else {
|
|
acc[assetId] = {
|
|
status: "success",
|
|
url: json[index].location
|
|
};
|
|
}
|
|
return acc;
|
|
}, {});
|
|
|
|
res.json({
|
|
type: "batching-response",
|
|
data: responses
|
|
});
|
|
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
type: "batching-response",
|
|
data: req.body.data.reduce((acc, item) => {
|
|
acc[item] = {
|
|
status: "failure",
|
|
url: "",
|
|
additional: "Request failed"
|
|
};
|
|
return acc;
|
|
}, {})
|
|
});
|
|
}
|
|
});
|
|
|
|
discord.on("ready", async () => {
|
|
console.log(`${colors.cyan("[Discord]")} Logged in as ${discord.user.displayName}`);
|
|
|
|
const commands = require("./commands.json");
|
|
//Register commands
|
|
await (async () => {
|
|
try {
|
|
console.log(`${colors.cyan("[Discord]")} Registering Commands...`)
|
|
//Global
|
|
await rest.put(Routes.applicationCommands(discord.user.id), { body: commands })
|
|
console.log(`${colors.cyan("[Discord]")} Successfully registered commands.`);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
})();
|
|
|
|
app.listen(port, () => {
|
|
console.log(`${colors.cyan("[EXPRESS]")} Listening on ${port}`);
|
|
});
|
|
})
|
|
|
|
discord.on("interactionCreate", async (interaction) => {
|
|
// When someone runs the /getaudio command, respond with a modal asking for the asset id and optional cookie
|
|
switch (interaction.type) {
|
|
case Discord.InteractionType.ApplicationCommand:
|
|
await interaction.showModal({
|
|
//type: Discord.ComponentType.ActionRow,
|
|
title: "Get Audio",
|
|
custom_id: "getaudio",
|
|
components: [
|
|
{
|
|
type: Discord.ComponentType.ActionRow,
|
|
components: [
|
|
{
|
|
label: "Asset ID",
|
|
custom_id: "assetId",
|
|
type: Discord.ComponentType.TextInput,
|
|
style: Discord.TextInputStyle.Short,
|
|
required: true,
|
|
placeholder: "Asset ID",
|
|
maxLength: 15
|
|
}
|
|
]
|
|
},
|
|
{
|
|
type: Discord.ComponentType.ActionRow,
|
|
components: [
|
|
{
|
|
label: "Optional ROBLOSECURITY",
|
|
custom_id: "cookie",
|
|
type: Discord.ComponentType.TextInput,
|
|
style: Discord.TextInputStyle.Short,
|
|
required: false,
|
|
placeholder: "ROBLOSECURITY cookie",
|
|
maxLength: 100
|
|
}
|
|
]
|
|
}
|
|
]
|
|
})
|
|
break;
|
|
case Discord.InteractionType.ModalSubmit:
|
|
assetId = interaction.fields.getTextInputValue("assetId");
|
|
cookie = interaction.fields.getTextInputValue("cookie");
|
|
bulk([new String(new Number(assetId))], cookie ? cookie : null).then((data) => {
|
|
if (data.data[assetId].status == "failure") {
|
|
return interaction.reply({
|
|
embeds: [{
|
|
title: "Error",
|
|
color: 0xff0000,
|
|
description: `${data.data[assetId].message} - ${data.data[assetId].additional}`
|
|
}]
|
|
});
|
|
} else {
|
|
return interaction.reply({
|
|
files: [{
|
|
attachment: data.data[assetId].url,
|
|
name: `${assetId}.ogg`
|
|
}],
|
|
embeds: [{
|
|
title: "Success",
|
|
color: 0x00ff00,
|
|
description: `Downloaded audio file for asset ${assetId}`
|
|
}]
|
|
})
|
|
}
|
|
});
|
|
break;
|
|
}
|
|
});
|
|
|
|
discord.login(process.env.DISCORD_TOKEN) |