+1
-1
packages/api/routes/api/v1/currency.ts
+1
-1
packages/api/routes/api/v1/currency.ts
···
1
1
import { Hono } from "hono";
2
-
import { UserCurrency, UserCurrencyType, UserIntegration } from "@voidy/bot/db"
2
+
import { UserCurrency, UserCurrencyType, UserIntegration } from "@voidy/bot/db";
3
3
import { isAuthenticated } from "../../../middlewares/isAuthenticated";
4
4
5
5
export const currency = new Hono();
+2
packages/api/routes/api/v1/index.ts
+2
packages/api/routes/api/v1/index.ts
+80
packages/api/routes/api/v1/shop.ts
+80
packages/api/routes/api/v1/shop.ts
···
1
+
import { Hono } from "hono";
2
+
import { MinecraftShopItem, MinecraftShop } from "@voidy/bot/db";
3
+
import { isAuthenticated } from "../../../middlewares/isAuthenticated";
4
+
5
+
export const shop = new Hono();
6
+
7
+
shop.get("/", isAuthenticated, async (c) => {
8
+
const latestShop = await MinecraftShop.findOne().sort({ createdAt: -1 });
9
+
10
+
if (!latestShop) {
11
+
return c.json({ error: "No shop found" }, 404);
12
+
}
13
+
14
+
return c.json(latestShop);
15
+
});
16
+
17
+
shop.post("/generate", isAuthenticated, async (c) => {
18
+
const body = await c.req.json().catch(() => ({}));
19
+
const { highlight } = body as { highlight?: string };
20
+
21
+
const allItems = await MinecraftShopItem.find();
22
+
23
+
if (!allItems.length) {
24
+
return c.json(
25
+
{ error: "No shop items available to generate from" },
26
+
400,
27
+
);
28
+
}
29
+
30
+
// Config
31
+
const MIN_ITEMS = 4;
32
+
const MAX_ITEMS = 8;
33
+
34
+
const selected: typeof allItems = [];
35
+
36
+
// --- Force highlighted item ---
37
+
if (highlight) {
38
+
const highlightedItem = allItems.find(
39
+
(item) => item.item === highlight,
40
+
);
41
+
42
+
if (!highlightedItem) {
43
+
return c.json(
44
+
{ error: `Highlighted item '${highlight}' not found` },
45
+
400,
46
+
);
47
+
}
48
+
49
+
selected.push(highlightedItem);
50
+
}
51
+
52
+
// --- Random fill ---
53
+
const pool = allItems.filter(
54
+
(item) => !selected.some((s) => s.id === item.id),
55
+
);
56
+
57
+
const targetCount =
58
+
Math.floor(Math.random() * (MAX_ITEMS - MIN_ITEMS + 1)) + MIN_ITEMS;
59
+
60
+
while (selected.length < targetCount && pool.length > 0) {
61
+
const index = Math.floor(Math.random() * pool.length);
62
+
selected.push(pool.splice(index, 1)[0]);
63
+
}
64
+
65
+
// --- Snapshot normalization ---
66
+
const items = selected.map((item) => ({
67
+
label: item.label,
68
+
icon: item.icon,
69
+
item: item.item,
70
+
price: item.price,
71
+
defaultOptions: {
72
+
quantity: item.defaultOptions.quantity,
73
+
stackLimit: item.defaultOptions.stackLimit,
74
+
},
75
+
}));
76
+
77
+
const newShop = await MinecraftShop.create({ items });
78
+
79
+
return c.json(newShop, 201);
80
+
});
+5
-4
packages/bot/.env.example
+5
-4
packages/bot/.env.example
···
1
-
BOT_TOKEN=#Get this from https://discord.com/developers - REQUIRED
2
-
BOT_CLIENT_ID=#Get this from https://discord.com/developers - REQUIRED
3
-
BOT_ADMINS=#Discord Ids of bot administrators, comma separated
4
-
DB_URI=#Your MongoDB connection URI - REQUIRED
1
+
BOT_TOKEN: #Get this from https://discord.com/developers - REQUIRED
2
+
BOT_CLIENT_ID: #Get this from https://discord.com/developers - REQUIRED
3
+
BOT_ADMINS: #Discord Ids of bot administrators, comma separated
4
+
DB_URI: #Your MongoDB connection URI - REQUIRED
5
+
VOIDY_API_TOKEN: # 123456789
+62
packages/bot/src/modules/economy/commands/minecraft/shop/generate.ts
+62
packages/bot/src/modules/economy/commands/minecraft/shop/generate.ts
···
1
+
import { MessageFlags, SlashCommandSubcommandBuilder } from "discord.js";
2
+
import type { Command } from "@voidy/framework";
3
+
4
+
export default {
5
+
id: "minecraft.shop.generate",
6
+
devOnly: true,
7
+
data: new SlashCommandSubcommandBuilder()
8
+
.setName("generate")
9
+
.setDescription("Generate a new shop for the minecraft currency integration.")
10
+
.addStringOption((option) =>
11
+
option
12
+
.setName("highlight")
13
+
.setDescription("Force pick an item to highlight.")
14
+
),
15
+
16
+
execute: async (interaction, _client) => {
17
+
await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
18
+
19
+
const highlight = interaction.options.getString("highlight");
20
+
21
+
try {
22
+
const res = await fetch(
23
+
"https://voidy.thevoid.cafe/api/v1/shop/generate",
24
+
{
25
+
method: "POST",
26
+
headers: {
27
+
"Content-Type": "application/json",
28
+
Authorization: `Bearer ${process.env.VOIDY_API_TOKEN}`,
29
+
},
30
+
body: highlight
31
+
? JSON.stringify({ highlight })
32
+
: undefined,
33
+
},
34
+
);
35
+
36
+
if (!res.ok) {
37
+
const text = await res.text();
38
+
throw new Error(`API error (${res.status}): ${text}`);
39
+
}
40
+
41
+
if (!highlight) {
42
+
await interaction.followUp({
43
+
content: "Generated new shop for the Minecraft currency integration.",
44
+
flags: [MessageFlags.Ephemeral],
45
+
});
46
+
return;
47
+
}
48
+
49
+
await interaction.followUp({
50
+
content: `Generated new shop for the Minecraft currency integration, with highlighted item: \`${highlight}\``,
51
+
flags: [MessageFlags.Ephemeral],
52
+
});
53
+
} catch (err) {
54
+
console.error("Failed to generate shop:", err);
55
+
56
+
await interaction.followUp({
57
+
content: "❌ Failed to generate a new shop. Check logs for details.",
58
+
flags: [MessageFlags.Ephemeral],
59
+
});
60
+
}
61
+
},
62
+
} as Command;
+28
packages/bot/src/modules/economy/schemas/MinecraftShop.ts
+28
packages/bot/src/modules/economy/schemas/MinecraftShop.ts
···
1
+
import { Schema, model } from "mongoose";
2
+
3
+
export const MinecraftShop = model(
4
+
"MinecraftShop",
5
+
new Schema({
6
+
createdAt: {
7
+
type: Date,
8
+
required: true,
9
+
default: Date.now,
10
+
},
11
+
items: {
12
+
type: Array,
13
+
required: true,
14
+
default: [
15
+
{
16
+
label: "Bread",
17
+
icon: "textures/items/bread",
18
+
item: "minecraft:bread",
19
+
price: 12,
20
+
defaultOptions: {
21
+
quantity: 16,
22
+
stackLimit: 64,
23
+
},
24
+
},
25
+
]
26
+
}
27
+
}),
28
+
);
+18
packages/bot/src/modules/economy/schemas/MinecraftShopItem.ts
+18
packages/bot/src/modules/economy/schemas/MinecraftShopItem.ts
···
1
+
import { Schema, model } from "mongoose";
2
+
3
+
export const MinecraftShopItem = model(
4
+
"MinecraftShopItem",
5
+
new Schema({
6
+
label: { type: String, required: true },
7
+
icon: { type: String, required: true },
8
+
item: { type: String, required: true },
9
+
price: { type: Number, required: true },
10
+
defaultOptions: {
11
+
type: {
12
+
quantity: { type: Number, required: true, default: 16 },
13
+
stackLimit: { type: Number, required: true, default: 64 },
14
+
},
15
+
required: true
16
+
},
17
+
}),
18
+
);