Skip to content

Commit

Permalink
Merge pull request #11 from ZakaHaceCosas/dev
Browse files Browse the repository at this point in the history
some (possibly cool) stuff
MrSerge01 authored Jan 31, 2025
2 parents 583c37a + 9010c79 commit 572cc0f
Showing 21 changed files with 237 additions and 79 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -3,3 +3,4 @@ node_modules/
.DS_Store
data.db
.idea
.vscode/
2 changes: 1 addition & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
@@ -2,6 +2,6 @@
"tabWidth": 2,
"useTabs": false,
"arrowParens": "avoid",
"trailingComma": "none",
"trailingComma": "es5",
"printWidth": 100
}
23 changes: 21 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,44 @@
# Sokora Contributing Guide

## Prerequisites

- Basic knowledge of [TypeScript](https://typescriptlang.org/) and [discord.js](https://discord.js.org/).
- [Bun](https://bun.sh) installed.

## Get started with contributing

### Getting the code

- Make a fork of this repository.
- Clone your fork.

### Creating your bot

- Head over to the [Discord Developer Portal](https://discord.com/developers/applications) and make a new application.
- Invite your bot to your server.
- Reset and then copy your bot's token.

### Setting up .env
- Run `bun run setup` and our cli tool will install dependencies and write .env for you

- Run `bun run setup` and our CLI tool will install dependencies and write .env for you. It'll ask you to paste in your bot's token.

### Running

- Run `bun dev`.

Be sure to open a pull request when you're ready to push your changes. Be descriptive of the changes you've made.

![](https://user-images.githubusercontent.com/51555391/176925763-cdfd57ba-ae1e-4bf3-85e9-b3ebd30b1d59.png)
## Code style guidelines

A few, simple guidelines onto how code contributed to Sokora should look like.

- Keep a consistent indentation of two spaces. Don't use tabs.
- Use `K&R` style for bracket placement (`function() {}` instead of `function() \n {}`).
- Use `camelCase` for both variables and function names.
- Keep lines reasonably short, don't fear linebreaks. Of course, longer lines are valid where needed.
- Use early returns to avoid nesting.
- Avoid curly braces in one-line `if` statements.
- Non-nullish assertions are valid when needed.
- Use the cache instead of a new `fetch()` call where possible, to avoid unnecessary usage (e.g., if possible, prefer `guild.members.cache` over `await guild.members.fetch()`).

![PLEASE SUBMIT A PR, NO DIRECT COMMITS](https://user-images.githubusercontent.com/51555391/176925763-cdfd57ba-ae1e-4bf3-85e9-b3ebd30b1d59.png)
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -9,8 +9,10 @@
</div>

# About

Sokora is a multipurpose Discord bot that lets you manage your servers easily.
**Please note that Sokora is currently unstable so it might have issues.**

# Contributing

While we're developing the bot, you can [help us](CONTRIBUTING.MD) if you find any bugs.
2 changes: 1 addition & 1 deletion bun.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
// Note: this file must be committed for package version consisency for everyone who uses bun install
// Note: this file must be committed for package version consistency for everyone who uses bun install
"lockfileVersion": 1,
"workspaces": {
"": {
2 changes: 1 addition & 1 deletion src/commands/about.ts
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ export async function run(interaction: ChatInputCommandInteraction) {
{
name: "🔗 • Links",
value: [
"[Discord](https://discord.gg/c6C25P4BuY) • [GitHub](https://www.github.com/SokoraDesu) • [YouTube](https://www.youtube.com/@SokoraDesu) • [Instagram](https://instagram.com/NebulaTheBot) • [Mastodon](https://mastodon.online/@NebulaTheBot@mastodon.social) • [Matrix](https://matrix.to/#/#sokora:matrix.org) • [Revolt](https://rvlt.gg/28TS9aXy)",
"[Discord](https://discord.gg/c6C25P4BuY) • [GitHub](https://www.github.com/SokoraDesu) • [YouTube](https://www.youtube.com/@SokoraDesu) • [Mastodon](https://mastodon.online/@NebulaTheBot@mastodon.social) • [Matrix](https://matrix.to/#/#sokora:matrix.org) • [Revolt](https://rvlt.gg/28TS9aXy)",
"Also, please read the [ToS](https://sokora.org/terms) and the [privacy policy](https://sokora.org/privacy)."
].join("\n")
}
3 changes: 2 additions & 1 deletion src/commands/games/rps.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import {
ButtonInteraction,
ButtonStyle,
EmbedBuilder,
MessageFlags,
SlashCommandSubcommandBuilder,
type ChatInputCommandInteraction
} from "discord.js";
@@ -74,7 +75,7 @@ export async function run(interaction: ChatInputCommandInteraction) {

collector.on("collect", async (i: ButtonInteraction) => {
playerChoices.set(i.user.id, i.customId.split("_")[1] as RPSChoice);
await i.reply({ content: "Choice recorded!", ephemeral: true });
await i.reply({ content: "Choice recorded!", flags: MessageFlags.Ephemeral });
if (playerChoices.size == 2) collector.stop("game-complete");
});

9 changes: 5 additions & 4 deletions src/commands/math/graph.ts
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import {
} from "discord.js";
import * as math from "mathjs";
import { errorEmbed } from "../../utils/embeds/errorEmbed";
import { genColor, genRGBColor } from "../../utils/colorGen";

export const data = new SlashCommandSubcommandBuilder()
.setName("graph")
@@ -58,7 +59,7 @@ export async function run(interaction: ChatInputCommandInteraction) {
ctx.stroke();

ctx.strokeStyle = "#ff0000";
ctx.lineWidth = 2;
ctx.lineWidth = 3;
ctx.beginPath();

const points = 1000;
@@ -89,16 +90,16 @@ export async function run(interaction: ChatInputCommandInteraction) {
const attachment = new AttachmentBuilder(canvas.toBuffer(), { name: "graph.png" });
const embed = new EmbedBuilder()
.setTitle("Function Graph")
.setDescription(`f(x) = ${func}`)
.setDescription(`\`f(x) = ${func}\``)
.setImage("attachment://graph.png")
.setColor("#00ff00");
.setColor(genColor(100));

await interaction.reply({ embeds: [embed], files: [attachment] });
} catch (error) {
return await errorEmbed(
interaction,
"Invalid function",
"Please provide a valid mathematical function. Examples: 'x^2', 'sin(x)', '2*x + 1'"
"Please provide a valid mathematical function. Examples: 'x^2', 'sin(x)', '2*x + 1'."
);
}
}
3 changes: 2 additions & 1 deletion src/commands/news/add.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ActionRowBuilder,
EmbedBuilder,
MessageFlags,
ModalBuilder,
SlashCommandSubcommandBuilder,
TextInputBuilder,
@@ -68,7 +69,7 @@ export async function run(interaction: ChatInputCommandInteraction) {
await sendChannelNews(guild, id, interaction).catch(err => console.error(err));
await i.reply({
embeds: [new EmbedBuilder().setTitle("News added.").setColor(genColor(100))],
ephemeral: true
flags: MessageFlags.Ephemeral
});
});
}
3 changes: 2 additions & 1 deletion src/commands/news/edit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
ActionRowBuilder,
EmbedBuilder,
MessageFlags,
ModalBuilder,
SlashCommandSubcommandBuilder,
TextInputBuilder,
@@ -93,7 +94,7 @@ export async function run(interaction: ChatInputCommandInteraction) {
updateNews(guild.id, id, title, body);
await i.reply({
embeds: [new EmbedBuilder().setTitle("News edited.").setColor(genColor(100))],
ephemeral: true
flags: MessageFlags.Ephemeral
});
});
}
3 changes: 2 additions & 1 deletion src/commands/news/remove.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
EmbedBuilder,
MessageFlags,
SlashCommandSubcommandBuilder,
TextChannel,
type ChatInputCommandInteraction
@@ -40,6 +41,6 @@ export async function run(interaction: ChatInputCommandInteraction) {
deleteNews(guild.id, id);
await interaction.reply({
embeds: [new EmbedBuilder().setTitle("News removed.").setColor(genColor(100))],
ephemeral: true
flags: MessageFlags.Ephemeral
});
}
30 changes: 24 additions & 6 deletions src/commands/serverboard.ts
Original file line number Diff line number Diff line change
@@ -3,8 +3,9 @@ import {
ButtonBuilder,
ButtonInteraction,
ButtonStyle,
Guild,
SlashCommandBuilder,
type ChatInputCommandInteraction
type ChatInputCommandInteraction,
} from "discord.js";
import { listPublicServers } from "../utils/database/settings";
import { errorEmbed } from "../utils/embeds/errorEmbed";
@@ -16,9 +17,17 @@ export const data = new SlashCommandBuilder()
.addNumberOption(number => number.setName("page").setDescription("The page you want to see."));

export async function run(interaction: ChatInputCommandInteraction) {
const guildList = (
await Promise.all(listPublicServers().map(id => interaction.client.guilds.fetch(id)))
).sort((a, b) => b.memberCount - a.memberCount);
const guildList: { guild: Guild; showInvite: boolean; inviteChannelId: string | null }[] = (
await Promise.all(
listPublicServers().map(async entry => {
return {
guild: await interaction.client.guilds.fetch(entry.guildID),
showInvite: entry.showInvite,
inviteChannelId: entry.inviteChannelId,
};
})
)
).sort((a, b) => b.guild.memberCount - a.guild.memberCount);

const pages = guildList.length;
if (!pages)
@@ -32,7 +41,16 @@ export async function run(interaction: ChatInputCommandInteraction) {
let page = (argPage - 1 <= 0 ? 0 : argPage - 1 > pages ? pages - 1 : argPage - 1) || 0;

async function getEmbed() {
return await serverEmbed({ guild: guildList[page], page: page + 1, pages });
return await serverEmbed({
guild: guildList[page].guild,
invite: {
show: guildList[page].showInvite,
channel: guildList[page].inviteChannelId,
},
page: page + 1,
pages,
roles: false,
});
}

const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
@@ -48,7 +66,7 @@ export async function run(interaction: ChatInputCommandInteraction) {

const reply = await interaction.reply({
embeds: [await getEmbed()],
components: pages != 1 ? [row] : []
components: pages != 1 ? [row] : [],
});
if (pages == 1) return;

30 changes: 20 additions & 10 deletions src/commands/settings.ts
Original file line number Diff line number Diff line change
@@ -5,17 +5,18 @@ import {
PermissionsBitField,
SlashCommandBuilder,
SlashCommandSubcommandBuilder,
type ChatInputCommandInteraction
type ChatInputCommandInteraction,
} from "discord.js";
import { genColor } from "../utils/colorGen";
import {
getSetting,
setSetting,
settingsDefinition,
settingsKeys
settingsKeys,
} from "../utils/database/settings";
import { errorEmbed } from "../utils/embeds/errorEmbed";
import { capitalize } from "../utils/capitalize";
import { humanizeSettings } from "../utils/humanizeSettings";

export let data = new SlashCommandBuilder()
.setName("settings")
@@ -94,7 +95,7 @@ export async function run(interaction: ChatInputCommandInteraction) {
const key = interaction.options.getSubcommand() as keyof typeof settingsDefinition;
const values = interaction.options.data[0].options!;
const settingsDef = settingsDefinition[key];
const settingText = (name: string) => {
const settingText = (name: string): string => {
const setting = getSetting(guild.id, key, name)?.toString();
let text;
switch (settingsDef.settings[name].type) {
@@ -108,7 +109,7 @@ export async function run(interaction: ChatInputCommandInteraction) {
text = setting ? `<@&${setting}>` : "Not set";
break;
default:
text = setting || "Not set";
text = setting || "*Not set*";
break;
}
return text;
@@ -118,7 +119,14 @@ export async function run(interaction: ChatInputCommandInteraction) {
const embed = new EmbedBuilder()
.setAuthor({ name: `${capitalize(key)} settings` })
.setDescription(
Object.keys(settingsDef.settings).map(setting => `${settingsDef.settings[setting].emoji} **• ${capitalize(setting.replaceAll("_", " "))}**: ${settingText(setting)}`).join("\n")
Object.keys(settingsDef.settings)
.map(
setting =>
`${settingsDef.settings[setting].emoji} **• ${humanizeSettings(
capitalize(setting)
)}**: ${humanizeSettings(settingText(setting))}`
)
.join("\n")
)
.setColor(genColor(100));

@@ -127,9 +135,9 @@ export async function run(interaction: ChatInputCommandInteraction) {

const embed = new EmbedBuilder()
.setColor(genColor(100))
.setAuthor({ name: `✅ • ${capitalize(key)} settings changed` })
.setAuthor({ name: `✅ • ${capitalize(key)} settings changed` });

let description = ""
let description = "";
for (let i = 0; i < values.length; i++) {
const option = values[i];

@@ -147,9 +155,11 @@ export async function run(interaction: ChatInputCommandInteraction) {
);

setSetting(guild.id, key, option.name, option.value as string);
description += `**${capitalize(option.name)}:** ${settingText(option.name.toString()!)}\n`
description += `**${humanizeSettings(capitalize(option.name))}:** ${humanizeSettings(
settingText(option.name.toString())
)}\n`;
}
embed.setDescription(description)
embed.setDescription(description);

await interaction.reply({ embeds: [embed] });
}
@@ -161,7 +171,7 @@ export async function autocomplete(interaction: AutocompleteInteraction) {
await interaction.respond(
["true", "false"].map(choice => ({
name: choice,
value: choice
value: choice,
}))
);
break;
5 changes: 4 additions & 1 deletion src/events/messageDelete.ts
Original file line number Diff line number Diff line change
@@ -16,7 +16,10 @@ export default (async function run(message) {
name: `• ${author.displayName}'s message has been deleted.`,
iconURL: author.displayAvatarURL()
})
.setDescription(`[Jump to message](${message.url})`)
.setDescription(
`[Jump to message](${message.url}) • [See ${author.displayName}'s profile](https://discord.com/users/${author.id})`
)
.setTimestamp(new Date())
.addFields({
name: "🗑️ • Deleted message",
value: message.content!
5 changes: 4 additions & 1 deletion src/events/messageUpdate.ts
Original file line number Diff line number Diff line change
@@ -20,7 +20,10 @@ export default (async function run(oldMessage, newMessage) {
name: `• ${author.displayName} edited a message.`,
iconURL: author.displayAvatarURL()
})
.setDescription(`[Jump to message](${oldMessage.url})`)
.setDescription(
`[Jump to message](${oldMessage.url}) • [See ${author.displayName}'s profile](https://discord.com/users/${author.id})`
)
.setTimestamp(new Date())
.addFields(
{
name: "🖋️ • Old message",
3 changes: 1 addition & 2 deletions src/utils/capitalize.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
* @param string String, the first letter of which should be capitalized.
*/

export function capitalize(string: string) {
if (!string) return;
export function capitalize(string: string): string {
return `${string.charAt(0).toUpperCase()}${string.slice(1)}`;
}
78 changes: 54 additions & 24 deletions src/utils/database/settings.ts
Original file line number Diff line number Diff line change
@@ -7,19 +7,19 @@ const tableDefinition = {
definition: {
guildID: "TEXT",
key: "TEXT",
value: "TEXT"
}
value: "TEXT",
},
} satisfies TableDefinition;

export const settingsDefinition: Record<
string,
{
description: string;
settings: Record<string, { type: FieldData; desc: string; val?: any, emoji: string }>;
settings: Record<string, { type: FieldData; desc: string; val?: any; emoji: string }>;
}
> = {
leveling: {
description: "Customise the behaviour of the leveling system.",
description: "Customize the behavior of the leveling system.",
settings: {
enabled: {
type: "BOOL",
@@ -131,8 +131,8 @@ export const settingsDefinition: Record<
desc: "Delay before autokicking is triggered",
val: false,
emoji: "🍍",
}
}
},
},
},
news: {
description: "Configure news for your server.",
@@ -164,8 +164,8 @@ export const settingsDefinition: Record<
desc: "Allow users to receive news in DMs.",
val: false,
emoji: "🍍",
}
}
},
},
},
starboard: {
description: "Configure the starboard system.",
@@ -192,8 +192,8 @@ export const settingsDefinition: Record<
desc: "Reactions needed for a message to be starred.",
val: 3,
emoji: "🍍",
}
}
},
},
},
serverboard: {
description: "Configure your server's appearance on the serverboard.",
@@ -209,8 +209,13 @@ export const settingsDefinition: Record<
desc: "Whether to show server invite on the serverboard.",
val: false,
emoji: "🍍",
}
}
},
invite_channel: {
type: "CHANNEL",
desc: "Channel for the invite. If unset, if a rules channel exists uses it, hides the invite otherwise.",
emoji: "🍍",
},
},
},
welcome: {
description: "Change how Sokora welcomes your new users.",
@@ -254,8 +259,8 @@ export const settingsDefinition: Record<
type: "TEXT",
desc: "Roles to exclude from retention (comma-separated IDs)",
emoji: "🍍",
}
}
},
},
},
easter: {
description: "Enable/disable easter eggs.",
@@ -270,8 +275,8 @@ export const settingsDefinition: Record<
type: "TEXT",
desc: "Channel IDs where easter eggs are allowed (comma-separated).",
emoji: "🍍",
}
}
},
},
},
commands: {
description: "Configure command availability.",
@@ -280,8 +285,8 @@ export const settingsDefinition: Record<
type: "TEXT",
desc: "Disabled commands (comma-separated names).",
emoji: "🍍",
}
}
},
},
},
currency: {
description: "Configure the multi-currency system.",
@@ -303,9 +308,9 @@ export const settingsDefinition: Record<
desc: "Name of the secondary currency.",
val: "gems",
emoji: "🍍",
}
}
}
},
},
},
};

