Working concept part 2

This commit is contained in:
Christopher Cookman 2026-06-13 23:05:06 -06:00
parent 21b9041351
commit 70978819ee
7 changed files with 338 additions and 73 deletions

193
bvs.js Normal file
View file

@ -0,0 +1,193 @@
async function getPremiumDIDs() {
return new Promise((resolve, reject) => {
fetch(`https://portal.bulkvs.com/api/v1.0/tnRecord?Status=Active&Trunk%20Group=${process.env.TRUNK_GROUP}`, {
headers: {
"Authorization": `Bearer ${process.env.BVS_TOKEN}`,
}
})
.then(res => {
if (!res.ok) {
throw new Error(`Error fetching DIDs: ${res.status} ${res.statusText}`);
}
return res.json();
})
.then(data => {
// data.forEach(record => {
// console.log(record.ReferenceID);
// });
// data is array of objects. If object.ReferenceID.split(";;")[0] is premDID, then [1] is the user id, and anything after is a note
const dids = data.filter(record => record.ReferenceID && record.ReferenceID.startsWith(";;")).map(record => {
// Get all flags (just keep looking at the next split item until its not a valid flag, after that, it's all a custom note, most likely to be used fo)
let validFlags = ["bypassNitroReq"];
let flags = [];
let note = "";
let splitReference = record.ReferenceID.split(";;");
for (let i = 1; i < splitReference.length; i++) {
if (validFlags.includes(splitReference[i])) {
flags.push(splitReference[i]);
}
}
return {
did: record.TN,
userId: record.ReferenceID.split(";;")[1],
flags: flags,
rawRef: record.ReferenceID
}
});
resolve(dids);
})
.catch(error => {
console.error("Error fetching DIDs:", error);
reject(error);
});
});
}
async function getAllDIDs() {
// Use the same BVS API endpoint, but just get all the results, not just premium ones.
return new Promise((resolve, reject) => {
fetch(`https://portal.bulkvs.com/api/v1.0/tnRecord?Status=Active&Trunk%20Group=${process.env.TRUNK_GROUP}`, {
headers: {
"Authorization": `Bearer ${process.env.BVS_TOKEN}`,
}
})
.then(res => {
if (!res.ok) {
throw new Error(`Error fetching DIDs: ${res.status} ${res.statusText}`);
}
return res.json();
})
.then(data => {
const dids = data.map(record => record.TN);
resolve(dids);
})
.catch(error => {
console.error("Error fetching DIDs:", error);
reject(error);
});
});
}
async function getAccountInfo() {
const res = await fetch("https://portal.bulkvs.com/api/v1.0/accountDetail", {
headers: {
"Authorization": `Bearer ${process.env.BVS_TOKEN}`,
}
});
if (!res.ok) {
throw new Error(`BVS_TOKEN is invalid: ${res.status} ${res.statusText}`);
}
return res.json();
}
async function searchDIDs(query) {
// Query will either be NPA, or NPANXX. Validate with regex, then use the API to find some.
// API Example: GET https://portal.bulkvs.com/api/v1.0/orderTn?Npa=310&Nxx=906&Lca=true&Limit=100
query = query.replace(/\D/g, ""); // Remove all non-digit characters
// Validate query w regex
if (!/^\d{3}$/.test(query) && !/^\d{6}$/.test(query)) {
throw new Error("Query must be either NPA (3 digits) or NPANXX (6 digits)");
}
const res = await fetch(`https://portal.bulkvs.com/api/v1.0/orderTn?Npa=${query.slice(0, 3)}&Nxx=${query.slice(3, 6)}&Lca=true&Limit=100`, {
headers: {
"Authorization": `Bearer ${process.env.BVS_TOKEN}`,
}
});
if (!res.ok) {
throw new Error(`Error searching DIDs: ${res.status} ${res.statusText}`);
}
return res.json();
}
async function searchPurchasableDIDs(query) {
// Use searchDIDs, then filter down to 6 random from the results where the Nrc value == "0.05" and Mrc == "0.06"
const dids = await searchDIDs(query).catch(error => {
console.error("Error searching DIDs:", error);
return [];
});
// console.log(dids)
const purchasableDIDs = dids.filter(did => did.Nrc <= "0.50" && did.Mrc === "0.06");
// console.log(purchasableDIDs)
// Shuffle the purchasableDIDs array, then take the first 6
let shuffled = [...purchasableDIDs];
for (let i = 0; i < 5; i++) {
shuffled = shuffled.sort(() => 0.5 - Math.random());
}
// shuffle more
return shuffled.slice(0, 6);
}
async function purchaseDID(did, userId) {
// POST /orderTn with body:
/*
{
"TN": "did to purchase",
"Lidb": "LITENET",
"Portout Pin": `${random 12 digit number}`,
"ReferenceID": `;;${userId}`,
"Trunk Group": `${process.env.TRUNK_GROUP}`,
"Sms": false,
"Mms": false,
"Webhook": "Default"
}
*/
let portoutPin = "";
const digits = "0123456789";
for (let i = 0; i < 12; i++) {
portoutPin += digits[Math.floor(Math.random() * digits.length)];
}
const res = await fetch("https://portal.bulkvs.com/api/v1.0/orderTn", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.BVS_TOKEN}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"TN": did,
"Lidb": "LITENET",
"Portout Pin": portoutPin,
"ReferenceID": `;;${userId}`,
"Trunk Group": `${process.env.TRUNK_GROUP}`,
"Sms": false,
"Mms": false,
"Webhook": "Default"
})
});
// If response is 404, check body.json().Description, return the json body.
if (res.status === 404) {
const errorData = await res.json();
throw new Error(`Error purchasing DID: ${res.status} ${res.statusText} - ${errorData.Description}`);
}
if (!res.ok) {
throw new Error(`Error purchasing DID: ${res.status} ${res.statusText}`);
}
return res.json();
}
function formatPhoneNumber(input) {
if (/^\d{10}$/.test(input)) {
input = `1${input}`;
}
// Regex to make sure it's a valid 11 digit NA phone number.
if (!/^\d{11}$/.test(input)) {
throw new Error("Phone number must be 10 or 11 digits, including country code (1 for US/Canada).");
}
// Format to human readable format: +1 (310) 906-1234
return `+${input[0]} (${input.slice(1, 4)}) ${input.slice(4, 7)}-${input.slice(7)}`;
}
module.exports = {
getAllDIDs,
getAccountInfo,
getPremiumDIDs,
searchDIDs,
searchPurchasableDIDs,
purchaseDID,
formatPhoneNumber
}

