update to latest jaba version

This commit is contained in:
Jonny_Bro (Nikita) 2024-07-13 15:43:14 +05:00
parent 11bb890a99
commit 3cf0b475af
Signed by: jonny_bro
GPG key ID: 3F1ECC04147E9BD8
42 changed files with 6178 additions and 5022 deletions

View file

@ -1,12 +1,12 @@
<img width="150" height="150" align="left" style="float: left; margin: 0 10px 0 0;" src="https://images-ext-1.discordapp.net/external/khumNb7SPgoX2KAmnB5-37LF8Hsg_gb9BithY5gaO_w/%3Fsize%3D2048/https/cdn.discordapp.com/avatars/708637495054565426/e1e9a50ec08988d1b25c13f8bd4801bd.webp">
# JaBa Bot # JaBa Bot
![JaBa avatar](https://cdn.discordapp.com/avatars/708637495054565426/e1e9a50ec08988d1b25c13f8bd4801bd.webp?size=128)
JaBa is an open source Discord Bot written by [Jonny_Bro](https://github.com/JonnyBro) using [discord.js](https://github.com/discordjs/discord.js) and [Mongoose](https://mongoosejs.com).
[![image](https://img.shields.io/discord/892727526911258654?logo=discord&&colorB=00BFFF&label=Discord&style=flat-square)](https://discord.gg/Ptkj2n9nzZ) [![image](https://img.shields.io/discord/892727526911258654?logo=discord&&colorB=00BFFF&label=Discord&style=flat-square)](https://discord.gg/Ptkj2n9nzZ)
[![image](https://img.shields.io/badge/discord.js-v14.14.1-blue.svg?logo=npm)](https://github.com/discordjs/discord.js) [![image](https://img.shields.io/badge/discord.js-v14.14.1-blue.svg?logo=npm)](https://github.com/discordjs/discord.js)
[![image](https://www.codefactor.io/repository/github/JonnyBro/JaBa/badge)](https://www.codefactor.io/repository/github/JonnyBro/JaBa) [![image](https://www.codefactor.io/repository/github/JonnyBro/JaBa/badge)](https://www.codefactor.io/repository/github/JonnyBro/JaBa)
[![image](https://img.shields.io/github/license/JonnyBro/JaBa?label=License&style=flat-square)](https://github.com/JonnyBro/JaBa/blob/main/LICENSE)\ [![image](https://img.shields.io/github/license/JonnyBro/JaBa?label=License&style=flat-square)](https://github.com/JonnyBro/JaBa/blob/main/LICENSE)
JaBa is an open source Discord Bot written by [Jonny_Bro](https://github.com/JonnyBro) using [discord.js](https://github.com/discordjs/discord.js) and [Mongoose](https://mongoosejs.com).
## Functionality ## Functionality
@ -62,7 +62,7 @@ Use [this instruction](https://github.com/JonnyBro/JaBa/wiki/Self-Hosting) to le
If you have any questions you can ask them on my [Discord Server](https://discord.gg/NPkySYKMkN).\ If you have any questions you can ask them on my [Discord Server](https://discord.gg/NPkySYKMkN).\
If you want to contribute, feel free to fork this repo and making a pull request! If you want to contribute, feel free to fork this repo and making a pull request!
# TODO ## TODO
* [ ] Finish and release *dashboard-core* submodule. * [ ] Finish and release *dashboard-core* submodule.

View file

@ -1,5 +1,5 @@
const mongoose = require("mongoose"), const mongoose = require("mongoose"),
Canvas = require("canvas"); Canvas = require("@napi-rs/canvas");
const genToken = () => { const genToken = () => {
let token = ""; let token = "";
@ -107,7 +107,7 @@ userSchema.method("getAchievements", async function () {
dim += 200; dim += 200;
} }
return canvas.toBuffer(); return (await canvas.encode("png"));
}); });
module.exports = mongoose.model("User", userSchema); module.exports = mongoose.model("User", userSchema);

View file

@ -194,6 +194,7 @@ async function changeSetting(interaction, setting, state, channel) {
if (!state) { if (!state) {
data.plugins[settingSplitted[0]][settingSplitted[1]] = null; data.plugins[settingSplitted[0]][settingSplitted[1]] = null;
data.markModified(`plugins.${setting}`);
await data.save(); await data.save();
return interaction.reply({ return interaction.reply({
@ -206,6 +207,7 @@ async function changeSetting(interaction, setting, state, channel) {
if (channel) { if (channel) {
data.plugins[settingSplitted[0]][settingSplitted[1]] = channel.id; data.plugins[settingSplitted[0]][settingSplitted[1]] = channel.id;
data.markModified(`plugins.${setting}`);
await data.save(); await data.save();
return interaction.reply({ return interaction.reply({
@ -224,6 +226,7 @@ async function changeSetting(interaction, setting, state, channel) {
if (!state) { if (!state) {
data.plugins[setting] = null; data.plugins[setting] = null;
data.markModified(`plugins.${setting}`);
await data.save(); await data.save();
return interaction.reply({ return interaction.reply({
@ -234,6 +237,7 @@ async function changeSetting(interaction, setting, state, channel) {
if (channel) { if (channel) {
data.plugins[setting] = channel.id; data.plugins[setting] = channel.id;
data.markModified(`plugins.${setting}`);
await data.save(); await data.save();
return interaction.reply({ return interaction.reply({

View file

@ -61,16 +61,16 @@ class Goodbye extends BaseCommand {
uk: client.translate("administration/goodbye:MESSAGE", null, "uk-UA"), uk: client.translate("administration/goodbye:MESSAGE", null, "uk-UA"),
ru: client.translate("administration/goodbye:MESSAGE", null, "ru-RU"), ru: client.translate("administration/goodbye:MESSAGE", null, "ru-RU"),
}), }),
)
.addBooleanOption(option =>
option
.setName("image")
.setDescription(client.translate("administration/goodbye:IMAGE"))
.setDescriptionLocalizations({
uk: client.translate("administration/goodbye:IMAGE", null, "uk-UA"),
ru: client.translate("administration/goodbye:IMAGE", null, "ru-RU"),
}),
), ),
// .addBooleanOption(option =>
// option
// .setName("image")
// .setDescription(client.translate("administration/goodbye:IMAGE"))
// .setDescriptionLocalizations({
// uk: client.translate("administration/goodbye:IMAGE", null, "uk-UA"),
// ru: client.translate("administration/goodbye:IMAGE", null, "ru-RU"),
// }),
// ),
), ),
dirname: __dirname, dirname: __dirname,
ownerOnly: false, ownerOnly: false,
@ -83,13 +83,15 @@ class Goodbye extends BaseCommand {
* @param {import("discord.js").ChatInputCommandInteraction} interaction * @param {import("discord.js").ChatInputCommandInteraction} interaction
*/ */
async execute(client, interaction) { async execute(client, interaction) {
await interaction.deferReply({ ephemeral: true });
const guildData = interaction.data.guild, const guildData = interaction.data.guild,
command = interaction.options.getSubcommand(); command = interaction.options.getSubcommand();
if (command === "test") { if (command === "test") {
client.emit("guildMemberRemove", interaction.member); client.emit("guildMemberRemove", client, interaction.member);
interaction.success("administration/goodbye:TEST_SUCCESS", null, { ephemeral: true }); interaction.success("administration/goodbye:TEST_SUCCESS", null, { edit: true });
} else { } else {
const state = interaction.options.getBoolean("state"); const state = interaction.options.getBoolean("state");
@ -101,13 +103,14 @@ class Goodbye extends BaseCommand {
withImage: null, withImage: null,
}; };
guildData.markModified("plugins.goodbye");
await guildData.save(); await guildData.save();
interaction.success("administration/goodbye:DISABLED", null, { ephemeral: true }); interaction.success("administration/goodbye:DISABLED", null, { edit: true });
} else { } else {
const channel = interaction.options.getChannel("channel") || interaction.channel; const channel = interaction.options.getChannel("channel") || interaction.channel;
const message = interaction.options.getString("message") || interaction.translate("administration/goodbye:DEFAULT_MESSAGE"); const message = interaction.options.getString("message") || interaction.translate("administration/goodbye:DEFAULT_MESSAGE");
const image = interaction.options.getBoolean("image") === true ? true : false; const image = false; // interaction.options.getBoolean("image") || false;
guildData.plugins.goodbye = { guildData.plugins.goodbye = {
enabled: true, enabled: true,
@ -116,11 +119,12 @@ class Goodbye extends BaseCommand {
withImage: image, withImage: image,
}; };
guildData.markModified("plugins.goodbye");
await guildData.save(); await guildData.save();
interaction.success("administration/goodbye:ENABLED", { interaction.success("administration/goodbye:ENABLED", {
channel: `${channel.toString()}`, channel: `${channel.toString()}`,
}, { ephemeral: true }); }, { edit: true });
} }
} }
} }

View file

@ -61,16 +61,16 @@ class Welcome extends BaseCommand {
uk: client.translate("administration/goodbye:MESSAGE", null, "uk-UA"), uk: client.translate("administration/goodbye:MESSAGE", null, "uk-UA"),
ru: client.translate("administration/goodbye:MESSAGE", null, "ru-RU"), ru: client.translate("administration/goodbye:MESSAGE", null, "ru-RU"),
}), }),
)
.addBooleanOption(option =>
option
.setName("image")
.setDescription(client.translate("administration/goodbye:IMAGE"))
.setDescriptionLocalizations({
uk: client.translate("administration/goodbye:IMAGE", null, "uk-UA"),
ru: client.translate("administration/goodbye:IMAGE", null, "ru-RU"),
}),
), ),
// .addBooleanOption(option =>
// option
// .setName("image")
// .setDescription(client.translate("administration/goodbye:IMAGE"))
// .setDescriptionLocalizations({
// uk: client.translate("administration/goodbye:IMAGE", null, "uk-UA"),
// ru: client.translate("administration/goodbye:IMAGE", null, "ru-RU"),
// }),
// ),
), ),
dirname: __dirname, dirname: __dirname,
ownerOnly: false, ownerOnly: false,
@ -83,13 +83,15 @@ class Welcome extends BaseCommand {
* @param {import("discord.js").ChatInputCommandInteraction} interaction * @param {import("discord.js").ChatInputCommandInteraction} interaction
*/ */
async execute(client, interaction) { async execute(client, interaction) {
await interaction.deferReply({ ephemeral: true });
const guildData = interaction.data.guild, const guildData = interaction.data.guild,
command = interaction.options.getSubcommand(); command = interaction.options.getSubcommand();
if (command === "test") { if (command === "test") {
client.emit("guildMemberAdd", interaction.member); client.emit("guildMemberAdd", client, interaction.member);
interaction.success("administration/goodbye:TEST_SUCCESS", null, { ephemeral: true }); interaction.success("administration/goodbye:TEST_SUCCESS", null, { edit: true });
} else { } else {
const state = interaction.options.getBoolean("state"); const state = interaction.options.getBoolean("state");
@ -101,13 +103,14 @@ class Welcome extends BaseCommand {
withImage: null, withImage: null,
}; };
guildData.markModified("plugins.welcome");
await guildData.save(); await guildData.save();
interaction.success("administration/welcome:DISABLED", null, { ephemeral: true }); interaction.success("administration/welcome:DISABLED", null);
} else { } else {
const channel = interaction.options.getChannel("channel") || interaction.channel; const channel = interaction.options.getChannel("channel") || interaction.channel;
const message = interaction.options.getString("message") || interaction.translate("administration/welcome:DEFAULT_MESSAGE"); const message = interaction.options.getString("message") || interaction.translate("administration/welcome:DEFAULT_MESSAGE");
const image = interaction.options.getBoolean("image") === true ? true : false; const image = false; // interaction.options.getBoolean("image") || false;
guildData.plugins.welcome = { guildData.plugins.welcome = {
enabled: true, enabled: true,
@ -116,11 +119,12 @@ class Welcome extends BaseCommand {
withImage: image, withImage: image,
}; };
guildData.markModified("plugins.welcome");
await guildData.save(); await guildData.save();
interaction.success("administration/welcome:ENABLED", { interaction.success("administration/welcome:ENABLED", {
channel: `${channel.toString()}`, channel: `${channel.toString()}`,
}, { ephemeral: true }); }, { edit: true });
} }
} }
} }

View file

@ -28,16 +28,13 @@ class Work extends BaseCommand {
*/ */
async execute(client, interaction) { async execute(client, interaction) {
const { member: memberData, user: userData } = interaction.data, const { member: memberData, user: userData } = interaction.data,
isInCooldown = memberData.cooldowns?.work; cooldown = memberData.cooldowns?.work,
now = Date.now();
if (isInCooldown && isInCooldown > Date.now()) if (now < cooldown) return interaction.error("economy/work:COOLDOWN", { time: `<t:${Math.floor(cooldown / 1000)}:R>` });
return interaction.error("economy/work:COOLDOWN", { if (Math.abs(cooldown - now) > 30 * 60 * 60 * 1000) memberData.workStreak = 0;
time: `<t:${Math.floor(isInCooldown / 1000)}:R>`,
});
if (Date.now() > memberData.cooldowns.work + 30 * 60 * 60 * 1000) memberData.workStreak = 0; memberData.cooldowns.work = now + 24 * 60 * 60 * 1000;
memberData.cooldowns.work = Date.now() + 24 * 60 * 60 * 1000;
memberData.workStreak = (memberData.workStreak || 0) + 1; memberData.workStreak = (memberData.workStreak || 0) + 1;
const embed = client.embed({ const embed = client.embed({

View file

@ -124,7 +124,7 @@ class Boosters extends BaseCommand {
const boosters = (await interaction.guild.members.fetch()).filter(m => m.premiumSince); const boosters = (await interaction.guild.members.fetch()).filter(m => m.premiumSince);
if (boosters.size === 0) return interaction.error("general/boosters:NO_BOOSTERS", null, { edit: true }); if (boosters.size === 0) return interaction.error("general/boosters:NO_BOOSTERS", null, { edit: true });
const embeds = generateBoostersEmbeds(client, interaction, boosters); const embeds = generateBoostersEmbeds(interaction, boosters);
const row = new ActionRowBuilder().addComponents( const row = new ActionRowBuilder().addComponents(
new ButtonBuilder().setCustomId("boosters_prev_page").setStyle(ButtonStyle.Primary).setEmoji("⬅️"), new ButtonBuilder().setCustomId("boosters_prev_page").setStyle(ButtonStyle.Primary).setEmoji("⬅️"),
@ -159,7 +159,7 @@ function generateBoostersEmbeds(interaction, boosters) {
let j = i; let j = i;
k += 10; k += 10;
const info = current.map(member => `${++j}. ${member.toString()} | ${interaction.translate("general/boosters:BOOSTER_SINCE")}: **${Math.floor(new Date(member.premiumSince).getTime() / 1000)}**`).join("\n"); const info = current.map(member => `${++j}. ${member.toString()} | ${interaction.translate("general/boosters:BOOSTER_SINCE")}: <t:${Math.floor(member.premiumSinceTimestamp / 1000)}:f>`).join("\n");
const embed = interaction.client.embed({ const embed = interaction.client.embed({
title: interaction.translate("general/boosters:BOOSTERS_LIST"), title: interaction.translate("general/boosters:BOOSTERS_LIST"),

View file

@ -33,14 +33,13 @@ class CreateTicketEmbed extends BaseCommand {
interaction.data = []; interaction.data = [];
interaction.data.guild = await client.getGuildData(interaction.guildId); interaction.data.guild = await client.getGuildData(interaction.guildId);
const guildData = interaction.data.guild,
ticketsCategory = guildData.plugins?.tickets?.ticketsCategory,
ticketLogs = guildData.plugins?.tickets?.ticketLogs,
transcriptionLogs = guildData.plugins?.tickets?.transcriptionLogs;
const button = interaction.component; const button = interaction.component;
if (button.customId === "support_ticket") { if (button.customId === "support_ticket") {
const guildData = interaction.data.guild,
ticketsCategory = guildData.plugins.tickets.ticketsCategory,
ticketLogs = guildData.plugins.tickets.ticketLogs;
if (interaction.guild.channels.cache.get(ticketsCategory).children.cache.size >= 50) { if (interaction.guild.channels.cache.get(ticketsCategory).children.cache.size >= 50) {
const sorted = interaction.guild.channels.cache.get(ticketsCategory).children.cache.sort((ch1, ch2) => ch1.createdTimestamp - ch2.createdTimestamp); const sorted = interaction.guild.channels.cache.get(ticketsCategory).children.cache.sort((ch1, ch2) => ch1.createdTimestamp - ch2.createdTimestamp);
@ -137,8 +136,10 @@ class CreateTicketEmbed extends BaseCommand {
collector.on("end", async (_, reason) => { collector.on("end", async (_, reason) => {
if (reason !== "canceled") { if (reason !== "canceled") {
const reversedMessages = (await interaction.channel.messages.fetch()).filter(m => !m.author.bot); const reversedMessages = (await interaction.channel.messages.fetch()).filter(m => !m.author.bot),
const messages = Array.from(reversedMessages.values()).reverse(); messages = Array.from(reversedMessages.values()).reverse(),
transcriptionLogs = interaction.data.guild.plugins.tickets.transcriptionLogs,
ticketLogs = interaction.data.guild.plugins.tickets.ticketLogs;
if (messages.length > 1) { if (messages.length > 1) {
let transcript = "---- TICKET CREATED ----\n"; let transcript = "---- TICKET CREATED ----\n";

View file

@ -1,6 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies # dependencies
/node_modules
/.pnp /.pnp
.pnp.js .pnp.js

View file

@ -1,8 +1,6 @@
# Discord Bot Dashboard Template # Discord Bot Dashboard Template
> Made By <https://github.com/fuma-nama> > Forked from [here](https://github.com/FileEditor97/discord-bot-dashboard)
![banner](./document/preview-new.png)
Using Typescript, Next.js 13, React 18 and Chakra ui 2.0 Using Typescript, Next.js 13, React 18 and Chakra ui 2.0
@ -168,9 +166,7 @@ Moreover, you can use redis instead of connecting to the bot server directly
The client will pass their access token via the `Authorization` header The client will pass their access token via the `Authorization` header
```js `Bearer MY_TOKEN_1212112`
Bearer MY_TOKEN_1212112
```
### Required Routes ### Required Routes

View file

@ -9,7 +9,7 @@ const nextConfig = {
]; ];
}, },
i18n: { i18n: {
locales: ['en', 'cn'], locales: ['en'],
defaultLocale: 'en', defaultLocale: 'en',
}, },
}; };

View file

@ -1,6 +1,11 @@
{ {
"name": "dashboard", "name": "nohi-dashboard",
"version": "1.2.0", "version": "0.1.0",
"license": "MIT",
"author": "Fuma Nama (https://github.com/fuma-nama)",
"maintainers": [
"FileEditor97 (https://github.com/fileeditor97)"
],
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
@ -20,35 +25,34 @@
"@chakra-ui/theme-tools": "^2.0.0", "@chakra-ui/theme-tools": "^2.0.0",
"@emotion/react": "^11.8.1", "@emotion/react": "^11.8.1",
"@emotion/styled": "^11.3.0", "@emotion/styled": "^11.3.0",
"@hookform/resolvers": "^2.9.11", "@hookform/resolvers": "^3.9.0",
"@tanstack/react-query": "^4.2.3", "@tanstack/react-query": "^5.50.1",
"apexcharts": "^3.36.3", "apexcharts": "^3.36.3",
"chakra-react-select": "^4.4.2", "chakra-react-select": "^4.4.2",
"cookies-next": "^2.1.1", "cookies-next": "^2.1.1",
"deepmerge-ts": "^4.2.2", "deepmerge-ts": "^7.0.3",
"framer-motion": "^6.0.0", "framer-motion": "^11.0.0",
"next": "^13.2.1", "next": "^14.2.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-apexcharts": "^1.4.0", "react-apexcharts": "^1.4.0",
"react-calendar": "^3.7.0", "react-calendar": "^5.0.0",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",
"react-dom": "^18.0.0", "react-dom": "^18.0.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",
"react-hook-form": "^7.43.2", "react-hook-form": "^7.43.2",
"react-icons": "^4.3.1", "react-icons": "^5.2.1",
"zod": "^3.20.6", "zod": "^3.20.6",
"zustand": "^4.4.6" "zustand": "^4.4.6"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.21.0", "@babel/core": "^7.21.0",
"@types/node": "16.11.7", "@types/node": "20.14.10",
"@types/react": "^18.0.0", "@types/react": "^18.0.0",
"@types/react-calendar": "^3.5.2",
"@types/react-dom": "^18.0.0", "@types/react-dom": "^18.0.0",
"eslint": "^8.0.0", "eslint": "^8.0.0",
"eslint-config-next": "^13.2.1", "eslint-config-next": "^14.2.4",
"eslint-config-prettier": "8.1.0", "eslint-config-prettier": "9.1.0",
"prettier": "^2.6.2", "prettier": "^3.3.2",
"typescript": "^4.8.2" "typescript": "^5.5.3"
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
import { CustomFeatures, CustomGuildInfo } from '../config/types'; import { CustomFeatures, CustomGuildInfo } from '../config/types';
import { QueryClient, useMutation, useQuery } from '@tanstack/react-query'; import { QueryClient, useMutation, useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { UserInfo, getGuild, getGuilds, fetchUserInfo } from '@/api/discord'; import { UserInfo, getGuild, getGuilds, fetchUserInfo } from '@/api/discord';
import { import {
disableFeature, disableFeature,
@ -41,48 +41,54 @@ export const Mutations = {
export function useGuild(id: string) { export function useGuild(id: string) {
const accessToken = useAccessToken(); const accessToken = useAccessToken();
return useQuery(['guild', id], () => getGuild(accessToken as string, id), { return useQuery({
enabled: accessToken != null, queryKey: ['guild', id],
queryFn: () => getGuild(accessToken as string, id),
enabled: accessToken != null
}); });
} }
export function useGuilds() { export function useGuilds() {
const accessToken = useAccessToken(); const accessToken = useAccessToken();
return useQuery(['user_guilds'], () => getGuilds(accessToken as string), { return useQuery({
enabled: accessToken != null, queryKey: ['user_guilds'],
queryFn: () => getGuilds(accessToken as string),
enabled: accessToken != null
}); });
} }
export function useSelfUserQuery() { export function useSelfUserQuery() {
const accessToken = useAccessToken(); const accessToken = useAccessToken();
return useQuery<UserInfo>(['users', 'me'], () => fetchUserInfo(accessToken!!), { return useQuery<UserInfo>({
queryKey: ['users', 'me'],
queryFn: () => fetchUserInfo(accessToken!!),
enabled: accessToken != null, enabled: accessToken != null,
staleTime: Infinity, staleTime: Infinity
}); });
} }
export function useGuildInfoQuery(guild: string) { export function useGuildInfoQuery(guild: string) {
const { status, session } = useSession(); const { status, session } = useSession();
return useQuery<CustomGuildInfo | null>( return useQuery<CustomGuildInfo | null>({
Keys.guild_info(guild), queryKey: Keys.guild_info(guild),
() => fetchGuildInfo(session!!, guild), queryFn: () => fetchGuildInfo(session!!, guild),
{ enabled: status === 'authenticated',
enabled: status === 'authenticated', refetchOnWindowFocus: true,
refetchOnWindowFocus: true, retry: false,
retry: false, staleTime: 0
staleTime: 0, });
}
);
} }
export function useFeatureQuery<K extends keyof CustomFeatures>(guild: string, feature: K) { export function useFeatureQuery<K extends keyof CustomFeatures>(guild: string, feature: K) {
// eslint-disable-next-line no-unused-vars
const { status, session } = useSession(); const { status, session } = useSession();
return useQuery(Keys.features(guild, feature), () => getFeature(session!!, guild, feature), { return useSuspenseQuery({
enabled: status === 'authenticated', queryKey: Keys.features(guild, feature),
queryFn: () => getFeature(session!!, guild, feature)
}); });
} }
@ -90,34 +96,32 @@ export type EnableFeatureOptions = { guild: string; feature: string; enabled: bo
export function useEnableFeatureMutation() { export function useEnableFeatureMutation() {
const { session } = useSession(); const { session } = useSession();
return useMutation( return useMutation({
async ({ enabled, guild, feature }: EnableFeatureOptions) => { mutationFn: async ({ enabled, guild, feature }: EnableFeatureOptions) => {
if (enabled) return enableFeature(session!!, guild, feature); if (enabled) return enableFeature(session!!, guild, feature);
return disableFeature(session!!, guild, feature); return disableFeature(session!!, guild, feature);
}, },
{ async onSuccess(_, { guild, feature, enabled }) {
async onSuccess(_, { guild, feature, enabled }) { await client.invalidateQueries({queryKey: Keys.features(guild, feature)});
await client.invalidateQueries(Keys.features(guild, feature)); client.setQueryData<GuildInfo | null>(Keys.guild_info(guild), (prev) => {
client.setQueryData<GuildInfo | null>(Keys.guild_info(guild), (prev) => { if (prev == null) return null;
if (prev == null) return null;
if (enabled) { if (enabled) {
return { return {
...prev, ...prev,
enabledFeatures: prev.enabledFeatures.includes(feature) enabledFeatures: prev.enabledFeatures.includes(feature)
? prev.enabledFeatures ? prev.enabledFeatures
: [...prev.enabledFeatures, feature], : [...prev.enabledFeatures, feature],
}; };
} else { } else {
return { return {
...prev, ...prev,
enabledFeatures: prev.enabledFeatures.filter((f) => f !== feature), enabledFeatures: prev.enabledFeatures.filter((f) => f !== feature),
}; };
} }
}); });
}, },
} });
);
} }
export type UpdateFeatureOptions = { export type UpdateFeatureOptions = {
@ -128,29 +132,33 @@ export type UpdateFeatureOptions = {
export function useUpdateFeatureMutation() { export function useUpdateFeatureMutation() {
const { session } = useSession(); const { session } = useSession();
return useMutation( return useMutation({
(options: UpdateFeatureOptions) => mutationFn: (options: UpdateFeatureOptions) =>
updateFeature(session!!, options.guild, options.feature, options.options), updateFeature(session!!, options.guild, options.feature, options.options),
{ onSuccess(updated, options) {
onSuccess(updated, options) { const key = Keys.features(options.guild, options.feature);
const key = Keys.features(options.guild, options.feature);
return client.setQueryData(key, updated); return client.setQueryData(key, updated);
}, },
} });
);
} }
export function useGuildRolesQuery(guild: string) { export function useGuildRolesQuery(guild: string) {
const { session } = useSession(); const { session } = useSession();
return useQuery(Keys.guildRoles(guild), () => fetchGuildRoles(session!!, guild)); return useQuery({
queryKey: Keys.guildRoles(guild),
queryFn: () => fetchGuildRoles(session!!, guild)
});
} }
export function useGuildChannelsQuery(guild: string) { export function useGuildChannelsQuery(guild: string) {
const { session } = useSession(); const { session } = useSession();
return useQuery(Keys.guildChannels(guild), () => fetchGuildChannels(session!!, guild)); return useQuery({
queryKey: Keys.guildChannels(guild),
queryFn: () => fetchGuildChannels(session!!, guild)
});
} }
export function useSelfUser(): UserInfo { export function useSelfUser(): UserInfo {

View file

@ -46,7 +46,7 @@ export function FeatureItem({
<CardFooter as={ButtonGroup} mt={3}> <CardFooter as={ButtonGroup} mt={3}>
<Button <Button
size={{ base: 'sm', md: 'md' }} size={{ base: 'sm', md: 'md' }}
disabled={mutation.isLoading} disabled={mutation.isPending}
{...(enabled {...(enabled
? { ? {
variant: 'action', variant: 'action',

View file

@ -41,14 +41,14 @@ export function UpdateFeaturePanel({
<Text color="TextSecondary">{config.description}</Text> <Text color="TextSecondary">{config.description}</Text>
</Box> </Box>
<ButtonGroup mt={3}> <ButtonGroup mt={3}>
<Button variant="danger" isLoading={enableMutation.isLoading} onClick={onDisable}> <Button variant="danger" isLoading={enableMutation.isPending} onClick={onDisable}>
<view.T text={(e) => e.bn.disable} /> <view.T text={(e) => e.bn.disable} />
</Button> </Button>
</ButtonGroup> </ButtonGroup>
</Flex> </Flex>
{result.component} {result.component}
<Savebar isLoading={mutation.isLoading} result={result} /> <Savebar isLoading={mutation.isPending} result={result} />
</Flex> </Flex>
); );
} }

View file

@ -1,5 +1,5 @@
// Chakra imports // Chakra imports
import { Box, HStack, Icon, Spacer, Text } from '@chakra-ui/react'; import { Box, HStack, Image, Spacer, Text } from '@chakra-ui/react';
import { config } from '@/config/common'; import { config } from '@/config/common';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { SelectField } from '../forms/SelectField'; import { SelectField } from '../forms/SelectField';
@ -19,7 +19,7 @@ export default function AuthLayout({ children }: { children: ReactNode }) {
px={{ base: 5, lg: 10 }} px={{ base: 5, lg: 10 }}
py={2} py={2}
> >
{config.icon != null && <Icon color="TextPrimary" as={config.icon} w={10} h={10} />} {config.icon != null && <Image src={config.icon} boxSize={10} alt='Logo'/>}
<Text fontWeight="600" fontSize="lg"> <Text fontWeight="600" fontSize="lg">
{config.name} {config.name}
</Text> </Text>

View file

@ -1,4 +1,3 @@
import { Box, Center } from '@chakra-ui/layout';
import AppLayout from '../app'; import AppLayout from '../app';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import GuildNavbar from './guild-navbar'; import GuildNavbar from './guild-navbar';

View file

@ -1,6 +1,6 @@
import { FaChevronLeft as ChevronLeftIcon } from 'react-icons/fa'; import { FaChevronLeft as ChevronLeftIcon } from 'react-icons/fa';
import { Box, Flex, Text } from '@chakra-ui/layout'; import { Box, Flex, Text } from '@chakra-ui/layout';
import { Avatar, Icon, IconButton, SkeletonCircle } from '@chakra-ui/react'; import { Avatar, Icon, SkeletonCircle } from '@chakra-ui/react';
import { iconUrl } from '@/api/discord'; import { iconUrl } from '@/api/discord';
import { useGuildPreview } from '@/api/hooks'; import { useGuildPreview } from '@/api/hooks';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';

View file

@ -1,29 +1,12 @@
import { createIcon } from '@chakra-ui/react';
import { PermissionFlags } from '@/api/discord'; import { PermissionFlags } from '@/api/discord';
import { AppConfig } from './types'; import { AppConfig } from './types';
const BotIcon = createIcon({
displayName: 'OmagizeLogo',
viewBox: '0 0 512 512',
path: (
<g>
<path
d="m494.6,241.1l-50.1-47c-50.5-47.3-117.4-73.3-188.5-73.3-71.1,0-138,26-188.4,73.3l-50.1,47c-12.1,12.9-4.3,26.5 0,29.8l50.1,47c50.4,47.3 117.3,73.3 188.4,73.3 71.1,0 138-26 188.4-73.3l50.1-47c4.7-3.9 12.2-17.6 0.1-29.8zm-238.6,74.9c-33.1,0-60-26.9-60-60 0-33.1 26.9-60 60-60 33.1,0 60,26.9 60,60 0,33.1-26.9,60-60,60zm-194.7-60l34.3-32.1c32-30 72-49.9 115.5-58.1-33.1,16.6-55.8,50.8-55.8,90.2 0,39.4 22.8,73.7 55.8,90.2-43.5-8.1-83.5-28.1-115.5-58.1l-34.3-32.1zm355.2,32.1c-32,30-72,50-115.5,58.1 33.1-16.6 55.8-50.8 55.8-90.2 0-39.4-22.8-73.6-55.8-90.2 43.5,8.1 83.5,28.1 115.5,58.1l34.3,32.1-34.3,32.1z"
fill="currentColor"
/>
<path
d="m256,235.2c-11.3,0-20.8,9.5-20.8,20.8 0,11.3 9.5,20.8 20.8,20.8 11.3,0 20.8-9.5 20.8-20.8 0-11.3-9.5-20.8-20.8-20.8z"
fill="currentColor"
/>
</g>
),
});
export const config: AppConfig = { export const config: AppConfig = {
name: 'Demo Bot', name: 'VOTL Bot',
icon: BotIcon, icon:
'https://cdn.fileeditor.dev/media/votl/logo.png',
inviteUrl: inviteUrl:
'https://discord.com/api/oauth2/authorize?client_id=1070011901385375845&permissions=8&scope=bot', 'https://discord.com/oauth2/authorize?client_id=397461072342417408&permissions=8&integration_type=0&scope=bot+applications.commands',
guild: { guild: {
//filter guilds that user has no permissions to manage it //filter guilds that user has no permissions to manage it
filter: (guild) => (Number(guild.permissions) & PermissionFlags.ADMINISTRATOR) !== 0, filter: (guild) => (Number(guild.permissions) & PermissionFlags.ADMINISTRATOR) !== 0,

View file

@ -23,16 +23,6 @@ const { T } = createI18n(provider, {
memes: 'Memes Time', memes: 'Memes Time',
'memes description': 'Send memes everyday', 'memes description': 'Send memes everyday',
}, },
cn: {
music: '音樂播放器',
'music description': '在您的 Discord 服務器中播放音樂',
gaming: '遊戲',
'gaming description': 'Enjoy playing games with your friends',
'reaction role': '反應角色',
'reaction role description': '單擊按鈕時為用戶賦予角色',
memes: '模因時間',
'memes description': '每天發送模因',
},
}); });
/** /**

View file

@ -7,9 +7,4 @@ export const auth = createI18n(provider, {
'login description': 'Login and start using our bot today', 'login description': 'Login and start using our bot today',
login_bn: 'Login with Discord', login_bn: 'Login with Discord',
}, },
cn: {
login: '登入控制面板',
'login description': '登錄並開始使用我們的機器人',
login_bn: '使用 Discord 登錄',
},
}); });

View file

@ -13,15 +13,4 @@ export const common = createI18n(provider, {
pages: 'Pages', pages: 'Pages',
logout: 'Logout', logout: 'Logout',
}, },
cn: {
loading: '加載中',
search: '搜索',
'select lang': '選擇你的語言',
'select role': '選擇身份組',
'select channel': '選擇頻道',
dashboard: '儀表板',
profile: '用戶資料',
pages: '所有頁面',
logout: '登出',
},
}); });

View file

@ -23,25 +23,4 @@ export const dashboard = createI18n(provider, {
description: 'Use of commands of your server', description: 'Use of commands of your server',
}, },
}, },
cn: {
pricing: '價錢',
learn_more: '了解更多',
invite: {
title: '邀請我們的機器人',
description: '一鍵試用我們的 Discord 機器人',
bn: '現在邀請',
},
servers: {
title: '選擇服務器',
description: '自定義您的服務器',
},
vc: {
create: '創建語音通道',
'created channels': '已創建語音頻道',
},
command: {
title: '命令使用量',
description: '使用你的服務器命令使用量',
},
},
}); });

View file

@ -17,19 +17,4 @@ export const feature = createI18n(provider, {
discard: 'Discard', discard: 'Discard',
}, },
}, },
cn: {
unsaved: '您有未保存的更改',
error: {
'not enabled': '未啟用',
'not enabled description': '嘗試啟用此功能?',
'not found': '未找到功能',
'not found description': '奇怪...我們找不到它',
},
bn: {
enable: '啟用功能',
disable: '關閉功能',
save: '保存更改',
discard: '放棄',
},
},
}); });

View file

@ -20,22 +20,4 @@ export const guild = createI18n(provider, {
settings: 'Settings', settings: 'Settings',
}, },
}, },
cn: {
features: '管理機器人功能',
banner: {
title: '立即免費試用',
description: '為您的服務器定制機器人',
},
error: {
'not found': '它在哪裡?',
'not found description': '機器人無法訪問服務器,我們邀請他吧!',
load: '無法加載服務器',
},
bn: {
'enable feature': '啟用功能',
'config feature': '配置',
invite: '邀請機器人',
settings: '設置',
},
},
}); });

View file

@ -13,14 +13,4 @@ export const profile = createI18n(provider, {
'dev mode': 'Developer Mode', 'dev mode': 'Developer Mode',
'dev mode description': 'Used for debugging and testing', 'dev mode description': 'Used for debugging and testing',
}, },
cn: {
logout: common.translations.cn.logout,
language: '你的語言',
'language description': '選擇你的語言',
settings: '設置',
'dark mode': '黑暗模式',
'dark mode description': '啟用深色主題可以保護您的眼睛',
'dev mode': '開發者模式',
'dev mode description': '用於調試和測試',
},
}); });

View file

@ -4,10 +4,9 @@ import Router, { useRouter } from 'next/router';
/** /**
* Supported languages * Supported languages
*/ */
export type Languages = 'en' | 'cn'; export type Languages = 'en';
export const { languages, names } = initLanguages<Languages>({ export const { languages, names } = initLanguages<Languages>({
en: 'English', en: 'English',
cn: '中文',
}); });
export const provider = initI18n<Languages>({ export const provider = initI18n<Languages>({

View file

@ -8,9 +8,9 @@ export type AppConfig = {
*/ */
name: string; name: string;
/** /**
* icon (react component) * Image Url
*/ */
icon?: (props: any) => ReactElement; icon: string;
/** /**
* Guild settings * Guild settings
*/ */

View file

@ -45,7 +45,7 @@ function NotEnabled() {
<Text color="TextSecondary">{t.error['not enabled description']}</Text> <Text color="TextSecondary">{t.error['not enabled description']}</Text>
<Button <Button
mt={3} mt={3}
isLoading={enable.isLoading} isLoading={enable.isPending}
onClick={() => enable.mutate({ enabled: true, guild, feature })} onClick={() => enable.mutate({ enabled: true, guild, feature })}
variant="action" variant="action"
px={6} px={6}

View file

@ -50,7 +50,7 @@ export function GuildSelect() {
</Button> </Button>
); );
if (guilds.status === 'loading') if (guilds.status === 'pending')
return ( return (
<SimpleGrid columns={{ base: 1, md: 2, xl: 3 }} gap={3}> <SimpleGrid columns={{ base: 1, md: 2, xl: 3 }} gap={3}>
<Skeleton minH="88px" rounded="2xl" /> <Skeleton minH="88px" rounded="2xl" />

View file

@ -105,7 +105,7 @@ const ProfilePage: NextPageWithLayout = () => {
<Button <Button
leftIcon={<IoLogOut />} leftIcon={<IoLogOut />}
variant="danger" variant="danger"
isLoading={logout.isLoading} isLoading={logout.isPending}
onClick={() => logout.mutate()} onClick={() => logout.mutate()}
> >
{t.logout} {t.logout}

View file

@ -22,7 +22,7 @@ export async function logout() {
}, },
}); });
await client.invalidateQueries(Keys.login); await client.invalidateQueries({queryKey: Keys.login});
await Router.push('/auth/signin'); await Router.push('/auth/signin');
} }
@ -37,7 +37,10 @@ type SessionResult =
}; };
export function useSession(): SessionResult { export function useSession(): SessionResult {
const { isError, isLoading, data } = useQuery(Keys.login, () => auth()); const { isError, isLoading, data } = useQuery({
queryKey: Keys.login,
queryFn: () => auth()
});
if (isError) if (isError)
return { return {
@ -53,7 +56,7 @@ export function useSession(): SessionResult {
return { return {
status: 'authenticated', status: 'authenticated',
session: data, session: data!,
}; };
} }
@ -64,5 +67,8 @@ export function useAccessToken() {
} }
export function useLogoutMutation() { export function useLogoutMutation() {
return useMutation(['logout'], () => logout()); return useMutation({
mutationKey: ['logout'],
mutationFn: () => logout()
});
} }

View file

@ -44,6 +44,7 @@ export function getServerSession(
} }
export function setServerSession(req: NextApiRequest, res: NextApiResponse, data: AccessToken) { export function setServerSession(req: NextApiRequest, res: NextApiResponse, data: AccessToken) {
// @ts-ignore
setCookie(TokenCookie, data, { req, res, ...options }); setCookie(TokenCookie, data, { req, res, ...options });
} }
@ -51,6 +52,7 @@ export async function removeSession(req: NextApiRequest, res: NextApiResponse) {
const session = getServerSession(req); const session = getServerSession(req);
if (session.success) { if (session.success) {
// @ts-ignore
deleteCookie(TokenCookie, { req, res, ...options }); deleteCookie(TokenCookie, { req, res, ...options });
await revokeToken(session.data.access_token); await revokeToken(session.data.access_token);
} }

View file

@ -3,7 +3,7 @@ import { AccessToken } from '@/utils/auth/server';
import { Options } from './core'; import { Options } from './core';
const bot_api_endpoint = process.env.NEXT_PUBLIC_API_ENDPOINT ?? 'http://localhost:8080'; const bot_api_endpoint = process.env.NEXT_PUBLIC_API_ENDPOINT ?? 'http://localhost:8080';
const discord_api_endpoint = 'https://discord.com/api/v9'; const discord_api_endpoint = 'https://discord.com/api/v10';
export function botRequest<T extends Options>(session: AccessToken, options: T): T { export function botRequest<T extends Options>(session: AccessToken, options: T): T {
return { return {

View file

@ -58,13 +58,14 @@ class CommandHandler extends BaseEvent {
} }
client.logger.cmd( client.logger.cmd(
`User ${interaction.user.getUsername()} used ${command.command.name} in ${interaction.guild ? interaction.guild.name : "DM"} with arguments: ${ `[${interaction.guild ? interaction.guild.name : "DM"}]: [${interaction.user.getUsername()}] => /${command.command.name}${
interaction.options.data.length > 0 interaction.options.data.length > 0
? interaction.options.data ? `, args: [${interaction.options.data
.map(arg => { .map(arg => {
return `${arg.name}: ${arg.value}`; return `${arg.name}: ${arg.value}`;
}).join(", ") })
: "no args" .join(", ")}]`
: ""
}`, }`,
); );

View file

@ -1,18 +1,9 @@
const Canvas = require("canvas"), // const Canvas = require("@napi-rs/canvas"),
BaseEvent = require("../../base/BaseEvent"), // BaseEvent = require("../../base/BaseEvent"),
{ AttachmentBuilder } = require("discord.js"), // { AttachmentBuilder } = require("discord.js"),
{ resolve } = require("path"); // { applyText } = require("../../helpers/functions");
Canvas.registerFont(resolve("./assets/fonts/RubikMonoOne-Regular.ttf"), { family: "RubikMonoOne" }); const BaseEvent = require("../../base/BaseEvent");
Canvas.registerFont(resolve("./assets/fonts/KeepCalm-Medium.ttf"), { family: "KeepCalm" });
const applyText = (canvas, text, defaultFontSize, width, font) => {
const ctx = canvas.getContext("2d");
do ctx.font = `${(defaultFontSize -= 1)}px ${font}`;
while (ctx.measureText(text).width > width);
return ctx.font;
};
class GuildMemberAdd extends BaseEvent { class GuildMemberAdd extends BaseEvent {
constructor() { constructor() {
@ -45,6 +36,7 @@ class GuildMemberAdd extends BaseEvent {
.replace(/{server}/g, member.guild.name) .replace(/{server}/g, member.guild.name)
.replace(/{membercount}/g, member.guild.memberCount); .replace(/{membercount}/g, member.guild.memberCount);
/*
if (guildData.plugins.welcome.withImage) { if (guildData.plugins.welcome.withImage) {
const canvas = Canvas.createCanvas(1024, 450), const canvas = Canvas.createCanvas(1024, 450),
ctx = canvas.getContext("2d"); ctx = canvas.getContext("2d");
@ -132,14 +124,15 @@ class GuildMemberAdd extends BaseEvent {
); );
ctx.drawImage(avatar, 45, 90, 270, 270); ctx.drawImage(avatar, 45, 90, 270, 270);
const attachment = new AttachmentBuilder(canvas.toBuffer(), { name: "welcome-image.png" }); const attachment = new AttachmentBuilder((await canvas.encode("png")), { name: "welcome.png" });
channel.send({ channel.send({
content: message, content: message,
files: [attachment], files: [attachment],
}); });
} else } else */
channel.send({ content: message });
channel.send({ content: message });
} }
} }
} }

View file

@ -1,18 +1,9 @@
const Canvas = require("canvas"), // const Canvas = require("@napi-rs/canvas"),
BaseEvent = require("../../base/BaseEvent"), // BaseEvent = require("../../base/BaseEvent"),
{ AttachmentBuilder } = require("discord.js"), // { AttachmentBuilder } = require("discord.js"),
{ resolve } = require("path"); // { applyText } = require("../../helpers/functions");
Canvas.registerFont(resolve("./assets/fonts/RubikMonoOne-Regular.ttf"), { family: "RubikMonoOne" }); const BaseEvent = require("../../base/BaseEvent");
Canvas.registerFont(resolve("./assets/fonts/KeepCalm-Medium.ttf"), { family: "KeepCalm" });
const applyText = (canvas, text, defaultFontSize, width, font) => {
const ctx = canvas.getContext("2d");
do ctx.font = `${(defaultFontSize -= 1)}px ${font}`;
while (ctx.measureText(text).width > width);
return ctx.font;
};
class GuildMemberRemove extends BaseEvent { class GuildMemberRemove extends BaseEvent {
constructor() { constructor() {
@ -43,6 +34,7 @@ class GuildMemberRemove extends BaseEvent {
.replace(/{server}/g, member.guild.name) .replace(/{server}/g, member.guild.name)
.replace(/{membercount}/g, member.guild.memberCount); .replace(/{membercount}/g, member.guild.memberCount);
/*
if (guildData.plugins.goodbye.withImage) { if (guildData.plugins.goodbye.withImage) {
const canvas = Canvas.createCanvas(1024, 450), const canvas = Canvas.createCanvas(1024, 450),
ctx = canvas.getContext("2d"); ctx = canvas.getContext("2d");
@ -135,14 +127,15 @@ class GuildMemberRemove extends BaseEvent {
); );
ctx.drawImage(avatar, 45, 90, 270, 270); ctx.drawImage(avatar, 45, 90, 270, 270);
const attachment = new AttachmentBuilder(canvas.toBuffer(), { name: "goodbye-image.png" }); const attachment = new AttachmentBuilder((await canvas.encode("png")), { name: "goodbye-image.png" });
channel.send({ channel.send({
content: message, content: message,
files: [attachment], files: [attachment],
}); });
} else } else */
channel.send({ content: message });
channel.send({ content: message });
} }
} }
} }

View file

@ -7,16 +7,18 @@ const { CronJob } = require("cron");
module.exports.init = async client => { module.exports.init = async client => {
const cronjob = new CronJob("0 5 * * *", async function () { const cronjob = new CronJob("0 5 * * *", async function () {
client.guilds.cache.forEach(async guild => { client.guilds.cache.forEach(async guild => {
const guildData = await client.getGuildData(guild.id); try {
const channel = guildData.plugins.birthdays ? client.channels.cache.get(guildData.plugins.birthdays) || (await client.channels.fetch(guildData.plugins.birthdays)) : null; console.log(`Checking birthdays for "${guild.name}"`);
if (guildData.plugins.birthdays && client.channels.cache.get(guildData.plugins.birthdays)) { const guildData = await client.getGuildData(guild.id);
const date = new Date(), const channel = guildData.plugins.birthdays ? await client.channels.fetch(guildData.plugins.birthdays) : null;
currentDay = date.getDate(),
currentMonth = date.getMonth() + 1,
currentYear = date.getFullYear();
if (channel) { if (channel) {
const date = new Date(),
currentDay = date.getDate(),
currentMonth = date.getMonth() + 1,
currentYear = date.getFullYear();
client.usersData.find({ birthdate: { $gt: 1 } }).then(async users => { client.usersData.find({ birthdate: { $gt: 1 } }).then(async users => {
for (const user of users) { for (const user of users) {
if (!guild.members.cache.find(m => m.id === user.id)) return; if (!guild.members.cache.find(m => m.id === user.id)) return;
@ -53,6 +55,9 @@ module.exports.init = async client => {
} }
}); });
} }
} catch (err) {
if (err.code === 10003) console.log(`No channel found for ${guild.name}`);
else throw err;
} }
}); });
}, },
@ -70,51 +75,49 @@ module.exports.init = async client => {
module.exports.run = async client => { module.exports.run = async client => {
client.guilds.cache.forEach(async guild => { client.guilds.cache.forEach(async guild => {
const guildData = await client.getGuildData(guild.id); const guildData = await client.getGuildData(guild.id);
const channel = guildData.plugins.birthdays ? client.channels.cache.get(guildData.plugins.birthdays) || (await client.channels.fetch(guildData.plugins.birthdays)) : null; const channel = guildData.plugins.birthdays ? await client.channels.fetch(guildData.plugins.birthdays) : null;
if (guildData.plugins.birthdays) { if (channel) {
const date = new Date(), const date = new Date(),
currentDay = date.getDate(), currentDay = date.getDate(),
currentMonth = date.getMonth() + 1, currentMonth = date.getMonth() + 1,
currentYear = date.getFullYear(); currentYear = date.getFullYear();
if (channel) { client.usersData.find({ birthdate: { $gt: 1 } }).then(async users => {
client.usersData.find({ birthdate: { $gt: 1 } }).then(async users => { for (const user of users) {
for (const user of users) { if (!guild.members.cache.find(m => m.id === user.id)) return;
if (!guild.members.cache.find(m => m.id === user.id)) return;
const userDate = new Date(user.birthdate * 1000), const userDate = new Date(user.birthdate * 1000),
day = userDate.getDate(), day = userDate.getDate(),
month = userDate.getMonth() + 1, month = userDate.getMonth() + 1,
year = userDate.getFullYear(), year = userDate.getFullYear(),
age = currentYear - year; age = currentYear - year;
if (currentMonth === month && currentDay === day) { if (currentMonth === month && currentDay === day) {
const embed = client.embed({ const embed = client.embed({
author: client.user.getUsername(), author: client.user.getUsername(),
fields: [ fields: [
{ {
name: client.translate("economy/birthdate:HAPPY_BIRTHDAY", null, guildData.language), name: client.translate("economy/birthdate:HAPPY_BIRTHDAY", null, guildData.language),
value: client.translate("economy/birthdate:HAPPY_BIRTHDAY_MESSAGE", { value: client.translate("economy/birthdate:HAPPY_BIRTHDAY_MESSAGE", {
user: user.id, user: user.id,
age: `**${age}** ${client.functions.getNoun( age: `**${age}** ${client.functions.getNoun(
age, age,
client.translate("misc:NOUNS:AGE:1", null, guildData.language), client.translate("misc:NOUNS:AGE:1", null, guildData.language),
client.translate("misc:NOUNS:AGE:2", null, guildData.language), client.translate("misc:NOUNS:AGE:2", null, guildData.language),
client.translate("misc:NOUNS:AGE:5", null, guildData.language), client.translate("misc:NOUNS:AGE:5", null, guildData.language),
)}`, )}`,
}, guildData.language), }, guildData.language),
}, },
], ],
}); });
channel.send({ channel.send({
embeds: [embed], embeds: [embed],
}).then(m => m.react("🎉")); }).then(m => m.react("🎉"));
}
} }
}); }
} });
} }
}); });
}; };

View file

@ -1,4 +1,9 @@
const moment = require("moment"); const moment = require("moment");
// const { GlobalFonts } = require("@napi-rs/canvas"),
// const { resolve } = require("path");
// GlobalFonts.registerFromPath(resolve("./assets/fonts/RubikMonoOne-Regular.ttf"), "RubikMonoOne");
// GlobalFonts.registerFromPath(resolve("./assets/fonts/KeepCalm-Medium.ttf"), "KeepCalm");
module.exports = { module.exports = {
/** /**
@ -125,4 +130,23 @@ module.exports = {
return five; return five;
}, },
/**
* Function to apply text on a canvas with dynamic font size based on the width constraint.
*
* @param {import("@napi-rs/canvas").Canvas} canvas - The canvas object where the text will be applied.
* @param {string} text - The string of text that needs to be applied on the canvas.
* @param {number} defaultFontSize - The initial font size for the text. It is expected to decrease with each iteration.
* @param {number} width - The maximum width that the text can occupy before it has to shrink down.
* @param {string} font - The name of the font used for drawing the text on the canvas.
*
* @return {string} - The final calculated font size in a format '<size>px <family>'.
*/
applyText(canvas, text, defaultFontSize, width, font) {
const ctx = canvas.getContext("2d");
do ctx.font = `${(defaultFontSize -= 1)}px ${font}`;
while (ctx.measureText(text).width > width);
return ctx.font;
},
}; };

View file

@ -1,6 +1,6 @@
{ {
"name": "jaba", "name": "jaba",
"version": "4.6.0", "version": "4.6.1",
"description": "My Discord Bot", "description": "My Discord Bot",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@ -11,15 +11,15 @@
"dependencies": { "dependencies": {
"@discord-player/extractor": "^4.4.7", "@discord-player/extractor": "^4.4.7",
"@discordjs/opus": "^0.9.0", "@discordjs/opus": "^0.9.0",
"@discordjs/rest": "^2.2.0", "@discordjs/rest": "^2.3.0",
"@discordjs/voice": "^0.16.1", "@discordjs/voice": "^0.17.0",
"canvas": "^2.11.2", "@napi-rs/canvas": "^0.1.53",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"cron": "^2.4.4", "cron": "^2.4.4",
"discord-api-types": "^0.37.71", "discord-api-types": "^0.37.90",
"discord-giveaways": "^6.0.1", "discord-giveaways": "^6.0.1",
"discord-player": "^6.6.8", "discord-player": "^6.6.10",
"discord.js": "^14.14.1", "discord.js": "^14.15.3",
"gamedig": "^4.1.0", "gamedig": "^4.1.0",
"i18next": "^21.10.0", "i18next": "^21.10.0",
"i18next-fs-backend": "^1.2.0", "i18next-fs-backend": "^1.2.0",

File diff suppressed because it is too large Load diff