export const settingsKeys = Object.keys(settingsDefinition) as (keyof typeof settingsDefinition)[];
@@ -314,6 +319,9 @@ const getQuery = database.query("SELECT * FROM settings WHERE guildID = $1 AND k
const listPublicQuery = database.query(
"SELECT * FROM settings WHERE key = 'serverboard.shown' AND value = '1';"
);
const listPublicWithInvitesEnabledQuery = database.query(
"SELECT * FROM settings WHERE EXISTS (SELECT 1 FROM settings WHERE key = 'serverboard.server_invite' AND value = '1') AND EXISTS (SELECT 1 FROM settings WHERE key = 'serverboard.shown' AND value = '1');"
);
const deleteQuery = database.query("DELETE FROM settings WHERE guildID = $1 AND key = $2;");
const insertQuery = database.query(
"INSERT INTO settings (guildID, key, value) VALUES (?1, ?2, ?3);"
@@ -369,8 +377,30 @@ export function setSetting<K extends keyof typeof settingsDefinition>(
insertQuery.run(JSON.stringify(guildID), `${key}.${setting}`, value);
}

export function listPublicServers() {
return (listPublicQuery.all() as TypeOfDefinition<typeof tableDefinition>[]).map(entry =>
JSON.parse(entry.guildID)
export function listPublicServers(): {
guildID: string;
showInvite: boolean;
inviteChannelId: string | null;
}[] {
const publicGuildSet = new Set(
(listPublicQuery.all() as TypeOfDefinition<typeof tableDefinition>[]).map(entry =>
JSON.parse(entry.guildID)
)
);
// you know that time-complexity thingy? idk much but uh an array has O(n) and a JS Set() has O(1) which should mean using a Set is more performant
const inviteGuildsSet = new Set(
(listPublicWithInvitesEnabledQuery.all() as TypeOfDefinition<typeof tableDefinition>[]).map(
entry => JSON.parse(entry.guildID)
)
);

return Array.from(publicGuildSet).map(entry => {
const inviteChannel = getSetting(entry, "serverboard", "invite_channel");

return {
guildID: entry,
showInvite: inviteGuildsSet.has(entry),
inviteChannelId: inviteChannel ? inviteChannel.toString() : null,
};
});
}
6 changes: 3 additions & 3 deletions src/utils/embeds/errorEmbed.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EmbedBuilder, type ButtonInteraction, type ChatInputCommandInteraction } from "discord.js";
import { EmbedBuilder, MessageFlags, type ButtonInteraction, type ChatInputCommandInteraction } from "discord.js";
import { genColor } from "../colorGen";

/**
@@ -21,6 +21,6 @@ export async function errorEmbed(
.setDescription(content.join("\n"))
.setColor(genColor(0));

if (interaction.replied) return await interaction.followUp({ embeds: [embed], ephemeral: true });
return await interaction.reply({ embeds: [embed], ephemeral: true });
if (interaction.replied) return await interaction.followUp({ embeds: [embed], flags: MessageFlags.Ephemeral });
return await interaction.reply({ embeds: [embed], flags: MessageFlags.Ephemeral });
}
16 changes: 10 additions & 6 deletions src/utils/embeds/modEmbed.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import {
EmbedBuilder,
type PermissionResolvable,
type ChatInputCommandInteraction,
type User
type User,
} from "discord.js";
import ms from "ms";
import { genColor } from "../colorGen";
@@ -89,7 +89,9 @@ export async function errorCheck(
return await errorEmbed(
interaction,
`You can't ${action.toLowerCase()} ${name}.`,
`The member has ${highestModPos == highestTargetPos ? "the same" : "a higher"} role position ${highestModPos == highestTargetPos ? "as" : "than"} you.`
`The member has ${
highestModPos == highestTargetPos ? "the same" : "a higher"
} role position ${highestModPos == highestTargetPos ? "as" : "than"} you.`
);

if (ownerError) {
@@ -124,7 +126,9 @@ export async function modEmbed(
const guild = interaction.guild!;
const name = user.displayName;
const generalValues = [`**Moderator**: ${interaction.user.displayName}`];
let author = `• ${previousID ? "Edited a " : ""}${previousID ? dbAction?.toLowerCase() : action}${previousID ? " on" : ""} ${name}`;
let author = `• ${previousID ? "Edited a " : ""}${
previousID ? dbAction?.toLowerCase() : action
}${previousID ? " on" : ""} ${name}`;
reason ? generalValues.push(`**Reason**: ${reason}`) : generalValues.push("*No reason provided*");
if (duration) generalValues.push(`**Duration**: ${ms(ms(duration), { long: true })}`);
if (previousID) {
@@ -181,11 +185,11 @@ export async function modEmbed(
embed
.setAuthor({
name: `• You got ${action.toLowerCase()}.`,
iconURL: user.displayAvatarURL()
iconURL: user.displayAvatarURL(),
})
.setDescription(generalValues.slice(+!showModerator, generalValues.length).join("\n"))
.setColor(genColor(0))
]
.setColor(genColor(0)),
],
})
.catch(() => null);
} catch (e) {
73 changes: 60 additions & 13 deletions src/utils/embeds/serverEmbed.ts
Original file line number Diff line number Diff line change
@@ -4,13 +4,17 @@
* @returns Embed that contains the guild info.
*/

import { EmbedBuilder, type Guild } from "discord.js";
import { ChannelType, EmbedBuilder, Invite, type Guild } from "discord.js";
import { genColor } from "../colorGen";
import { imageColor } from "../imageColor";
import { pluralOrNot } from "../pluralOrNot";

type Options = {
guild: Guild;
invite?: {
show: boolean;
channel: string | null;
};
roles?: boolean;
page?: number;
pages?: number;
@@ -35,26 +39,28 @@ export async function serverEmbed(options: Options) {
text: channels.filter(channel => channel.type == 0 || channel.type == 15 || channel.type == 5)
.size,
voice: channels.filter(channel => channel.type == 2 || channel.type == 13).size,
categories: channels.filter(channel => channel.type == 4).size
categories: channels.filter(channel => channel.type == 4).size,
};

const generalValues = [
`Owned by **${(await guild.fetchOwner()).user.displayName}**`,
`Created on **<t:${Math.round(guild.createdAt.valueOf() / 1000)}:D>**`
`Created on **<t:${Math.round(guild.createdAt.valueOf() / 1000)}:D>**`,
];

const embed = new EmbedBuilder()
.setAuthor({
name: `${pages ? `#${page} • ` : icon ? "• " : ""}${guild.name}`,
iconURL: icon
iconURL: icon,
})
.setDescription(guild.description ? guild.description : null)
.setFields({ name: "📃 • General", value: generalValues.join("\n") })
.setFooter({ text: `${pages ? `Page ${page}/${pages}\n` : ""}Server ID: ${guild.id}` })
.setThumbnail(icon)
.setColor((await imageColor(icon)) ?? genColor(200));

if (options.roles)
const channelCount = channelSizes.text + channelSizes.voice;

if (options.roles) {
embed.addFields({
name: `🎭 • ${roles.size - 1} ${pluralOrNot("role", roles.size - 1)}`,
value:
@@ -63,37 +69,78 @@ export async function serverEmbed(options: Options) {
: `${sortedRoles
.slice(0, 5)
.map(role => `<@&${role[0]}>`)
.join(", ")}${rolesLength > 5 ? ` and **${rolesLength - 5}** more` : ""}`
.join(", ")}${rolesLength > 5 ? ` and **${rolesLength - 5}** more` : ""}`,
});
}

embed.addFields(
{
name: `👥 • ${guild.memberCount?.toLocaleString("en-US")} members`,
value: [
`**${formattedUserCount}** ${pluralOrNot("user", guild.memberCount - bots.size)}`,
`**${bots.size?.toLocaleString("en-US")}** ${pluralOrNot("bot", bots.size)}`
`**${bots.size?.toLocaleString("en-US")}** ${pluralOrNot("bot", bots.size)}`,
].join("\n"),
inline: true
inline: true,
},
{
name: `🗨️ • ${channelSizes.text + channelSizes.voice} ${pluralOrNot("channel", channelSizes.text + channelSizes.voice)}`,
name: `🗨️ • ${channelCount} ${pluralOrNot("channel", channelCount)}`,
value: [
`**${channelSizes.text}** text • **${channelSizes.voice}** voice`,
`**${channelSizes.categories}** ${pluralOrNot("category", channelSizes.categories)}`
`**${channelSizes.categories}** ${pluralOrNot("category", channelSizes.categories)}`,
].join("\n"),
inline: true
inline: true,
},
{
name: `🌟 • ${!boostTier ? "No level" : `Level ${boostTier}`}`,
value: [
`**${boostCount}**${
!boostTier ? "/2" : boostTier == 1 ? "/7" : boostTier == 2 ? "/14" : ""
} ${pluralOrNot("boost", boostCount!)}`,
`**${boosters.size}** ${pluralOrNot("booster", boosters.size)}`
`**${boosters.size}** ${pluralOrNot("booster", boosters.size)}`,
].join("\n"),
inline: true
inline: true,
}
);

if (options.invite?.show) {
const previousInvite: Invite | undefined = (await options.guild.invites.fetch()).find(
invite =>
invite.inviter?.id === "873918300726394960" &&
invite.maxUses === null &&
invite.expiresAt === null
);

if (!options.guild.rulesChannel) return embed;

const possiblyFetchedInviteChannel = await options.guild.channels.fetch(
options.invite.channel ?? "hi"
);

const inviteChannel =
possiblyFetchedInviteChannel &&
possiblyFetchedInviteChannel.isTextBased() &&
!possiblyFetchedInviteChannel.isThread()
? possiblyFetchedInviteChannel
: options.guild.rulesChannel;

if (!inviteChannel) return embed;

const inviteUrl = previousInvite
? previousInvite.url
: await inviteChannel.createInvite({
maxAge: undefined,
maxUses: undefined,
reason: "Serverboard",
temporary: false,
unique: true,
});

embed.addFields({
name: `🚪 • Join in!`,
value: `This server allows you to join from here. ${inviteUrl}`,
inline: true,
});
}

return embed;
}
17 changes: 17 additions & 0 deletions src/utils/humanizeSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Outputs the given settings_string
* @param {string} string Settings string, either a key or a value.
*/
export function humanizeSettings(string: string) {
if (!string) return;
const humanized = string
.trim()
.replaceAll("_", " ")
.replaceAll("true", "Enabled")
.replaceAll("false", "Disabled")
.replaceAll("(name)", "`(name)`")
.replaceAll("(servername)", "`(servername)`")
.replaceAll("(count)", "`(count)`");

return humanized;
}

0 comments on commit 572cc0f

Please sign in to comment.