40
bvsTest.js Normal file
View file

@ -0,0 +1,40 @@
require("dotenv").config({quiet: true});
const bvs = require("./bvs");
// bvs.getAccountInfo().then(accountInfo => {
// console.log(`Validated BulkVS Token. Account Contact is ${accountInfo["Main Contact"].Name}`)
// }).catch(error => {
// console.error("Error validating BVS_TOKEN:", error);
// process.exit(1);
// })
// bvs.getAllDIDs().then(dids => {
// console.log(`Fetched ${dids.length} DIDs from BulkVS.`);
// console.log(dids);
// }).catch(error => {
// console.error("Error fetching DIDs:", error);
// });
bvs.getPremiumDIDs().then(dids => {
console.log(`Fetched ${dids.length} premium DIDs from BulkVS.`);
console.log(dids);
}).catch(error => {
console.error("Error fetching premium DIDs:", error);
});
bvs.searchPurchasableDIDs("910").then(dids => {
console.log(`Fetched ${dids.length} DIDs from BulkVS with user ID 610548.`);
console.log(dids.map(did => did.TN))
// console.log(dids)
// Test purchase the first one, if there is one
if (dids.length > 0) {
// bvs.purchaseDID(dids[0].TN, 8949849456165).then((data) => {
// console.log(`Successfully purchased DID ${dids[0].TN}`);
// console.log(data)
// }).catch(error => {
// console.error(`Error purchasing DID ${dids[0].TN}:`, error);
// });
}
}).catch(error => {
console.error("Error fetching DIDs with user ID 610548:", error);
});

View file

