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 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/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://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).
[![image](https://img.shields.io/github/license/JonnyBro/JaBa?label=License&style=flat-square)](https://github.com/JonnyBro/JaBa/blob/main/LICENSE)
## 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 want to contribute, feel free to fork this repo and making a pull request!
# TODO
## TODO
* [ ] Finish and release *dashboard-core* submodule.

View file

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

View file

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

View file

@ -61,16 +61,16 @@ class Goodbye extends BaseCommand {
uk: client.translate("administration/goodbye:MESSAGE", null, "uk-UA"),
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,
ownerOnly: false,
@ -83,13 +83,15 @@ class Goodbye extends BaseCommand {
* @param {import("discord.js").ChatInputCommandInteraction} interaction
*/
async execute(client, interaction) {
await interaction.deferReply({ ephemeral: true });
const guildData = interaction.data.guild,
command = interaction.options.getSubcommand();
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 {
const state = interaction.options.getBoolean("state");
@ -101,13 +103,14 @@ class Goodbye extends BaseCommand {
withImage: null,
};
guildData.markModified("plugins.goodbye");
await guildData.save();
interaction.success("administration/goodbye:DISABLED", null, { ephemeral: true });
interaction.success("administration/goodbye:DISABLED", null, { edit: true });
} else {
const channel = interaction.options.getChannel("channel") || interaction.channel;
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 = {
enabled: true,
@ -116,11 +119,12 @@ class Goodbye extends BaseCommand {
withImage: image,
};
guildData.markModified("plugins.goodbye");
await guildData.save();
interaction.success("administration/goodbye:ENABLED", {
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"),
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,
ownerOnly: false,
@ -83,13 +83,15 @@ class Welcome extends BaseCommand {
* @param {import("discord.js").ChatInputCommandInteraction} interaction
*/
async execute(client, interaction) {
await interaction.deferReply({ ephemeral: true });
const guildData = interaction.data.guild,
command = interaction.options.getSubcommand();
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 {
const state = interaction.options.getBoolean("state");
@ -101,13 +103,14 @@ class Welcome extends BaseCommand {
withImage: null,
};
guildData.markModified("plugins.welcome");
await guildData.save();
interaction.success("administration/welcome:DISABLED", null, { ephemeral: true });
interaction.success("administration/welcome:DISABLED", null);
} else {
const channel = interaction.options.getChannel("channel") || interaction.channel;
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 = {
enabled: true,
@ -116,11 +119,12 @@ class Welcome extends BaseCommand {
withImage: image,
};
guildData.markModified("plugins.welcome");
await guildData.save();
interaction.success("administration/welcome:ENABLED", {
channel: `${channel.toString()}`,
}, { ephemeral: true });
}, { edit: true });
}
}
}

View file

@ -28,16 +28,13 @@ class Work extends BaseCommand {
*/
async execute(client, interaction) {
const { member: memberData, user: userData } = interaction.data,
isInCooldown = memberData.cooldowns?.work;
cooldown = memberData.cooldowns?.work,
now = Date.now();
if (isInCooldown && isInCooldown > Date.now())
return interaction.error("economy/work:COOLDOWN", {
time: `<t:${Math.floor(isInCooldown / 1000)}:R>`,
});
if (now < cooldown) return interaction.error("economy/work:COOLDOWN", { time: `<t:${Math.floor(cooldown / 1000)}:R>` });
if (Math.abs(cooldown - now) > 30 * 60 * 60 * 1000) memberData.workStreak = 0;
if (Date.now() > memberData.cooldowns.work + 30 * 60 * 60 * 1000) memberData.workStreak = 0;
memberData.cooldowns.work = Date.now() + 24 * 60 * 60 * 1000;
memberData.cooldowns.work = now + 24 * 60 * 60 * 1000;
memberData.workStreak = (memberData.workStreak || 0) + 1;
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);
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(
new ButtonBuilder().setCustomId("boosters_prev_page").setStyle(ButtonStyle.Primary).setEmoji("⬅️"),
@ -159,7 +159,7 @@ function generateBoostersEmbeds(interaction, boosters) {
let j = i;
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({
title: interaction.translate("general/boosters:BOOSTERS_LIST"),

View file

@ -33,14 +33,13 @@ class CreateTicketEmbed extends BaseCommand {
interaction.data = [];
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;
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) {
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) => {
if (reason !== "canceled") {
const reversedMessages = (await interaction.channel.messages.fetch()).filter(m => !m.author.bot);
const messages = Array.from(reversedMessages.values()).reverse();
const reversedMessages = (await interaction.channel.messages.fetch()).filter(m => !m.author.bot),
messages = Array.from(reversedMessages.values()).reverse(),
transcriptionLogs = interaction.data.guild.plugins.tickets.transcriptionLogs,
ticketLogs = interaction.data.guild.plugins.tickets.ticketLogs;
if (messages.length > 1) {
let transcript = "---- TICKET CREATED ----\n";

View file

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

View file

@ -1,8 +1,6 @@
# Discord Bot Dashboard Template
> Made By <https://github.com/fuma-nama>
![banner](./document/preview-new.png)
> Forked from [here](https://github.com/FileEditor97/discord-bot-dashboard)
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
```js
Bearer MY_TOKEN_1212112
```
`Bearer MY_TOKEN_1212112`
### Required Routes

View file

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

View file

@ -1,6 +1,11 @@
{
"name": "dashboard",
"version": "1.2.0",
"name": "nohi-dashboard",
"version": "0.1.0",
"license": "MIT",
"author": "Fuma Nama (https://github.com/fuma-nama)",
"maintainers": [
"FileEditor97 (https://github.com/fileeditor97)"
],
"scripts": {
"dev": "next dev",
"build": "next build",
@ -20,35 +25,34 @@
"@chakra-ui/theme-tools": "^2.0.0",
"@emotion/react": "^11.8.1",
"@emotion/styled": "^11.3.0",
"@hookform/resolvers": "^2.9.11",
"@tanstack/react-query": "^4.2.3",
"@hookform/resolvers": "^3.9.0",
"@tanstack/react-query": "^5.50.1",
"apexcharts": "^3.36.3",
"chakra-react-select": "^4.4.2",
"cookies-next": "^2.1.1",
"deepmerge-ts": "^4.2.2",
"framer-motion": "^6.0.0",
"next": "^13.2.1",
"deepmerge-ts": "^7.0.3",
"framer-motion": "^11.0.0",
"next": "^14.2.4",
"react": "^18.2.0",
"react-apexcharts": "^1.4.0",
"react-calendar": "^3.7.0",
"react-calendar": "^5.0.0",
"react-colorful": "^5.6.1",
"react-dom": "^18.0.0",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.43.2",
"react-icons": "^4.3.1",
"react-icons": "^5.2.1",
"zod": "^3.20.6",
"zustand": "^4.4.6"
},
"devDependencies": {
"@babel/core": "^7.21.0",
"@types/node": "16.11.7",
"@types/node": "20.14.10",
"@types/react": "^18.0.0",
"@types/react-calendar": "^3.5.2",
"@types/react-dom": "^18.0.0",
"eslint": "^8.0.0",
"eslint-config-next": "^13.2.1",
"eslint-config-prettier": "8.1.0",
"prettier": "^2.6.2",
"typescript": "^4.8.2"
"eslint-config-next": "^14.2.4",
"eslint-config-prettier": "9.1.0",
"prettier": "^3.3.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 { 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 {
disableFeature,
@ -41,48 +41,54 @@ export const Mutations = {
export function useGuild(id: string) {
const accessToken = useAccessToken();
return useQuery(['guild', id], () => getGuild(accessToken as string, id), {
enabled: accessToken != null,
return useQuery({
queryKey: ['guild', id],
queryFn: () => getGuild(accessToken as string, id),
enabled: accessToken != null
});
}
export function useGuilds() {
const accessToken = useAccessToken();
return useQuery(['user_guilds'], () => getGuilds(accessToken as string), {
enabled: accessToken != null,
return useQuery({
queryKey: ['user_guilds'],
queryFn: () => getGuilds(accessToken as string),
enabled: accessToken != null
});
}
export function useSelfUserQuery() {
const accessToken = useAccessToken();
return useQuery<UserInfo>(['users', 'me'], () => fetchUserInfo(accessToken!!), {
return useQuery<UserInfo>({
queryKey: ['users', 'me'],
queryFn: () => fetchUserInfo(accessToken!!),
enabled: accessToken != null,
staleTime: Infinity,
staleTime: Infinity
});
}
export function useGuildInfoQuery(guild: string) {
const { status, session } = useSession();
return useQuery<CustomGuildInfo | null>(
Keys.guild_info(guild),
() => fetchGuildInfo(session!!, guild),
{
enabled: status === 'authenticated',
refetchOnWindowFocus: true,
retry: false,
staleTime: 0,
}
);
return useQuery<CustomGuildInfo | null>({
queryKey: Keys.guild_info(guild),
queryFn: () => fetchGuildInfo(session!!, guild),
enabled: status === 'authenticated',
refetchOnWindowFocus: true,
retry: false,
staleTime: 0
});
}
export function useFeatureQuery<K extends keyof CustomFeatures>(guild: string, feature: K) {
// eslint-disable-next-line no-unused-vars
const { status, session } = useSession();
return useQuery(Keys.features(guild, feature), () => getFeature(session!!, guild, feature), {
enabled: status === 'authenticated',
return useSuspenseQuery({
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() {
const { session } = useSession();
return useMutation(
async ({ enabled, guild, feature }: EnableFeatureOptions) => {
return useMutation({
mutationFn: async ({ enabled, guild, feature }: EnableFeatureOptions) => {
if (enabled) return enableFeature(session!!, guild, feature);
return disableFeature(session!!, guild, feature);
},
{
async onSuccess(_, { guild, feature, enabled }) {
await client.invalidateQueries(Keys.features(guild, feature));
client.setQueryData<GuildInfo | null>(Keys.guild_info(guild), (prev) => {
if (prev == null) return null;
async onSuccess(_, { guild, feature, enabled }) {
await client.invalidateQueries({queryKey: Keys.features(guild, feature)});
client.setQueryData<GuildInfo | null>(Keys.guild_info(guild), (prev) => {
if (prev == null) return null;
if (enabled) {
return {
...prev,
enabledFeatures: prev.enabledFeatures.includes(feature)
? prev.enabledFeatures
: [...prev.enabledFeatures, feature],
};
} else {
return {
...prev,
enabledFeatures: prev.enabledFeatures.filter((f) => f !== feature),
};
}
});
},
}
);
if (enabled) {
return {
...prev,
enabledFeatures: prev.enabledFeatures.includes(feature)
? prev.enabledFeatures
: [...prev.enabledFeatures, feature],
};
} else {
return {
...prev,
enabledFeatures: prev.enabledFeatures.filter((f) => f !== feature),
};
}
});
},
});
}
export type UpdateFeatureOptions = {
@ -128,29 +132,33 @@ export type UpdateFeatureOptions = {
export function useUpdateFeatureMutation() {
const { session } = useSession();
return useMutation(
(options: UpdateFeatureOptions) =>
return useMutation({
mutationFn: (options: UpdateFeatureOptions) =>
updateFeature(session!!, options.guild, options.feature, options.options),
{
onSuccess(updated, options) {
const key = Keys.features(options.guild, options.feature);
onSuccess(updated, options) {
const key = Keys.features(options.guild, options.feature);
return client.setQueryData(key, updated);
},
}
);
return client.setQueryData(key, updated);
},
});
}
export function useGuildRolesQuery(guild: string) {
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) {
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 {

View file

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

View file

@ -41,14 +41,14 @@ export function UpdateFeaturePanel({
<Text color="TextSecondary">{config.description}</Text>
</Box>
<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} />
</Button>
</ButtonGroup>
</Flex>
{result.component}
<Savebar isLoading={mutation.isLoading} result={result} />
<Savebar isLoading={mutation.isPending} result={result} />
</Flex>
);
}

View file

@ -1,5 +1,5 @@
// 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 { ReactNode } from 'react';
import { SelectField } from '../forms/SelectField';
@ -19,7 +19,7 @@ export default function AuthLayout({ children }: { children: ReactNode }) {
px={{ base: 5, lg: 10 }}
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">
{config.name}
</Text>

View file

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

View file

@ -1,6 +1,6 @@
import { FaChevronLeft as ChevronLeftIcon } from 'react-icons/fa';
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 { useGuildPreview } from '@/api/hooks';
import { motion } from 'framer-motion';

View file

@ -1,29 +1,12 @@
import { createIcon } from '@chakra-ui/react';
import { PermissionFlags } from '@/api/discord';
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 = {
name: 'Demo Bot',
icon: BotIcon,
name: 'VOTL Bot',
icon:
'https://cdn.fileeditor.dev/media/votl/logo.png',
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: {
//filter guilds that user has no permissions to manage it
filter: (guild) => (Number(guild.permissions) & PermissionFlags.ADMINISTRATOR) !== 0,

View file

@ -23,16 +23,6 @@ const { T } = createI18n(provider, {
memes: 'Memes Time',
'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_bn: 'Login with Discord',
},
cn: {
login: '登入控制面板',
'login description': '登錄並開始使用我們的機器人',
login_bn: '使用 Discord 登錄',
},
});

View file

@ -13,15 +13,4 @@ export const common = createI18n(provider, {
pages: 'Pages',
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',
},
},
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',
},
},
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',
},
},
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 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
*/
export type Languages = 'en' | 'cn';
export type Languages = 'en';
export const { languages, names } = initLanguages<Languages>({
en: 'English',
cn: '中文',
});
export const provider = initI18n<Languages>({

View file

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

View file

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

View file

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

View file

@ -105,7 +105,7 @@ const ProfilePage: NextPageWithLayout = () => {
<Button
leftIcon={<IoLogOut />}
variant="danger"
isLoading={logout.isLoading}
isLoading={logout.isPending}
onClick={() => logout.mutate()}
>
{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');
}
@ -37,7 +37,10 @@ type 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)
return {
@ -53,7 +56,7 @@ export function useSession(): SessionResult {
return {
status: 'authenticated',
session: data,
session: data!,
};
}
@ -64,5 +67,8 @@ export function useAccessToken() {
}
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) {
// @ts-ignore
setCookie(TokenCookie, data, { req, res, ...options });
}
@ -51,6 +52,7 @@ export async function removeSession(req: NextApiRequest, res: NextApiResponse) {
const session = getServerSession(req);
if (session.success) {
// @ts-ignore
deleteCookie(TokenCookie, { req, res, ...options });
await revokeToken(session.data.access_token);
}

View file

@ -3,7 +3,7 @@ import { AccessToken } from '@/utils/auth/server';
import { Options } from './core';
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 {
return {

View file

@ -58,13 +58,14 @@ class CommandHandler extends BaseEvent {
}
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
? `, args: [${interaction.options.data
.map(arg => {
return `${arg.name}: ${arg.value}`;
}).join(", ")
: "no args"
})
.join(", ")}]`
: ""
}`,
);

View file

@ -1,18 +1,9 @@
const Canvas = require("canvas"),
BaseEvent = require("../../base/BaseEvent"),
{ AttachmentBuilder } = require("discord.js"),
{ resolve } = require("path");
// const Canvas = require("@napi-rs/canvas"),
// BaseEvent = require("../../base/BaseEvent"),
// { AttachmentBuilder } = require("discord.js"),
// { applyText } = require("../../helpers/functions");
Canvas.registerFont(resolve("./assets/fonts/RubikMonoOne-Regular.ttf"), { family: "RubikMonoOne" });
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;
};
const BaseEvent = require("../../base/BaseEvent");
class GuildMemberAdd extends BaseEvent {
constructor() {
@ -45,6 +36,7 @@ class GuildMemberAdd extends BaseEvent {
.replace(/{server}/g, member.guild.name)
.replace(/{membercount}/g, member.guild.memberCount);
/*
if (guildData.plugins.welcome.withImage) {
const canvas = Canvas.createCanvas(1024, 450),
ctx = canvas.getContext("2d");
@ -132,14 +124,15 @@ class GuildMemberAdd extends BaseEvent {
);
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({
content: message,
files: [attachment],
});
} else
channel.send({ content: message });
} else */
channel.send({ content: message });
}
}
}

View file

@ -1,18 +1,9 @@
const Canvas = require("canvas"),
BaseEvent = require("../../base/BaseEvent"),
{ AttachmentBuilder } = require("discord.js"),
{ resolve } = require("path");
// const Canvas = require("@napi-rs/canvas"),
// BaseEvent = require("../../base/BaseEvent"),
// { AttachmentBuilder } = require("discord.js"),
// { applyText } = require("../../helpers/functions");
Canvas.registerFont(resolve("./assets/fonts/RubikMonoOne-Regular.ttf"), { family: "RubikMonoOne" });
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;
};
const BaseEvent = require("../../base/BaseEvent");
class GuildMemberRemove extends BaseEvent {
constructor() {
@ -43,6 +34,7 @@ class GuildMemberRemove extends BaseEvent {
.replace(/{server}/g, member.guild.name)
.replace(/{membercount}/g, member.guild.memberCount);
/*
if (guildData.plugins.goodbye.withImage) {
const canvas = Canvas.createCanvas(1024, 450),
ctx = canvas.getContext("2d");
@ -135,14 +127,15 @@ class GuildMemberRemove extends BaseEvent {
);
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({
content: message,
files: [attachment],
});
} else
channel.send({ content: message });
} else */
channel.send({ content: message });
}
}
}

View file

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

View file

@ -1,4 +1,9 @@
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 = {
/**
@ -125,4 +130,23 @@ module.exports = {
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",
"version": "4.6.0",
"version": "4.6.1",
"description": "My Discord Bot",
"main": "index.js",
"scripts": {
@ -11,15 +11,15 @@
"dependencies": {
"@discord-player/extractor": "^4.4.7",
"@discordjs/opus": "^0.9.0",
"@discordjs/rest": "^2.2.0",
"@discordjs/voice": "^0.16.1",
"canvas": "^2.11.2",
"@discordjs/rest": "^2.3.0",
"@discordjs/voice": "^0.17.0",
"@napi-rs/canvas": "^0.1.53",
"chalk": "^4.1.2",
"cron": "^2.4.4",
"discord-api-types": "^0.37.71",
"discord-api-types": "^0.37.90",
"discord-giveaways": "^6.0.1",
"discord-player": "^6.6.8",
"discord.js": "^14.14.1",
"discord-player": "^6.6.10",
"discord.js": "^14.15.3",
"gamedig": "^4.1.0",
"i18next": "^21.10.0",
"i18next-fs-backend": "^1.2.0",

File diff suppressed because it is too large Load diff