Skip to content

Generation command & refactors #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
"axios": "1.9.0",
"discord.js": "14.19.3",
"dns-query": "0.11.2",
"dotenv": "16.5.0"
"dotenv": "16.5.0",
"ms": "^2.1.3"
},
"devDependencies": {
"@types/ms": "^2.1.0",
"@types/node": "22.15.24",
"typescript": "5.8.3"
}
Expand Down
4 changes: 2 additions & 2 deletions src/commands/is-a-dev/available-single-letters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import Command from "../../classes/Command";
import ExtendedClient from "../../classes/ExtendedClient";
import { ChatInputCommandInteraction, ColorResolvable } from "discord.js";

import axios from "axios";
import { emojis as emoji } from "../../../config.json";
import { fetchDomains } from "../../util/functions";

const command: Command = {
name: "available-single-letters",
Expand All @@ -23,7 +23,7 @@ const command: Command = {
try {
const subdomains = "abcdefghijklmnopqrstuvwxyz0123456789".split("");

const res = (await axios.get("https://raw.is-a.dev/v2.json")).data;
const res = await fetchDomains();

const taken = res.map((entry: any) => entry.subdomain);
const available = subdomains.filter((subdomain) => !taken.includes(subdomain));
Expand Down
4 changes: 2 additions & 2 deletions src/commands/is-a-dev/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import Command from "../../classes/Command";
import ExtendedClient from "../../classes/ExtendedClient";
import { ChatInputCommandInteraction, ColorResolvable } from "discord.js";

import axios from "axios";
import { emojis as emoji } from "../../../config.json";
import { fetchDomains } from "../../util/functions";

const command: Command = {
name: "check",
Expand Down Expand Up @@ -43,7 +43,7 @@ const command: Command = {
return;
}

const res = (await axios.get("https://raw.is-a.dev/v2.json")).data;
const res = await fetchDomains();
const data = res.find((entry: any) => entry.subdomain === subdomain);

if (!data) {
Expand Down
240 changes: 240 additions & 0 deletions src/commands/is-a-dev/generate-domain-json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import Command from "../../classes/Command";
import ExtendedClient from "../../classes/ExtendedClient";
import {
ActionRowBuilder,
ButtonBuilder,
ButtonInteraction,
ButtonStyle,
ChatInputCommandInteraction,
ColorResolvable,
ModalBuilder,
TextInputBuilder,
TextInputStyle,
} from "discord.js";

import { emojis as emoji } from "../../../config.json";
import ms from "ms";
import { fetchDomains, filterObject } from "../../util/functions";

const command: Command = {
name: "generate-domain-json",
description: "Generate a subdomain's JSON file.",
options: [
{
type: 3,
name: "subdomain",
description: "The subdomain you want to generate the JSON for.",
max_length: 253 - ".is-a.dev".length,
required: true,
},
],
botPermissions: [],
requiredRoles: [],
cooldown: 5,
enabled: true,
deferReply: true,
ephemeral: false,
async execute(
interaction: ChatInputCommandInteraction,
client: ExtendedClient,
Discord: typeof import("discord.js")
) {
try {
const subdomain = interaction.options.get("subdomain").value as string;

const hostnameRegex =
/^(?=.{1,253}$)(?:(?:[_a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)\.)+[a-zA-Z]{2,63}$/;

if (!hostnameRegex.test(`${subdomain}.is-a.dev`)) {
const invalidSubdomain = new Discord.EmbedBuilder()
.setColor(client.config.embeds.error as ColorResolvable)
.setDescription(
`${emoji.cross} \`${subdomain}\` is not a valid subdomain.`
);

await interaction.editReply({ embeds: [invalidSubdomain] });
return;
}

let domains = await fetchDomains();
const data = domains.find((v: any) => v.domain === `${subdomain}.is-a.dev`);

if (data) {
const noResult = new Discord.EmbedBuilder()
.setColor(client.config.embeds.error as ColorResolvable)
.setDescription(
`${emoji.cross} \`${subdomain}.is-a.dev\` exists already.`
);

await interaction.editReply({ embeds: [noResult] });
return;
}

if (data && (data.internal || data.reserved)) {
const internalError = new Discord.EmbedBuilder()
.setColor(client.config.embeds.error as ColorResolvable)
.setDescription(
`${emoji.cross} \`${subdomain}.is-a.dev\` is an internal or reserved subdomain and cannot be accessed.`
);

await interaction.editReply({ embeds: [internalError] });
return;
}

const embed = new Discord.EmbedBuilder()
.setColor(client.config.embeds.default as ColorResolvable)
.setTitle("Domain JSON Generation")
.setDescription(
`Generate your .is-a.dev domain JSON by hitting the button bellow.`
);

const endEmbed = new Discord.EmbedBuilder()
.setColor(client.config.embeds.error as ColorResolvable)
.setDescription(`${emoji.cross} Ended Generation`);

const actions = new ActionRowBuilder<ButtonBuilder>().setComponents(
new ButtonBuilder()
.setCustomId("start-generation")
.setStyle(ButtonStyle.Primary)
.setLabel("Generate domain JSON")
);

const records = [
new TextInputBuilder()
.setCustomId("cname-record")
.setLabel("CNAME Record")
.setStyle(TextInputStyle.Short)
.setPlaceholder("Place your CNAME Record here")
.setRequired(false),
new TextInputBuilder()
.setCustomId("a-records")
.setLabel("A Records")
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder("A Records are split into an array by commas")
.setRequired(false),
];

const records2 = [
new TextInputBuilder()
.setCustomId("txt-record")
.setLabel("TXT Record")
.setStyle(TextInputStyle.Short)
.setPlaceholder("Place your TXT Record here")
.setRequired(false),
new TextInputBuilder()
.setCustomId("mx-record")
.setLabel("MX Records")
.setStyle(TextInputStyle.Paragraph)
.setPlaceholder("MX Records are split into an array by commas")
.setRequired(false),
];

const userInfo = [
new TextInputBuilder()
.setCustomId("username")
.setLabel("Github Username")
.setStyle(TextInputStyle.Short)
.setPlaceholder("Place your github username here")
.setRequired(true),
];

// Each TextInput needs its own ActionRow, or discord will cause an error
const modalActionRows = [
new ActionRowBuilder<TextInputBuilder>().addComponents(userInfo[0]),
new ActionRowBuilder<TextInputBuilder>().addComponents(records[0]),
new ActionRowBuilder<TextInputBuilder>().addComponents(records[1]),
new ActionRowBuilder<TextInputBuilder>().addComponents(records2[0]),
new ActionRowBuilder<TextInputBuilder>().addComponents(records2[1]),
];

const modal = new ModalBuilder()
.setCustomId("generation-modal")
.setTitle(".is-a.dev JSON Domain")
.addComponents(...modalActionRows);

const sentMessage = await interaction.editReply({
embeds: [embed],
components: [actions],
});

const componentCollector = sentMessage.createMessageComponentCollector({
time: ms("10m"),
});

componentCollector.on(
"collect",
async (v: ButtonInteraction<"cached">) => {
if (v.user.id !== interaction.user.id) return; // Disallow any other users from disrupting the generation
if (v.customId === "start-generation") {
await v.showModal(modal);

const modalResponse = await v.awaitModalSubmit({ time: ms("10m") });
await modalResponse.reply({
content: "Generated",
flags: Discord.MessageFlags.Ephemeral,
});
const aRecords =
modalResponse.fields.getTextInputValue("a-records");

const mxRecords = modalResponse.fields.getTextInputValue("mx-record");

const { A, cname, txt, mx, github, discord } = {
cname: modalResponse.fields.getTextInputValue("cname-record"),
A: aRecords.length ? aRecords.split(",") : [],
txt: modalResponse.fields.getTextInputValue("txt-record"),
mx: mxRecords.length ? aRecords.split(",") : [],

// User Data
github: modalResponse.fields.getTextInputValue("username"),
discord: interaction.user.username,
};

const data = filterObject(
{
owner: {
username: github,
discord,
},
records: {
CNAME: cname.length ? cname : null,
A: A.length ? A : null,
TXT: txt.length ? txt : null,
MX: mx.length ? mx : null,
},
},
(_, v) => {
return v != null;
},
true
);

const resultEmbed = new Discord.EmbedBuilder()
.setColor(client.config.embeds.default as ColorResolvable)
.setDescription(
`\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\``
);

await interaction.editReply({
embeds: [resultEmbed],
components: [],
});
componentCollector.stop("generated");
}
}
);

componentCollector.on("end", async (collected, reason) => {
if (reason === "unfinished") {
await interaction.editReply({
embeds: [endEmbed],
components: [], // Remove all the components, aka the action row
});
}
});
} catch (err) {
client.logCommandError(err, interaction, Discord);
}
},
};

export = command;
18 changes: 6 additions & 12 deletions src/commands/is-a-dev/get-domain-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AutocompleteInteraction, ChatInputCommandInteraction, ColorResolvable }

import axios from "axios";
import { emojis as emoji } from "../../../config.json";
import { fetchDomains } from "../../util/functions";

const command: Command = {
name: "get-domain-json",
Expand Down Expand Up @@ -82,22 +83,15 @@ const command: Command = {

if (option.name === "subdomain") {
// Fetch all subdomains
const res = (await axios.get("https://raw.is-a.dev/v2.json")).data;
const res = await fetchDomains();

// Filter subdomains
const filteredSubdomains = res.filter((entry: any) => entry.subdomain.startsWith(option.value) && !entry.internal && !entry.reserved);

// Map subdomains to choices
const choices = filteredSubdomains
.map((entry: any) => {
return {
name: entry.subdomain,
value: entry.subdomain
};
})
const filteredSubdomains = res
.filter((entry: any) => entry.subdomain.startsWith(option.value) && !entry.internal && !entry.reserved)
.map((entry: any) => ({ name: entry.subdomain, value: entry.subdomain }))
.slice(0, 25);

await interaction.respond(choices);
await interaction.respond(filteredSubdomains);
}
}
};
Expand Down
4 changes: 2 additions & 2 deletions src/commands/is-a-dev/random-website.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import Command from "../../classes/Command";
import ExtendedClient from "../../classes/ExtendedClient";
import { ChatInputCommandInteraction, ColorResolvable } from "discord.js";

import axios from "axios";
import { emojis as emoji } from "../../../config.json";
import { fetchDomains } from "../../util/functions";

const command: Command = {
name: "random-website",
Expand All @@ -21,7 +21,7 @@ const command: Command = {
Discord: typeof import("discord.js")
) {
try {
const res = (await axios.get("https://raw.is-a.dev/v2.json")).data;
const res = await fetchDomains();
const data = res
.filter((entry: any) => entry.owner.username !== "is-a-dev" && !entry.reserved && !entry.internal)
.filter((entry: any) => entry.records.A || entry.records.AAAA || entry.records.CNAME)
Expand Down
Loading