AudioDownloadAPI/index.js

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)