@ -2,15 +2,37 @@ const Discord = require('discord.js');
module.exports = [ module.exports = [
{ {
name: 'checkmember', name: 'mynumber',
description: 'Check if a member is boosting the guild.', description: 'Get your associated DID, if you have one.',
options: [
{
name: 'user',
description: 'The user to check. If not provided, checks the user who ran the command.',
type: Discord.ApplicationCommandOptionType.User, // USER type
required: false,
}
]
}, },
{
name: 'searchnumbers',
description: 'Search for DIDs, and purchase one if you have an active server boost.',
options: [
{
name: 'area_code',
description: 'The area code to search for (e.g. 610).',
type: Discord.ApplicationCommandOptionType.Number,
required: true
},
{
name: 'office_code',
description: 'The office code to search for (e.g. 548).',
type: Discord.ApplicationCommandOptionType.Number,
required: false
}
]
},
{
name: 'getnumber',
description: 'Purchase a DID from the latest search results of /searchnumbers.',
options: [
{
name: 'choice',
description: 'The choice of DID to purchase from the latest search results.',
type: Discord.ApplicationCommandOptionType.Number,
required: true
}
]
}
] ]

View file

@ -7,49 +7,12 @@ const client = new Discord.Client({
] ]
}) })
const bvs = require("./bvs");
const { REST, Routes } = require("discord.js"); const { REST, Routes } = require("discord.js");
const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN); const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN);
async function getAllDIDs() {
return new Promise((resolve, reject) => {
fetch(`https://portal.bulkvs.com/api/v1.0/tnRecord?Status=Active&Trunk%20Group=${process.env.TRUNK_GROUP}`, {
headers: {
"Authorization": `Bearer ${process.env.BVS_TOKEN}`,
}
})
.then(res => {
if (!res.ok) {
throw new Error(`Error fetching DIDs: ${res.status} ${res.statusText}`);
}
return res.json();
})
.then(data => {
data.forEach(record => {
console.log(record.ReferenceID);
});
// data is array of objects. If object.ReferenceID.split(";;")[0] is premDID, then [1] is the user id, and anything after is a note
const dids = data.filter(record => record.ReferenceID && record.ReferenceID.startsWith(";;")).map(record => {
// Get all flags (just keep looking at the next split item until its not a valid flag, after that, it's all a custom note, most likely to be used fo)
return {
did: record.TN,
userId: record.ReferenceID.split(";;")[1],
}
});
resolve(dids);
})
.catch(error => {
console.error("Error fetching DIDs:", error);
reject(error);
});
});
}
getAllDIDs().then(dids => {
console.log(`Fetched ${dids.length} DIDs from BulkVS.`);
console.log(dids)
}).catch(error => {
console.error("Error fetching DIDs:", error);
});
client.on("clientReady", () => { client.on("clientReady", () => {
console.log(`Logged in as ${client.user.tag}!`); console.log(`Logged in as ${client.user.tag}!`);
@ -69,28 +32,6 @@ client.on("clientReady", () => {
rest.put(Routes.applicationGuildCommands(client.user.id, process.env.HOME_GUILD), { body: commands }) rest.put(Routes.applicationGuildCommands(client.user.id, process.env.HOME_GUILD), { body: commands })
.then(() => console.log("Successfully registered application commands.")) .then(() => console.log("Successfully registered application commands."))
.catch(console.error); .catch(console.error);
// Check that we have a valid BVS_TOKEN with https://portal.bulkvs.com/api/v1.0/accountInfo (Bearer token in Authorization header)
if (!process.env.BVS_TOKEN) {
console.error("BVS_TOKEN environment variable is not set. Please set it to a valid token from https://portal.bulkvs.com/api/v1.0/accountInfo (Bearer token in Authorization header).");
process.exit(1);
}
fetch("https://portal.bulkvs.com/api/v1.0/accountDetail", {
headers: {
"Authorization": `Bearer ${process.env.BVS_TOKEN}`,
}
}).then(res => {
if (!res.ok) {
throw new Error(`BVS_TOKEN is invalid: ${res.status} ${res.statusText}`);
}
// Get raw text
return res.json();
}).then(data => {
console.log(`Validated BulkVS Token. Account Contact is ${data["Main Contact"].Name}`)
}).catch(error => {
console.error("Error validating BVS_TOKEN:", error);
process.exit(1);
});
}); });
client.on('interactionCreate', async interaction => { client.on('interactionCreate', async interaction => {
@ -98,7 +39,7 @@ client.on('interactionCreate', async interaction => {
if (interaction.isChatInputCommand()) { if (interaction.isChatInputCommand()) {
try { try {
const handler = require(`./interactions/chatCommand/${interaction.commandName}.js`); const handler = require(`./interactions/chatCommand/${interaction.commandName}.js`);
await handler(interaction, client); await handler(interaction, client, bvs);
} catch (error) { } catch (error) {
console.error(`Error handling interaction ${interaction.id}:`, error); console.error(`Error handling interaction ${interaction.id}:`, error);
} }

View file

@ -0,0 +1,21 @@
module.exports = (interaction, client, bvs) => {
const choice = interaction.options.getNumber('choice');
if (!choice) {
return interaction.reply({ content: `You must provide a choice from the latest run of \`/searchnumbers\`. Use \`/searchnumbers\` to see available DIDs and their corresponding choices.`, ephemeral: true });
}
if (!global.tempPurchasableDIDs || !global.tempPurchasableDIDs[interaction.user.id]) {
return interaction.reply({ content: `You must provide a choice from the latest run of \`/searchnumbers\`. Use \`/searchnumbers\` to see available DIDs and their corresponding choices.`, ephemeral: true });
}
const dids = global.tempPurchasableDIDs[interaction.user.id];
const index = parseInt(choice) - 1;
if (isNaN(index) || index < 0 || index >= dids.length) {
return interaction.reply({ content: `Invalid choice. You must provide a choice from the latest run of \`/searchnumbers\`. Use \`/searchnumbers\` to see available DIDs and their corresponding choices.`, ephemeral: true });
}
const didToPurchase = dids[index];
return interaction.reply({ content: `Attempting to purchase DID \`${bvs.formatPhoneNumber(didToPurchase)}\`...`, ephemeral: true }).then(() => {
// wait, we are in dev phase, dont buy lol
});
}

View file

@ -0,0 +1,23 @@
module.exports = (interaction, client, bvs) => {
const member = interaction.guild.members.cache.get(interaction.user.id);
if (!member) {
interaction.reply({ content: `Could not find member with ID ${user.id}.`, ephemeral: true });
return;
}
bvs.getPremiumDIDs().then(dids => {
const userDIDs = dids.filter(did => did.userId === interaction.user.id);
if (userDIDs.length === 0) {
if (member.premiumSince) {
interaction.reply({ content: `You don't have a DID associated with your account. But with your currently active server boost, you can get one with \`/getnumber\`!`, ephemeral: true });
} else {
interaction.reply({ content: `You don't have a DID associated with your account. Boost the server to request one!`, ephemeral: true });
}
} else {
interaction.reply({ content: `Your DID is \`${bvs.formatPhoneNumber(userDIDs[0].did)}\``, ephemeral: true });
}
}).catch(error => {
console.error("Error fetching premium DIDs:", error);
interaction.reply({ content: `There was an error fetching your DID. Please try again later.`, ephemeral: true });
});
}

View file

@ -0,0 +1,25 @@
module.exports = (interaction, client, bvs) => {
const areaCode = interaction.options.getNumber('area_code');
const officeCode = interaction.options.getNumber('office_code');
// Validate input
if (!/^\d{3}$/.test(areaCode)) {
return interaction.reply({ content: `Area code must be 3 digits.`, ephemeral: true });
}
if (officeCode && !/^\d{3}$/.test(officeCode)) {
return interaction.reply({ content: `Office code must be 3 digits.`, ephemeral: true });
}
bvs.searchPurchasableDIDs(areaCode + (officeCode || "")).then(dids => {
if (dids.length === 0) {
return interaction.reply({ content: `No results for search query. Try again.`, ephemeral: true });
}
console.log(dids)
interaction.reply({ content: `Found the following DIDs\n\`\`\`\n${dids.map((did, index) => `${index + 1}. ${bvs.formatPhoneNumber(did.TN)}`).join("\n")}\n\`\`\`\nUse \`/getnumber <choice>\` to purchase one of these DIDs! (Choice is one of the numbers listed above by index, not phone number)`, ephemeral: true });
// Store the DIDs in a global temp variable with the user id so the user can purchase by 1, 2, 3, etc. in the /getnumber command. This is a bit janky but it works for now.
global.tempPurchasableDIDs = global.tempPurchasableDIDs || {};
global.tempPurchasableDIDs[interaction.user.id] = dids.map(did => did.TN);
console.log(global.tempPurchasableDIDs)
}).catch(error => {
console.error("Error searching DIDs:", error);
interaction.reply({ content: `There was an error searching for DIDs. Please try again later.`, ephemeral: true });
});
}