Skip to content

Commit

Permalink
fix: skip functions when missing db or api keys
Browse files Browse the repository at this point in the history
also updated some setup stuff. would like the commands to be dynamically deployed, maybe
fixes #36
  • Loading branch information
12beesinatrenchcoat committed Jan 3, 2024
1 parent 6bed445 commit a9aa07e
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 89 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ dist
tsconfig.tsbuildinfo

# secret tokens
*.env
*.env*

# database
db/**
Expand Down
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,20 @@ slabbot is also built on [node.js](https://nodejs.org) v18.x (v18.4.0 as of writ

Install dependencies with `pnpm i` and then build with `pnpm build` (or `pnpm tsc`).

Create a .env file with `pnpm run setup` (see [setup.js](/setup.js)).
Create a .env file with `pnpm run setup` (see [setup.js](/setup.js)). You can also fill this out if you'd like:
```sh
# Discord (https://discord.com/developers/applications)
DISCORD_TOKEN= # required for bot to functi:won
CLIENT_ID= # required to deploy commands
GUILD_ID= # deploying commands to testing guild

# MongoDB - used for stats (/slabbot and exp)
MONGO_URL=

# osu! (https://osu.ppy.sh/home/account/edit#oauth) -
OSU_ID=
OSU_SECRET=
```

Run with `pnpm dev` or `pnpm start`.

Expand Down
7 changes: 7 additions & 0 deletions src/Utilities.exp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import {ChatInputCommandInteraction, EmbedBuilder, Message, User} from "discord.js";
import {UsersModel} from "./models.js";
import {newUser} from "./Utilities.Db.js";
import logger from "./logger.js";
import mongoose from "mongoose";

/**
* Gets the required Total EXP required to reach a specified level.
Expand All @@ -12,6 +14,11 @@ import {newUser} from "./Utilities.Db.js";
export const expNeededForLevel = (level: number): number => level * (2500 + ((level - 1) * 100));

export async function grantExp(user: User, event: Message | ChatInputCommandInteraction) {
if (mongoose.connection.readyState !== 1) {
logger.trace("no database, not granting exp");
return;
}

if (!user.bot) {
const userInDb = await UsersModel.findById(user.id);

Expand Down
21 changes: 16 additions & 5 deletions src/commands/osu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ const rankEmojis = {
SSH: "<:osu_SSH:994113327481499728>",
} as const;

auth();
if (OSU_ID && OSU_SECRET) {
auth();
} else {
logger.warn("missing osu!api credentials, skipping authentication; osu command will not work.");
}

export default class implements Command {
data = new SlashCommandBuilder()
Expand Down Expand Up @@ -89,6 +93,13 @@ export default class implements Command {
const subcommand = interaction.options.getSubcommand();
let embed;

if (!token) {
return interaction.reply({
content: "this command isn't available.",
embeds: [generateCommandProblemEmbed("missing credentials", "there's no token available to log into the osu!api. contact the person who runs this bot.", "error")],
});
}

if (subcommand === "user") {
const user = interaction.options.getString("user", true);
const mode = interaction.options.getString("mode", false) as GameMode || undefined;
Expand All @@ -101,7 +112,7 @@ export default class implements Command {
return interaction.reply("vs");
}

if(embed) {
if (embed) {
return interaction.reply({
content: "here's what i found:",
embeds: [embed],
Expand Down Expand Up @@ -242,7 +253,7 @@ interface User {
async function getUser(username: string, mode?: GameMode): Promise<User> {
const userString = username + (mode ? `/${mode}` : "");
if (osuUserCache.has(userString)) {
logger.debug(`${userString} found in cache!`);
logger.trace(`${userString} found in cache!`);
return osuUserCache.get(userString) as User;
}

Expand All @@ -262,15 +273,15 @@ async function getUser(username: string, mode?: GameMode): Promise<User> {
return response;
}

async function makeUserEmbed(user: string, mode: GameMode | undefined){
async function makeUserEmbed(user: string, mode: GameMode | undefined) {
// Fetch from osu!api
const data = await getUser(user, mode) as User;

if (data.error === null) {
return generateCommandProblemEmbed(
"user not found!",
`The osu!api returned an error when looking for user \`${user}\`. The user may have changed their username, their account may be unavailable due to security issues or a restriction, or you may have made a typo!`,
"error"
"error",
);
}

Expand Down
85 changes: 59 additions & 26 deletions src/commands/slabbot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {formatNum, generateCommandProblemEmbed, generateProgressBar, msToDuratio
import {newUser} from "../Utilities.Db.js";
import {CommandUsageModel, SlabbotCommand, SlabbotUser, UsersModel} from "../models.js";
import {expNeededForLevel, generateLargeNumber} from "../Utilities.exp.js";
import mongoose from "mongoose";

export default class implements Command {
data = new SlashCommandBuilder()
Expand Down Expand Up @@ -36,6 +37,8 @@ export default class implements Command {
return logger.error("client.user or client.uptime is null… which really shouldn't happen.");
}

const slabbotStats = await getSlabbotStats();

const embed = new EmbedBuilder()
.setColor(colors.orange)
.setTitle("[= ^ x ^ =] hello!")
Expand All @@ -52,44 +55,44 @@ export default class implements Command {
inline: true,
});

const commandUsage = await CommandUsageModel.find() as SlabbotCommand[];
if (commandUsage) {
const totalCommandsUsed = commandUsage.reduce((total, i) => total + i.value, 0);
const mostUsedCommands = commandUsage.sort((a, b) => b.value - a.value);
let mostUsedCommandsString = "";
for (let i = 0; i < 5; i++) {
if (mostUsedCommands[i]) {
mostUsedCommandsString += `${i + 1}. /${mostUsedCommands[i]._id} (used ${formatNum(mostUsedCommands[i].value)} times)`;
mostUsedCommandsString += "\n";
} else {
break;
}
}

embed.addFields(
{
name: "Commands Run",
value: formatNum(totalCommandsUsed) || "None yet…",
inline: true,
}, {
name: "Most Used Commands",
value: mostUsedCommandsString || "Nothing yet…",
inline: false,
},
);
if (slabbotStats) {
embed.addFields({
name: "Commands Run",
value: slabbotStats.commandsRun,
inline: true,
}, {
name: "Most Used Commands",
value: slabbotStats.mostUsedCommands,
inline: false,
});
}

return interaction.reply({
content: "hi am slabbot [= ^ x ^ =]",
content: "oh hi! [= ^ x ^ =]",
embeds: [embed],
});
}

/* /slabbot profile */
if (subcommand === "profile") {
if (mongoose.connection.readyState !== 1) {
logger.trace("no database, no stats");
return interaction.reply({
content: "database missing…",
embeds: [
generateCommandProblemEmbed(
"database not connected",
"there's no database connected, and therefore no stats to get. contact the person running the bot.",
"error",
),
],
});
}

let member: GuildMember | null = null;
let user: User;

// In server or in DMs
if (interaction.inGuild()) {
member = interaction.options.getMember("user") as GuildMember || interaction.member;
user = member.user;
Expand Down Expand Up @@ -186,3 +189,33 @@ const botProfileError = generateCommandProblemEmbed(
"See title.",
"error",
);

async function getSlabbotStats(): Promise<{commandsRun: string, mostUsedCommands: string} | null> {
if (mongoose.connection.readyState !== 1) {
logger.trace("no database, no stats");
return null;
}

const commandUsage = await CommandUsageModel.find() as SlabbotCommand[];
if (!commandUsage) {
logger.error("no commandUsage???");
return null;
}

const totalCommandsUsed = commandUsage.reduce((total, i) => total + i.value, 0);
const mostUsedCommands = commandUsage.sort((a, b) => b.value - a.value);
let mostUsedCommandsString = "";
for (let i = 0; i < 5; i++) {
if (mostUsedCommands[i]) {
mostUsedCommandsString += `${i + 1}. /${mostUsedCommands[i]._id} (used ${formatNum(mostUsedCommands[i].value)} times)`;
mostUsedCommandsString += "\n";
} else {
break;
}
}

return {
commandsRun: formatNum(totalCommandsUsed) || "None yet…",
mostUsedCommands: mostUsedCommandsString || "Nothing yet…",
};
}
66 changes: 36 additions & 30 deletions src/events/interactionCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {CommandUsageModel, UsersModel} from "../models.js";
import {newUser} from "../Utilities.Db.js";
import {grantExp} from "../Utilities.exp.js";
import {generateCommandProblemEmbed} from "../Utilities.js";
import mongoose from "mongoose";

// Object of when user last used any command.
const lastUseTimes: {[key: Snowflake]: number | undefined} = {};
Expand All @@ -16,6 +17,7 @@ const lastCommandUseTimes: {[key: string]: {[key: Snowflake]: number | undefined
export default class implements DJSEvent {
name = "interactionCreate";
once = false;

execute = async function (interaction: ChatInputCommandInteraction) {
if (!interaction.isCommand()) {
return;
Expand Down Expand Up @@ -68,45 +70,49 @@ export default class implements DJSEvent {
// Run the command…
command.execute(interaction, client);

// Database name…
const subcommand = interaction.options.getSubcommand(false) ?? "";
const commandNameInDb = (command.data.name + " " + subcommand).trim();
if (mongoose.connection.readyState === 1) {
// Database name…
const subcommand = interaction.options.getSubcommand(false) ?? "";
const commandNameInDb = (command.data.name + " " + subcommand).trim();

// Global command usage.
const commandUsage = await CommandUsageModel.findById(commandNameInDb);
if (commandUsage) {
const {value} = commandUsage;
commandUsage.set("value", value + 1);
commandUsage.save();
} else {
const usage = new CommandUsageModel({
_id: commandNameInDb,
value: 1,
});
await usage.save();
logger.debug(`Added command ${commandNameInDb} to usage database.`);
}
// Global command usage.
const commandUsage = await CommandUsageModel.findById(commandNameInDb);
if (commandUsage) {
const {value} = commandUsage;
commandUsage.set("value", value + 1);
commandUsage.save();
} else {
const usage = new CommandUsageModel({
_id: commandNameInDb,
value: 1,
});
await usage.save();
logger.debug(`Added command ${commandNameInDb} to usage database.`);
}

// User command usage.
const slabbotUser = await UsersModel.findById(interaction.user.id);
if (slabbotUser && slabbotUser.commandUsage) {
const {commandUsage} = slabbotUser;
const commandUsageTimes = commandUsage.get(commandNameInDb);
// User command usage.
const slabbotUser = await UsersModel.findById(interaction.user.id);
if (slabbotUser && slabbotUser.commandUsage) {
const {commandUsage} = slabbotUser;
const commandUsageTimes = commandUsage.get(commandNameInDb);

if (commandUsageTimes) {
commandUsage.set(commandNameInDb, commandUsageTimes + 1);
if (commandUsageTimes) {
commandUsage.set(commandNameInDb, commandUsageTimes + 1);
} else {
commandUsage.set(commandNameInDb, 1);
}

slabbotUser.save();
} else {
commandUsage.set(commandNameInDb, 1);
newUser(interaction.user.id);
}

slabbotUser.save();
grantExp(interaction.user, interaction);
lastUseTimes[interaction.user.id] = interaction.createdTimestamp;
} else {
newUser(interaction.user.id);
logger.trace("no database, skipping stats");
}

grantExp(interaction.user, interaction);
lastUseTimes[interaction.user.id] = interaction.createdTimestamp;

if (command.cooldown) {
lastCommandUseTimes[command.data.name][interaction.user.id] = interaction.createdTimestamp;
}
Expand Down
2 changes: 1 addition & 1 deletion src/events/messageCreate.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {Message} from "discord.js";
import {DJSEvent} from "../Interfaces";
import {grantExp} from "../Utilities.exp.js";

export default class implements DJSEvent {
name = "messageCreate";
once = false;

execute = async function (message: Message) {
grantExp(message.author, message);
};
Expand Down
Loading

0 comments on commit a9aa07e

Please sign in to comment.