user registration, bal, change_bal, transfer
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
.env
|
.env
|
||||||
node_modules
|
node_modules
|
||||||
*.js
|
*.js
|
||||||
|
config.json
|
||||||
|
|||||||
35
commands/bal.ts
Normal file
35
commands/bal.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import type { ChatInputCommandInteraction } from "discord.js";
|
||||||
|
import { EmbedBuilder } from "discord.js";
|
||||||
|
|
||||||
|
import type { CommandData } from "./index";
|
||||||
|
import type { User } from "../db";
|
||||||
|
import config from "../config.json";
|
||||||
|
import { get_user } from "../db";
|
||||||
|
import { BotError } from "./error";
|
||||||
|
|
||||||
|
async function run(interaction: ChatInputCommandInteraction, user: User) {
|
||||||
|
const options = interaction.options;
|
||||||
|
const target = (await options.get("target"))?.user;
|
||||||
|
let embeds = [];
|
||||||
|
let bal_embed = new EmbedBuilder();
|
||||||
|
let bal_target = target ?? interaction.user;
|
||||||
|
let bal_user = target ? await get_user(target.id) : user;
|
||||||
|
if (!bal_user) throw new BotError("Target is not registered"); //must be target
|
||||||
|
bal_embed.setTitle(`${bal_target.tag}'s Balance`);
|
||||||
|
bal_embed.setDescription(`${bal_user.balance} ${ bal_user.balance === 1 ? config.currency : config.currency_plural }`);
|
||||||
|
embeds.push(bal_embed);
|
||||||
|
return await interaction.editReply({ embeds });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: CommandData = {
|
||||||
|
name: "bal",
|
||||||
|
description: "Show you or someone else's balance",
|
||||||
|
registered_only: true,
|
||||||
|
ephemeral: false,
|
||||||
|
admin_only: false,
|
||||||
|
run,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default data;
|
||||||
|
|
||||||
|
|
||||||
34
commands/change_bal.ts
Normal file
34
commands/change_bal.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { ChatInputCommandInteraction } from "discord.js";
|
||||||
|
|
||||||
|
import type { CommandData } from "./index";
|
||||||
|
import type { User } from "../db";
|
||||||
|
import { get_user, add_balance, sub_balance } from "../db";
|
||||||
|
import { BotError } from "./error";
|
||||||
|
|
||||||
|
async function run(interaction: ChatInputCommandInteraction, _user: User) {
|
||||||
|
const options = interaction.options;
|
||||||
|
const target_id: string = (await options.get("target")).user.id;
|
||||||
|
const amount: number = (await options.get("amount")).value as number;
|
||||||
|
const negative_allowed: boolean = ((await options.get("negative_allowed"))?.value ?? false) as boolean;
|
||||||
|
let change_user = await get_user(target_id);
|
||||||
|
if (!change_user) throw new BotError("Target is not registered");
|
||||||
|
if (amount >= 0) {
|
||||||
|
await add_balance(target_id, amount);
|
||||||
|
} else {
|
||||||
|
const success = await sub_balance(target_id, -amount, negative_allowed);
|
||||||
|
if (!success) throw new BotError("Failed because would make target balance negative, so `negative_allowed` must be true to do this");
|
||||||
|
}
|
||||||
|
return await interaction.editReply(`Changed <@${target_id}> balance by ${amount}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: CommandData = {
|
||||||
|
name: "change_bal",
|
||||||
|
description: "Change a user's balance",
|
||||||
|
registered_only: true,
|
||||||
|
ephemeral: false,
|
||||||
|
admin_only: true,
|
||||||
|
run,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default data;
|
||||||
|
|
||||||
@@ -1,28 +1,31 @@
|
|||||||
|
import type { ChatInputCommandInteraction, GuildMember } from "discord.js";
|
||||||
import { EmbedBuilder } from "discord.js";
|
import { EmbedBuilder } from "discord.js";
|
||||||
import type { ChatInputCommandInteraction } from "discord.js";
|
|
||||||
|
|
||||||
|
import type { User } from "../db";
|
||||||
|
import { get_user } from "../db";
|
||||||
import { BotError } from "./error";
|
import { BotError } from "./error";
|
||||||
|
import config from "../config.json";
|
||||||
|
|
||||||
import say from "./say";
|
import say from "./say";
|
||||||
import roll from "./roll";
|
import roll from "./roll";
|
||||||
|
import register_user from "./register_user";
|
||||||
|
import bal from "./bal";
|
||||||
|
import change_bal from "./change_bal";
|
||||||
|
import transfer from "./transfer";
|
||||||
|
|
||||||
export interface CommandData {
|
export interface CommandData {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
registered_only: boolean; //also means interaction will get deferReply()ed
|
||||||
ephemeral: boolean;
|
ephemeral: boolean;
|
||||||
admin_only: boolean;
|
admin_only: boolean;
|
||||||
run: (interaction: ChatInputCommandInteraction) => Promise<any>;
|
run: (interaction: ChatInputCommandInteraction, user?: User) => Promise<any>;
|
||||||
//
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const commands: CommandData[] = [say, roll];
|
const commands: CommandData[] = [say, roll, register_user, bal, change_bal, transfer];
|
||||||
|
|
||||||
//todo: look from config. also, have a config
|
|
||||||
function is_admin(interaction: ChatInputCommandInteraction): boolean {
|
function is_admin(interaction: ChatInputCommandInteraction): boolean {
|
||||||
//
|
return (interaction.member as GuildMember).roles.cache.some((r) => r.id === config.admin_role);
|
||||||
//placeholder
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function run(interaction: ChatInputCommandInteraction) {
|
export default async function run(interaction: ChatInputCommandInteraction) {
|
||||||
@@ -59,7 +62,15 @@ export default async function run(interaction: ChatInputCommandInteraction) {
|
|||||||
try {
|
try {
|
||||||
//admin stuff should be ideally handled by register.ts, but this is a fallback
|
//admin stuff should be ideally handled by register.ts, but this is a fallback
|
||||||
if (found.admin_only && !is_admin(interaction)) throw new BotError("Admin permission needed to run that command");
|
if (found.admin_only && !is_admin(interaction)) throw new BotError("Admin permission needed to run that command");
|
||||||
|
if (found.registered_only) {
|
||||||
|
await interaction.deferReply();
|
||||||
|
const user = await get_user(interaction.user.id);
|
||||||
|
if (!user) throw new BotError("You must be registered by an admin to use this command");
|
||||||
|
await found.run(interaction, user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
await found.run(interaction);
|
await found.run(interaction);
|
||||||
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof BotError) {
|
if (e instanceof BotError) {
|
||||||
//send error message to that channel
|
//send error message to that channel
|
||||||
|
|||||||
26
commands/register_user.ts
Normal file
26
commands/register_user.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type { ChatInputCommandInteraction } from "discord.js";
|
||||||
|
|
||||||
|
import type { CommandData } from "./index";
|
||||||
|
import { get_user, add_new_user } from "../db";
|
||||||
|
import { BotError } from "./error";
|
||||||
|
|
||||||
|
async function run(interaction: ChatInputCommandInteraction) {
|
||||||
|
await interaction.deferReply();
|
||||||
|
const options = interaction.options;
|
||||||
|
const target_id = (await options.get("target")).user.id;
|
||||||
|
if (await get_user(target_id)) throw new BotError("Target is already registered");
|
||||||
|
await add_new_user(target_id);
|
||||||
|
return await interaction.editReply({ content: `Registered <@${target_id}>`, allowedMentions: { users: [] } });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: CommandData = {
|
||||||
|
name: "register_user",
|
||||||
|
description: "Register a user so they can participate in the bot economy",
|
||||||
|
registered_only: false,
|
||||||
|
ephemeral: false,
|
||||||
|
admin_only: true,
|
||||||
|
run,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default data;
|
||||||
|
|
||||||
@@ -29,10 +29,10 @@ async function run(interaction: ChatInputCommandInteraction) {
|
|||||||
const data: CommandData = {
|
const data: CommandData = {
|
||||||
name: "roll",
|
name: "roll",
|
||||||
description: "Roll dice",
|
description: "Roll dice",
|
||||||
|
registered_only: false,
|
||||||
ephemeral: false,
|
ephemeral: false,
|
||||||
admin_only: false,
|
admin_only: false,
|
||||||
run,
|
run,
|
||||||
//
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default data;
|
export default data;
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ async function run(interaction: ChatInputCommandInteraction) {
|
|||||||
const data: CommandData = {
|
const data: CommandData = {
|
||||||
name: "say",
|
name: "say",
|
||||||
description: "Have the bot say something in a channel",
|
description: "Have the bot say something in a channel",
|
||||||
|
registered_only: false,
|
||||||
ephemeral: true,
|
ephemeral: true,
|
||||||
admin_only: true,
|
admin_only: true,
|
||||||
run,
|
run,
|
||||||
//
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default data;
|
export default data;
|
||||||
|
|||||||
34
commands/transfer.ts
Normal file
34
commands/transfer.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { ChatInputCommandInteraction } from "discord.js";
|
||||||
|
|
||||||
|
import type { CommandData } from "./index";
|
||||||
|
import type { User } from "../db";
|
||||||
|
import config from "../config.json";
|
||||||
|
import { get_user, add_balance, sub_balance } from "../db";
|
||||||
|
import { BotError } from "./error";
|
||||||
|
|
||||||
|
async function run(interaction: ChatInputCommandInteraction, user: User) {
|
||||||
|
const options = interaction.options;
|
||||||
|
const target_id: string = (await options.get("target")).user.id;
|
||||||
|
const amount: number = (await options.get("amount")).value as number;
|
||||||
|
if (amount <= 0) throw new BotError("Transfer account cannot be zero or negative");
|
||||||
|
let trans_user = await get_user(target_id);
|
||||||
|
if (!trans_user) throw new BotError("Target is not registered");
|
||||||
|
//no checks, baby! well, let db.ts handle it
|
||||||
|
const enough_balance = await sub_balance(user.user, amount);
|
||||||
|
if (!enough_balance) throw new BotError(`You do not have enough ${config.currency_plural} to transfer that amount`);
|
||||||
|
await add_balance(target_id, amount);
|
||||||
|
return await interaction.editReply(`<@${user.user}> transferred ${amount} ${ amount === 1 ? config.currency : config.currency_plural } to <@${target_id}>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: CommandData = {
|
||||||
|
name: "transfer",
|
||||||
|
description: "Send currency to another user",
|
||||||
|
registered_only: true,
|
||||||
|
ephemeral: false,
|
||||||
|
admin_only: false,
|
||||||
|
run,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default data;
|
||||||
|
|
||||||
|
|
||||||
5
config.json.example
Normal file
5
config.json.example
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"currency": "credit",
|
||||||
|
"currency_plural": "credits",
|
||||||
|
"admin_role": "000000000000000000"
|
||||||
|
}
|
||||||
91
db.ts
91
db.ts
@@ -1,12 +1,101 @@
|
|||||||
import { MongoClient } from "mongodb";
|
import { MongoClient } from "mongodb";
|
||||||
|
|
||||||
|
import { did_update } from "./util";
|
||||||
|
|
||||||
//figure out the options and whatnot later
|
//figure out the options and whatnot later
|
||||||
const client = new MongoClient(process.env.MONGO_CONNECTION_STRING);
|
const client = new MongoClient(process.env.MONGO_CONNECTION_STRING);
|
||||||
|
|
||||||
let store, users, income;
|
let store, users, roleincome;
|
||||||
|
|
||||||
client.connect().then(() => {
|
client.connect().then(() => {
|
||||||
console.log("Connected to the database");
|
console.log("Connected to the database");
|
||||||
|
const db = client.db("db");
|
||||||
|
store = db.collection("store");
|
||||||
|
users = db.collection("users");
|
||||||
|
roleincome = db.collection("roleincome");
|
||||||
//
|
//
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//db structure types
|
||||||
|
|
||||||
|
export interface StoreItem {
|
||||||
|
//
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
user: `${number}`; //discord user id
|
||||||
|
balance: number;
|
||||||
|
items: Record<string, number>;
|
||||||
|
//
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface RoleIncome {
|
||||||
|
//
|
||||||
|
};
|
||||||
|
|
||||||
|
//default
|
||||||
|
|
||||||
|
const DEFAULT_USER: Omit<User, "user"> = {
|
||||||
|
balance: 0,
|
||||||
|
items: {},
|
||||||
|
//
|
||||||
|
};
|
||||||
|
|
||||||
|
//users collection db functions
|
||||||
|
|
||||||
|
export async function add_new_user(user: string) {
|
||||||
|
return await users.insertOne({ user, ...DEFAULT_USER });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function get_user(user: string): Promise<User | undefined> {
|
||||||
|
return await users.findOne({ user });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function add_balance(user: string, amount: number) {
|
||||||
|
return await users.updateOne({ user }, {
|
||||||
|
$inc: {
|
||||||
|
balance: amount,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//if false, not enough balance
|
||||||
|
export async function sub_balance(user: string, amount: number, negative_allowed: boolean = false): Promise<boolean> {
|
||||||
|
let query: any = {
|
||||||
|
user,
|
||||||
|
};
|
||||||
|
if (!negative_allowed) {
|
||||||
|
query.balance = {
|
||||||
|
$gte: amount,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return did_update(await users.updateOne(query, {
|
||||||
|
$inc: {
|
||||||
|
balance: -amount,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function add_item_to_user(user: string, item: string, amount: number) {
|
||||||
|
return await users.updateOne({ user }, {
|
||||||
|
$inc: {
|
||||||
|
[`items.${item}`]: amount,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sub_item_to_user(user: string, item: string, amount: number): Promise<boolean> {
|
||||||
|
return await users.updateOne({
|
||||||
|
user,
|
||||||
|
[`items.${item}`]: {
|
||||||
|
$gte: amount,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
$inc: {
|
||||||
|
[`items.${item}`]: -amount,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
|||||||
12
index.ts
12
index.ts
@@ -1,12 +1,12 @@
|
|||||||
import { Client, BaseInteraction } from "discord.js";
|
import { BaseInteraction, Client, GatewayIntentBits } from "discord.js";
|
||||||
import { config } from "dotenv";
|
import { config } from "dotenv";
|
||||||
|
|
||||||
//import db from "./db";
|
|
||||||
import run from "./commands";
|
|
||||||
|
|
||||||
config();
|
config();
|
||||||
|
|
||||||
const client = new Client({ intents: [] });
|
import {} from "./db";
|
||||||
|
import run from "./commands";
|
||||||
|
|
||||||
|
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||||
|
|
||||||
client.on("ready", async () => {
|
client.on("ready", async () => {
|
||||||
console.log(`Logged in as ${client.user.tag}`);
|
console.log(`Logged in as ${client.user.tag}`);
|
||||||
@@ -20,5 +20,5 @@ client.on("interactionCreate", async (interaction: BaseInteraction) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => client.login(process.env.DISCORD_TOKEN), 2000);
|
setTimeout(() => client.login(process.env.DISCORD_TOKEN), 3000);
|
||||||
|
|
||||||
|
|||||||
67
register.ts
67
register.ts
@@ -57,6 +57,73 @@ const commands = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
//economy related
|
||||||
|
{
|
||||||
|
name: "register_user",
|
||||||
|
description: "Register a user so they can participate in the bot economy (admin only)",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: 6,
|
||||||
|
name: "target",
|
||||||
|
description: "The user to register",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bal",
|
||||||
|
description: "Show you or someone else's balance",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: 6,
|
||||||
|
name: "target",
|
||||||
|
description: "The user to check the balance of",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change_bal",
|
||||||
|
description: "Change a user's balance (admin only)",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: 6,
|
||||||
|
name: "target",
|
||||||
|
description: "The user to change the balance of",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 4,
|
||||||
|
name: "amount",
|
||||||
|
description: "Amount to add/subtract",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 5,
|
||||||
|
name: "negative_allowed",
|
||||||
|
description: "Allow user balance to become negative (default: false)",
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "transfer",
|
||||||
|
description: "Send currency to another user",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: 6,
|
||||||
|
name: "target",
|
||||||
|
description: "The user to send to",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 4,
|
||||||
|
name: "amount",
|
||||||
|
description: "Amount to transfer",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
(new REST().setToken(process.env.DISCORD_TOKEN)).put(Routes.applicationCommands(process.env.CLIENT_ID), { body: commands }).then(() => console.log("Finished reloading slash commands"));
|
(new REST().setToken(process.env.DISCORD_TOKEN)).put(Routes.applicationCommands(process.env.CLIENT_ID), { body: commands }).then(() => console.log("Finished reloading slash commands"));
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
"typeRoots": ["./node_modules/@types"],
|
"typeRoots": ["./node_modules/@types"],
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
},
|
},
|
||||||
"lib": ["ES2020"],
|
"lib": ["ES2020"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
|||||||
Reference in New Issue
Block a user