Compare commits

...

25 Commits

Author SHA1 Message Date
b82784608f fix: racoon and capybara images command 2024-10-04 16:55:31 +02:00
ea1d086fd0 fix: docker builds 2 2024-09-20 19:42:21 +02:00
b84260901d fix: docker builds 2024-09-20 19:37:32 +02:00
8ac9c16945 refactor: basically everything, move to src dir, move to sern build, publisher, v4, fix commands 2024-09-20 19:32:03 +02:00
8aeebb1164 fix: v ig on thread 2024-07-28 17:57:39 +02:00
fa23042c99 ci: update docker yml 2024-07-28 15:12:10 +02:00
2aafce2429 docs: remove discordapp attachment 2024-07-20 00:01:34 +02:00
8d5c962049 fix: some random bugs 2024-07-09 23:36:57 +02:00
37d983e150 chore: change model to mistral 2024-07-09 23:29:24 +02:00
3b9acf6f29 chore: empty redeploy commit 2024-07-09 23:20:20 +02:00
51ea593bcd copy all 2024-04-23 19:42:23 +02:00
8112f232e3 add corepack 2024-04-23 19:39:45 +02:00
214c35a7e6 docker buildx 2024-04-23 19:37:19 +02:00
d208f6b8c7 fix: why is ts not installing 2024-04-23 19:31:55 +02:00
0b8f23cefd fix: update tsc version 2024-04-23 19:27:52 +02:00
c95ceb1539 chore: set version 2024-04-23 19:21:14 +02:00
0654ce2bfd fix: some small stuff 2024-04-23 17:20:06 +02:00
b84bd0a250 chore: change to v!ig instead of "ig" 2024-04-22 21:33:42 +02:00
8005ac0699 La year inactivity update! (#48)
* feat: non t message deletion

* feat: cloudflare ai!

* feat: image classification and llama 3

* feat: image classification and infinitecraft

* feat: add initial find and lower the paste length

* chore: add devServer boolean to gpt
2024-04-22 21:25:52 +02:00
ecada7600e fix: the goofy ahh youtube notifications bug 2024-04-02 19:54:22 +02:00
73e397be5b fix: util folder getting copied over to root 2023-11-29 20:36:19 +01:00
0f08d35876 chore: empty commit 2023-11-04 23:04:52 +01:00
116f0be0b2 feat: spotify link cleaner, bye scam links 2023-11-04 22:53:10 +01:00
43c0b40703 Merge pull request #39 from SrIzan10/snyk-fix-f8a2f0a23fb4b30c3b5997999e893e30
[Snyk] Security upgrade systeminformation from 5.17.12 to 5.21.7
2023-10-11 22:06:44 +02:00
snyk-bot
10aa847aba fix: package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-SYSTEMINFORMATION-5914637
2023-09-22 17:01:02 +00:00
109 changed files with 1068340 additions and 3606 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules
images
.env*
dist

View File

@@ -22,10 +22,12 @@ jobs:
- name: Check out the repo
uses: actions/checkout@v3
- name: Log in to Sr Izan's container registry
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
with:
registry: containers.srizan.dev
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
@@ -33,7 +35,7 @@ jobs:
id: meta
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
with:
images: containers.srizan.dev/vinci
images: srizan10/vinci
tags: latest
- name: Build and push Docker image
@@ -50,5 +52,5 @@ jobs:
AUTH_HEADER: ${{ secrets.WHSERVER_TOKEN }}
run: |
curl -X POST \
-H "Authorization: Bearer $AUTH_HEADER" \
https://webhooks.srizan.dev/hooks/vinci
-H "Authorization: $AUTH_HEADER" \
https://webhooks.srizan.dev/hooks/vinci

124
.sern/ambient.d.ts vendored Normal file
View File

@@ -0,0 +1,124 @@
declare var __DEV__: boolean
declare var __PROD__: boolean
declare var __VERSION__: string
declare namespace NodeJS {
interface ProcessEnv {
SHELL:string
SESSION_MANAGER:string
COLORTERM:string
XDG_CONFIG_DIRS:string
XDG_SESSION_PATH:string
COREPACK_ENABLE_DOWNLOAD_PROMPT:string
NVM_INC:string
XDG_MENU_PREFIX:string
TERM_PROGRAM_VERSION:string
ICEAUTHORITY:string
NODE_OPTIONS:string
LC_ADDRESS:string
LC_NAME:string
MEMORY_PRESSURE_WRITE:string
DESKTOP_SESSION:string
LC_MONETARY:string
GTK_RC_FILES:string
NO_AT_BRIDGE:string
EDITOR:string
XDG_SEAT:string
PWD:string
LOGNAME:string
XDG_SESSION_DESKTOP:string
XDG_SESSION_TYPE:string
SYSTEMD_EXEC_PID:string
_:string
XAUTHORITY:string
VSCODE_GIT_ASKPASS_NODE:string
MOTD_SHOWN:string
GTK2_RC_FILES:string
HOME:string
COREPACK_ROOT:string
LANG:string
LC_PAPER:string
XDG_CURRENT_DESKTOP:string
npm_package_version:string
MEMORY_PRESSURE_WATCH:string
STARSHIP_SHELL:string
WAYLAND_DISPLAY:string
GIT_ASKPASS:string
XDG_SEAT_PATH:string
INVOCATION_ID:string
MANAGERPID:string
INIT_CWD:string
CHROME_DESKTOP:string
STARSHIP_SESSION_KEY:string
KDE_SESSION_UID:string
NVM_DIR:string
VSCODE_GIT_ASKPASS_EXTRA_ARGS:string
XKB_DEFAULT_LAYOUT:string
XDG_ACTIVATION_TOKEN:string
XDG_SESSION_CLASS:string
LC_IDENTIFICATION:string
TERM:string
npm_package_name:string
PROJECT_CWD:string
USER:string
VSCODE_GIT_IPC_HANDLE:string
QT_WAYLAND_RECONNECT:string
KDE_SESSION_VERSION:string
PAM_KWALLET5_LOGIN:string
DISPLAY:string
npm_lifecycle_event:string
SHLVL:string
NVM_CD_FLAGS:string
LC_TELEPHONE:string
LC_MEASUREMENT:string
XDG_VTNR:string
XDG_SESSION_ID:string
npm_config_user_agent:string
npm_execpath:string
XDG_RUNTIME_DIR:string
DEBUGINFOD_URLS:string
npm_package_json:string
LC_TIME:string
BUN_INSTALL:string
BERRY_BIN_FOLDER:string
XKB_DEFAULT_VARIANT:string
VSCODE_GIT_ASKPASS_MAIN:string
QT_AUTO_SCREEN_SCALE_FACTOR:string
JOURNAL_STREAM:string
XDG_DATA_DIRS:string
KDE_FULL_SESSION:string
GDK_BACKEND:string
BROWSER:string
PATH:string
ORIGINAL_XDG_CURRENT_DESKTOP:string
DBUS_SESSION_BUS_ADDRESS:string
KDE_APPLICATIONS_AS_SCOPE:string
MAIL:string
NVM_BIN:string
npm_node_execpath:string
LC_NUMERIC:string
OLDPWD:string
TERM_PROGRAM:string
TOKEN:string
PREFIX:string
MONGODB:string
YOURLS_KEY:string
CATAPI:string
DOGAPI:string
TWITTER:string
MAKESWEET:string
GENIUS:string
SPOTIFY_CLIENT:string
SPOTIFY_SECRET:string
CF_AI_TOKEN:string
CF_AI_ACC:string
GUILDID:string
SUGGESTIONS_CHANNEL:string
MODLOGS_CHANNEL:string
JOINSANDLEAVES_CHANNEL:string
SOCIALS_CHANNEL:string
BIRTHDAYS_CHANNEL:string
MCFORM_CHANNEL:string
CHATGPT_CHANNEL:string
T_CHANNEL:string
}
}

File diff suppressed because it is too large Load Diff

17
.sern/tsconfig.json Normal file
View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "node16",
"strict": true,
"skipLibCheck": true,
"target": "esnext",
"rootDirs": [
"./generated",
"../src"
]
},
"include": [
"./ambient.d.ts",
"../src"
]
}

BIN
.yarn/install-state.gz Normal file

Binary file not shown.

1
.yarnrc.yml Normal file
View File

@@ -0,0 +1 @@
nodeLinker: node-modules

View File

@@ -7,12 +7,11 @@ WORKDIR /app
RUN apk add --no-cache --virtual .gyp python3 make g++
COPY package.json yarn.lock ./
COPY . .
RUN corepack enable yarn
RUN yarn
COPY . .
RUN yarn build
RUN yarn cache clean
# Final stage
FROM node:lts-alpine AS final
@@ -20,9 +19,9 @@ FROM node:lts-alpine AS final
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/schemas ./schemas
COPY --from=build /app/util ./
COPY --from=build /app/images ./images
COPY --from=build /app/src ./src
COPY --from=build /app/.sern ./.sern
COPY --from=build /app/assets ./assets
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./package.json
RUN apk add --no-cache ffmpeg msttcorefonts-installer fontconfig && \

View File

@@ -26,10 +26,5 @@ the code is here for transparency purposes and it's not made to be hosted by thi
- ~~socials notification system~~ DONE!
- chatbot using IBM's AI (thanks @gosevil for the idea)
- ~~joke command~~
<!--<img src="https://srizan.s-ul.eu/RddzT2f9">-->
<img src="https://cdn.discordapp.com/attachments/928230817673641995/1036390945945559140/makesweet-hbt4h3.gif">
by @Oliverlg8
10 stars! tysm!

1059783
assets/icRecipes.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +0,0 @@
import { commandModule, CommandType } from '@sern/handler';
import axios from 'axios';
export default commandModule({
type: CommandType.Button,
plugins: [],
execute: async (ctx) => {
await ctx.deferReply({ ephemeral: true })
const request = await axios.get('https://api.minetools.eu/query/minecraft.maraturing.com/25565').then(res => res.data)
await ctx.editReply({
content: ``
})
},
});

View File

@@ -1,19 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";
import { ApplicationCommandOptionType } from "discord.js";
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
name: 'askjavi',
type: CommandType.Slash,
plugins: [publish()],
description: 'DESACTIVADO: Pregunta a Javi LO QUE SEA!',
//alias : [],
execute: async (ctx, options) => {
await ctx.reply({content: `Este comando ha sido desactivado ya que era para un evento que ya ha ocurrido.\nGracias por haber participado!`, ephemeral: true})
},
});

View File

@@ -1,36 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";;
import { ApplicationCommandOptionType, EmbedBuilder, GuildMember, TextChannel } from 'discord.js'
export default commandModule({
name: 'ban',
type: CommandType.Slash,
plugins: [publish(), ownerOnly()],
description: 'ADMIN: Banea usuarios.',
options: [{
name: 'usuario',
description: 'Escribe un usuario.',
type: ApplicationCommandOptionType.User,
required: true
},
{
name: 'razon',
description: 'Escribe la razón.',
type: ApplicationCommandOptionType.String,
required: true
}],
//alias : [],
execute: async (ctx, options) => {
try {
const userToBan = options[1].getMember('usuario') as GuildMember
const reason = options[1].getString('razon') as string
userToBan.ban({reason: reason})
const sendToMods = ctx.client.guilds.cache.get(process.env.GUILDID!)!.channels.cache.get(process.env.MODLOGS_CHANNEL!) as TextChannel
await sendToMods.send({content: `Se ha baneado a ${userToBan}.\nBan efectuado por ${ctx.user} con razón "${reason}."`})
await ctx.reply({content: 'Baneado correctamente!', ephemeral: true})
} catch {
await ctx.reply({content: `ERROR: No puedo hacer este comando porque a lo mejor soy inferior que el rol de esa persona o estoy usándolo contra admins.`})
}
},
});

View File

@@ -1,42 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "../../plugins/index.js";
import { ownerOnly } from "#plugins";
import { ApplicationCommandOptionType, TextChannel } from "discord.js";
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
name: 'eliminarmensaje',
type: CommandType.Slash,
plugins: [publish(), ownerOnly()],
description: 'ADMIN: Elimina comandos por su ID.',
//alias : [],
options: [
{
name: 'canal',
type: ApplicationCommandOptionType.Channel,
description: 'El canal de texto.',
required: true
},
{
name: 'id',
type: ApplicationCommandOptionType.String,
description: 'El ID del mensaje.',
required: true
}
],
execute: async (ctx, options) => {
try {
const idMensaje = options[1].getString('id', true);
const guildId = ctx.guild!.id
const guild = await ctx.client.guilds.fetch(guildId);
const channel = await guild.channels.fetch(ctx.channel!.id);
(await (channel as TextChannel).messages.fetch(idMensaje)).delete();
await ctx.reply({content: 'Mensaje eliminado correctamente.', ephemeral: true});
} catch {
await ctx.reply({content: `ERROR: No se ha podido eliminar el mensaje, asegúrate que estás usando el ID y el canal correcto.`})
}
},
});

View File

@@ -1,38 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";;
import { ApplicationCommandOptionType, EmbedBuilder, GuildMember, TextChannel } from 'discord.js'
export default commandModule({
name: 'kick',
type: CommandType.Slash,
plugins: [publish(), ownerOnly()],
description: 'ADMIN: Expulsa usuarios.',
options: [
{
name: 'usuario',
description: 'Escribe un usuario.',
type: ApplicationCommandOptionType.User,
required: true
},
{
name: 'razon',
description: 'Escribe la razón.',
type: ApplicationCommandOptionType.String,
required: true
}
],
//alias : [],
execute: async (ctx, options) => {
try {
const userToKick = options[1].getMember('usuario');
const reason = options[1].getString('razon') as string;
(userToKick as GuildMember).kick(reason)
const sendToMods = ctx.client.guilds.cache.get(process.env.GUILDID!)!.channels.cache.get(process.env.MODLOGS_CHANNEL!) as TextChannel
await sendToMods!.send({content: `Se ha expulsado a ${userToKick}.\nKick efectuado por ${ctx.user} con razón "${reason}."`})
await ctx.reply({content: 'Expulsado correctamente!'})
} catch {
await ctx.reply({content: `ERROR: No puedo hacer este comando porque a lo mejor soy inferior que el rol de esa persona o estoy usándolo contra admins.`})
}
},
});

View File

@@ -1,50 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";
import { ApplicationCommandOptionType, GuildMember, TextChannel } from "discord.js";
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
name: 'timeout',
type: CommandType.Slash,
plugins: [publish(), ownerOnly()],
description: 'ADMIN: Silencia a usuarios.',
options: [
{
name: "usuario",
description: "Escribe el usuario que silenciar.",
type: ApplicationCommandOptionType.User,
required: true
},
{
name: "razon",
description: "Escribe el razon que vas a silenciar.",
type: ApplicationCommandOptionType.String,
required: true
},
{
name: "minutos",
description: "Escribe los minutos que estará silenciado.",
type: ApplicationCommandOptionType.Number,
min_value: 0,
required: true
}
],
//alias : [],
execute: async (ctx, options) => {
try {
const usuario = options[1].getMember('usuario') as GuildMember
const minutos = options[1].getNumber('minutos') as number
const razon = options[1].getString('razon', true);
const minutosToMilisegundos = minutos * 60 * 1000
usuario.timeout(minutosToMilisegundos, razon).then(() => {ctx.reply({content: `Se ha silenciado a ${usuario} correctamente.`, ephemeral: true})})
const sendToMods = ctx.client.guilds.cache.get(process.env.GUILDID!)!.channels.cache.get(process.env.MODLOGS_CHANNEL!) as TextChannel
await sendToMods.send({content: `Se ha silenciado a ${usuario}.\nSlencio efectuado por ${ctx.user} con ${minutos} minutos de duración.\nRazón: ${razon}`})
} catch {
await ctx.reply({content: `ERROR: No puedo hacer este comando porque a lo mejor soy inferior que el rol de esa persona o estoy usándolo contra admins.`})
}
},
});

View File

@@ -1,212 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";;
import { ActionRowBuilder, ApplicationCommandOptionType, ButtonBuilder, ButtonInteraction, ButtonStyle, ComponentType, EmbedBuilder, GuildMember } from "discord.js";
import db from '../../schemas/warn.js';
export default commandModule({
name: 'warn',
type: CommandType.Slash,
plugins: [publish(), ownerOnly()],
description: 'ADMIN: Avisa a usuarios.',
//alias : [],
options: [
{
name: 'leve',
description: 'Aviso leve.',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'usuario',
description: 'el usuario al que avisar.',
type: ApplicationCommandOptionType.User,
required: true
},
{
name: 'razon',
description: 'la razón aviso.',
type: ApplicationCommandOptionType.String,
required: true
}
]
},
{
name: 'grave',
description: 'Aviso grave.',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'usuario',
description: 'el usuario al que avisar.',
type: ApplicationCommandOptionType.User,
required: true
},
{
name: 'razon',
description: 'la razón del aviso.',
type: ApplicationCommandOptionType.String,
required: true
}
]
},
{
name: 'clear',
description: 'Elimina los avisos de una persona.',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'usuario',
description: 'el usuario al que quitar el aviso.',
type: ApplicationCommandOptionType.User,
required: true
}
]
}
],
execute: async (ctx, options) => {
const subcommand = options[1].getSubcommand()
const user = (options[1].getMember('usuario') as GuildMember).id
const usermember = options[1].getMember('usuario') as GuildMember
const reason = options[1].getString('razon', true) as string
const times = await db.findOne({id: `${user}`}) as any
const buttons = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId('1hour')
.setLabel('1 hora')
.setStyle(ButtonStyle.Danger),
new ButtonBuilder()
.setCustomId('30mins')
.setLabel('30 minutos')
.setStyle(ButtonStyle.Danger),
new ButtonBuilder()
.setCustomId('15mins')
.setLabel('15 minutos')
.setStyle(ButtonStyle.Danger),
new ButtonBuilder()
.setCustomId('pardon')
.setLabel('Perdonar')
.setStyle(ButtonStyle.Primary)
);
const dmEmbed = new EmbedBuilder()
.setAuthor({name: `${ctx.user.username}`, iconURL: `${ctx.user.displayAvatarURL()}`})
.setColor('Red')
.setTitle('Tienes un aviso.')
.setDescription(`Has sido avisado en el servidor de Discord.\nRazón: ${reason}.`)
const dmEmbedTimeout = new EmbedBuilder()
.setAuthor({name: `${ctx.user.username}`, iconURL: `${ctx.user.displayAvatarURL()}`})
.setColor('Red')
.setTitle('Acabas de ser muteado del servidor.')
.setDescription(`Ve al servidor de Discord para ver el tiempo que estarás bloqueado.\nRazón: ${reason}.\n**Puede durar hasta una hora.**`)
switch (subcommand) {
case "leve": {
return db.exists({id: `${user}`}, async function (err, doc) {
if (err) {
console.log(err)
} else {
if (doc === null) {
const warn = new db({id: `${user}`, times: 1})
warn.save()
ctx.reply({content: `Se ha avisado a ${usermember} correctamente y añadido a la base de datos.`, ephemeral: true})
ctx.client.users.fetch(user).then((user) => {
user.send({embeds: [dmEmbed]})
}).catch(() => console.log(`couldn't send a DM to user ID ${user}.`));
} else {
if (times.times > 2) {
const msg = await ctx.reply({content: `El usuario ha excedido 3 avisos, ¿qué hacer?`, ephemeral: true, components: [buttons]})
const collector = msg.createMessageComponentCollector({ time: 15000, max: 1, componentType: ComponentType.Button });
collector.on('collect', async (i) => {
await i.deferReply({ephemeral: true})
if (i.customId === '1hour') {
await i.editReply({content: `Se ha silenciado a ${usermember} durante 1 hora correctamente. ;-;`})
usermember.timeout(60 * 60 * 1000, reason)
times.times = 0
times.save()
} else if (i.customId === '30mins') {
await i.editReply({content: `Se ha silenciado a ${usermember} durante 30 minutos correctamente. ;-;`})
usermember.timeout(30 * 60 * 1000, reason)
times.times = 0
times.save()
} else if (i.customId === '15mins') {
await i.editReply({content: `Se ha silenciado a ${usermember} durante 15 minutos correctamente. ;-;`})
usermember.timeout(15 * 60 * 1000, reason)
times.times = 0
times.save()
} else if (i.customId === 'pardon') {
await i.editReply({content: `Se ha perdonado a ${usermember} correctamente.\nSeguro que la persona te lo agradecerá! :'D`})
times.times = 0
times.save()
}
ctx.client.users.fetch(user).then((user) => {
user.send({embeds: [dmEmbedTimeout]})
}).catch(() => console.log(`couldn't send a DM to user ID ${user}.`));
});
} else {
ctx.reply({content: `se ha añadido un aviso con el motivo ${reason}.\navisos que tiene ahora: ${times.times + 1}`, ephemeral: true})
times.times = times.times + 1
times.save()
ctx.client.users.fetch(user).then((user) => {
user.send({embeds: [dmEmbed]});
}).catch(() => console.log(`couldn't send a DM to user ID ${user}.`))
}
}
}
});
}
case "grave": {
return db.exists({id: `${user}`}, async function (err, doc) {
if (err) {
console.log(err)
} else {
if (doc === null) {
const warn = new db({id: `${user}`, times: 2})
warn.save()
ctx.reply({content: `Se ha avisado a ${usermember} correctamente y añadido a la base de datos.`, ephemeral: true})
ctx.client.users.fetch(user).then((user) => {
user.send({embeds: [dmEmbed]});
}).catch(() => console.log(`couldn't send a DM to user ID ${user}.`))
} else {
if (times.times >= 4) {
const msg = await ctx.reply({content: `El usuario ha excedido 3 avisos, ¿qué hacer?`, ephemeral: true, components: [buttons]})
const collector = msg.createMessageComponentCollector({ time: 1000, max: 1, componentType: ComponentType.Button });
collector.on('collect', async (i: any) => {
if (i.customId === '1hour') {
await i.channel!.send({content: `Se ha silenciado a ${usermember} durante 1 hora correctamente. ;-;`})
usermember.timeout(60 * 60 * 1000, reason)
times.times = 0
times.save()
} else if (i.customId === '30mins') {
await i.channel!.send({content: `Se ha silenciado a ${usermember} durante 30 minutos correctamente. ;-;`})
usermember.timeout(30 * 60 * 1000, reason)
times.times = 0
times.save()
} else if (i.customId === '15mins') {
await i.channel!.send({content: `Se ha silenciado a ${usermember} durante 15 minutos correctamente. ;-;`})
usermember.timeout(15 * 60 * 1000, reason)
times.times = 0
times.save()
} else if (i.customId === 'pardon') {
await i.channel!.send({content: `Se ha perdonado a ${usermember} correctamente.\nSeguro que la persona te lo agradecerá! :'D`})
times.times = 0
times.save()
}
ctx.client.users.fetch(user).then((user) => {
user.send({embeds: [dmEmbedTimeout]})
}).catch(() => console.log(`couldn't send a DM to user ID ${user}.`));
});
} else {
ctx.reply({content: `se ha añadido un aviso con el motivo ${reason}.\navisos que tiene ahora: ${times.times + 2}`, ephemeral: true})
times.times = times.times + 2
times.save()
ctx.client.users.fetch(user).then((user) => {
user.send({embeds: [dmEmbed]});
}).catch(() => console.log(`couldn't send a DM to user ID ${user}.`))
}
}
}
});
}
}
}
})

2
dependencies.d.ts vendored
View File

@@ -1,9 +1,11 @@
import { SernEmitter, Logging, CoreModuleStore, ModuleManager, ErrorHandling, CoreDependencies, Singleton } from '@sern/handler'
import { Client } from 'discord.js'
import Spotify from 'spotify-api.js'
declare global {
interface Dependencies extends CoreDependencies {
'@sern/client': Singleton<Client>
'spotify-api-client': Singleton<Spotify.Client>
}
}

View File

@@ -1,10 +0,0 @@
#!/bin/bash
git pull
docker build . -t srizan10/vinci
docker stop vinci
docker rm vinci
docker run -d -t --name vinci -p 7272:7272 --restart unless-stopped srizan10/vinci

View File

@@ -1,41 +0,0 @@
import { discordEvent } from '@sern/handler';
import axios from 'axios';
import { TextChannel } from 'discord.js';
import db from '../schemas/chatgpt.js';
export default discordEvent({
name: 'messageCreate',
async execute(message) {
if (message.channel.id !== process.env.CHATGPT_CHANNEL) return;
if (message.author.bot) return;
if (message.content.includes('ig')) return;
try {
await (message.channel as TextChannel).sendTyping()
const response = await axios.post('https://chatgpt-api.shn.hk/v1/', {
"model": "gpt-3.5-turbo",
"messages": [{ "role": "user", "content": message.content }]
}).then(res => res.data)
const titleResponse = await axios.post('https://chatgpt-api.shn.hk/v1/', {
"model": "gpt-3.5-turbo",
"messages": [{ "role": "user", "content": `Generate a title in less than 6 words for the following message, also remove the quotes if you are going to add them AND don't put Title in the beginning:\nUser: ${message.content}\nAssistant: ${response.choices[0].message.content}` }]
}).then(res => res.data.choices[0].message.content.replaceAll('"', '') as string)
const botMsg = await message.reply({ content: response.choices[0].message.content.slice(0, 2000) })
const thread = await botMsg.startThread({ name: titleResponse })
const dbData = new db({
messageid: message.id,
threadid: thread.id,
messages: [
{ role: 'user', content: message.content },
{ role: 'assistant', content: response.choices[0].message.content.replace(/^\n{2}/, '') }
]
})
await dbData.save()
} catch (e) {
await message.reply({ content: 'Algo ha ido mal.' }).catch(() => {})
console.log(e)
}
},
});

View File

@@ -1,53 +0,0 @@
import { discordEvent } from '@sern/handler';
import axios from 'axios';
import { ThreadChannel } from 'discord.js';
import database from '../schemas/chatgpt.js';
export default discordEvent({
name: 'messageCreate',
async execute(message) {
const thread = message.channel as ThreadChannel
if (thread.parentId !== process.env.CHATGPT_CHANNEL) return;
if (message.author.bot) return;
if (message.content.includes('ig')) return;
try {
await thread.sendTyping()
let newObj = { role: 'user', content: message.content }
let db = await database.findOneAndUpdate({ threadid: thread.id }, {
$push: {
messages: newObj
}
})
const messages = db!.messages.map((message) => {
const { _id, ...rest } = message.toObject(); // Convert Mongoose document to plain object and remove _id field
return rest
})
const response = await axios.post('https://chatgpt-api.shn.hk/v1/', {
"model": "gpt-3.5-turbo",
"messages": messages
}, {
headers: {
'Content-Type': 'application/json'
}
}).then(res => res.data.choices[0].message.content as string)
newObj = { role: 'assistant', content: response }
db = await database.findOneAndUpdate({ threadid: thread.id }, {
$push: {
messages: newObj
}
})
await message.reply({ content: response.slice(0, 2000) })
} catch (e) {
await message.reply('Algo ha ido mal.')
console.log(e)
}
},
});
function replacer(key, value) {
return value.replace(/[^\w\s]/gi, '');
}

View File

@@ -1,13 +1,14 @@
{
"name": "vinci",
"version": "1.0.0",
"version": "1.1.0",
"description": "Vinci Discord Bot for Mara Turing",
"main": "dist/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "tsc-watch -p \"./tsconfig.json\" --onSuccess \"node ./dist/index.js --dev\"",
"dev": "sern build && node ./dist/index.js --dev",
"prod": "tsc-watch -p \"./tsconfig.json\" --onSuccess \"node ./dist/index.js\"",
"compile": "tsc --build",
"build": "tsc --build",
"build": "sern build",
"web": "node webserver.js",
"watch": "tsc --watch",
"start": "nodemon dist/index.js"
@@ -32,21 +33,25 @@
},
"homepage": "https://github.com/SrIzan10/vinci#readme",
"dependencies": {
"@consumet/extensions": "1.3.5",
"@ai-zen/node-fetch-event-source": "^2.1.4",
"@consumet/extensions": "^1.7.0",
"@discordjs/opus": "^0.9.0",
"@discordjs/voice": "^0.15.0",
"@napi-rs/canvas": "^0.1.30",
"@sern/handler": "^3.0.3",
"axios": "^1.1.3",
"@microsoft/fetch-event-source": "^2.0.1",
"@napi-rs/canvas": "^0.1.52",
"@sern/handler": "^4.0.2",
"@sern/publisher": "^1.1.2",
"axios": "^1.6.8",
"dayjs": "^1.11.6",
"discord-tictactoe": "^4.0.0",
"discord.js": "^14.13.0",
"discord.js": "^14.16.2",
"dotenv": "^16.0.1",
"execa": "^6.1.0",
"express": "^4.18.1",
"extended-eventsource": "^1.4.6",
"form-data": "^4.0.0",
"genius-lyrics": "^4.4.3",
"googlethis": "^1.7.1",
"googlethis": "^1.8.0",
"got": "^12.5.3",
"libsodium-wrappers": "^0.7.10",
"mongoose": "^6.11.3",
@@ -54,13 +59,18 @@
"pretty-seconds-spanish": "^2.1.1",
"rockpaperscissors-checker": "^1.2.0",
"set-interval-async": "^3.0.2",
"sharp": "^0.33.3",
"spotify-api.js": "^9.2.5",
"stringify-safe": "^1.0.3",
"systeminformation": "^5.12.6"
"systeminformation": "^5.21.7"
},
"devDependencies": {
"@sern/cli": "^1.3.3",
"@types/express": "^4.17.14",
"@types/node": "^20.12.7",
"ts-node": "10.9.1",
"tsc-watch": "^5.0.3",
"typescript": "^5.2.2"
}
"typescript": "^5.6.2"
},
"packageManager": "yarn@4.1.1"
}

View File

@@ -1,9 +0,0 @@
const mongoose = require('mongoose');
const schema = new mongoose.Schema({
number: {type: Number, required: true}
})
const db = new mongoose.Model('counting', schema)
module.exports = db;

View File

@@ -1,7 +1,7 @@
{
"language": "typescript",
"paths": {
"base": ".",
"base": "src",
"commands": "commands"
}
}

View File

@@ -1,16 +1,10 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";
import { ApplicationCommandOptionType } from "discord.js";
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
name: '8ball',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Preguntale a la 8-ball cosas.',
//alias : [],
options: [{
@@ -19,8 +13,8 @@ export default commandModule({
type: ApplicationCommandOptionType.String,
required: true
}],
execute: async (ctx, options) => {
// yes, the question argument is never used. There is no reason to use it in the code.
execute: async (ctx) => {
// yes, the question argument is never used. There is no reason to use it.
var eightballwords = [
'Probablemente',
'Sí',

View File

@@ -2,10 +2,8 @@ import { commandModule, CommandType } from '@sern/handler';
import {
ApplicationCommandOptionType,
AttachmentBuilder,
AutocompleteInteraction,
EmbedBuilder,
} from 'discord.js';
import { publish } from '#plugins';
const choices = [
'XaviXE',
'Paula',
@@ -28,9 +26,8 @@ const choices = [
export default commandModule({
name: 'a',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'A',
//alias : [],
options: [
{
name: 'usuario',
@@ -39,7 +36,7 @@ export default commandModule({
autocomplete: true,
command: {
onEvent: [],
async execute(ctx: AutocompleteInteraction) {
async execute(ctx) {
const focusedValue = ctx.options.getFocused();
const filtered = choices.filter((choice) =>
choice.startsWith(focusedValue)
@@ -48,10 +45,10 @@ export default commandModule({
filtered.map((choice) => ({ name: choice, value: choice }))
);
},
},
}
},
],
execute: async (ctx, options) => {
execute: async (ctx) => {
const option = ctx.interaction.options.getString('usuario');
if (!option) {
const imagesArray = [
@@ -77,9 +74,9 @@ export default commandModule({
await ctx.reply({ content: 'A', files: [images] });
} else {
if (choices.indexOf(options[1].getString('usuario', true)) > -1) {
if (choices.indexOf(option) > -1) {
const attachmentbuilder = new AttachmentBuilder(
`./images/a/${options[1].getString('usuario', true)}.png`
`./images/a/${option}.png`
);
await ctx.reply({ content: 'A', files: [attachmentbuilder] });
} else {

View File

@@ -1,16 +1,11 @@
import { commandModule, CommandType } from '@sern/handler'
import axios from "axios";
import { ActionRowBuilder, ApplicationCommandOptionType, ButtonBuilder, ButtonStyle, ComponentType, EmbedBuilder } from "discord.js";
import { publish } from "#plugins";
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
name: 'animal',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Enseña un animal',
//alias : [],
options: [
@@ -40,8 +35,8 @@ export default commandModule({
type: ApplicationCommandOptionType.Subcommand
}
],
execute: async (ctx, options) => {
switch (options[1].getSubcommand()) {
execute: async (ctx) => {
switch (ctx.options.getSubcommand()) {
case 'gato': {
const request = await axios.get(`https://api.thecatapi.com/v1/images/search?api_key=${process.env.CATAPI}`).then(res => res.data)
const embed = new EmbedBuilder()
@@ -92,7 +87,7 @@ export default commandModule({
"sub_id": i.user.id,
"value": -1
})
i.editReply({content: "Has votado negativamente al gato con ID " + "`" + request[0].id + "`"})
i.editReply({content: `Has votado negativamente al gato con ID \`${request[0].id}\``})
}
})
collector.on('end', async (i) => {
@@ -100,15 +95,11 @@ export default commandModule({
})
}
case 'capybara': {
const request = await axios('https://api.capybara-api.xyz/v1/image/random').then(res => res.data)
const requestfacts = await axios('https://api.capybara-api.xyz/v1/facts/random').then(res => res.data)
const embed = new EmbedBuilder()
.setAuthor({name: ctx.user.username, iconURL: ctx.user.displayAvatarURL()})
.setTitle('Capybara')
.setDescription(`Fun fact: ${requestfacts.fact}`)
.setColor('Random')
.setImage(request.image_urls.medium)
.setFooter({text: `ID: ${request.id}`})
.setImage('https://api.capy.lol/v1/capybara');
await ctx.interaction.reply({embeds: [embed]})
}
case 'zorro': {
@@ -178,7 +169,7 @@ export default commandModule({
})
}
case 'mapache': {
const request = await axios('https://some-random-api.ml/animal/raccoon').then(res => res.data)
const request = await axios('https://some-random-api.com/animal/raccoon').then(res => res.data)
const embed = new EmbedBuilder()
.setAuthor({name: ctx.user.username, iconURL: ctx.user.displayAvatarURL()})
.setTitle('Mapache')

View File

@@ -1,14 +1,9 @@
import { commandModule, CommandType } from '@sern/handler';
import { publish } from '#plugins';
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
name: 'cursivify',
type: CommandType.CtxMsg,
plugins: [publish()],
plugins: [],
execute: async (ctx) => {
await ctx.deferReply()
const trimmedstring = ctx.targetMessage.content.replaceAll('*', '');

View File

@@ -10,13 +10,13 @@ import {
EmbedBuilder,
} from 'discord.js';
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'busca cosas en gogoanime',
//alias : [],
options: [
@@ -91,10 +91,10 @@ export default commandModule({
execute: async (ctx, options) => {
const gogoanime = new ANIME.Gogoanime();
const doubleslashregex = new RegExp('(?<!:)\/\/+')
switch (options[1].getSubcommand()) {
switch (ctx.options.getSubcommand()) {
case 'buscar': {
await ctx.interaction.deferReply()
const option = options[1].getString('palabra-clave', true);
const option = ctx.options.getString('palabra-clave', true);
const search = await gogoanime.search(option);
const editedarray = await Promise.all(
search.results
@@ -132,7 +132,7 @@ export default commandModule({
})
} break;
case 'capitulo': {
const selepisode = options[1].getString('id-capitulo', true)
const selepisode = ctx.options.getString('id-capitulo', true)
try {
const search = await gogoanime.fetchEpisodeServers(selepisode)
const arrayed = await Promise.all(search.map((server) => `[${server.name}](<${server.url!.replace(doubleslashregex, '/')}>)`))
@@ -143,7 +143,7 @@ export default commandModule({
} break;
case 'info': {
try {
const option = options[1].getString('id', true)
const option = ctx.options.getString('id', true)
const info = await gogoanime.fetchAnimeInfo(option)
const embed = new EmbedBuilder()
.setColor('Random')

View File

@@ -1,13 +1,13 @@
import { commandModule, CommandType } from '@sern/handler'
import axios from "axios";
import { publish } from "#plugins";
export default commandModule({
name: 'chiste',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Enseña un chiste en inglés.',
execute: async (ctx, args) => {
execute: async (ctx) => {
const jokeJSON = await axios(
'https://v2.jokeapi.dev/joke/Programming,Miscellaneous,Spooky,Christmas?blacklistFlags=nsfw,religious,racist,sexist,explicit'
).then((res) => res.data);

View File

@@ -11,13 +11,13 @@ import {
import axios from 'axios';
import https from 'node:https'
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
//alias : [],
description: 'no one will read this (i hope)',
options: [
@@ -40,15 +40,15 @@ export default commandModule({
],
},
],
execute: async (ctx, options) => {
execute: async (ctx) => {
await ctx.interaction.deferReply();
switch (options[1].getSubcommand()) {
switch (ctx.options.getSubcommand()) {
case 'heartlocket':
{
try {
// get all options
const text = options[1].getString('texto');
const image = options[1].getAttachment('imagen', true);
const text = ctx.options.getString('texto', true);
const image = ctx.options.getAttachment('imagen', true);
// check file extension of attachment
if (
@@ -110,6 +110,7 @@ export default commandModule({
Authorization: process.env.MAKESWEET!,
},
responseType: 'arraybuffer',
httpsAgent: new https.Agent({ rejectUnauthorized: false })
}
)
.then((res) => res.data);
@@ -120,6 +121,7 @@ export default commandModule({
Authorization: process.env.MAKESWEET!,
},
responseType: 'arraybuffer',
httpsAgent: new https.Agent({ rejectUnauthorized: false })
})
.then((res) => res.data);
}

View File

@@ -1,16 +1,16 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";
import Canvas from '@napi-rs/canvas';
import { ApplicationCommandOptionType, AttachmentBuilder } from 'discord.js';
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
// , '928018226330337280'
description: 'Añade a una imagen de megamind "No ...?"',
//alias : [],
@@ -22,8 +22,8 @@ export default commandModule({
required: true
}
],
execute: async (ctx, options) => {
const option = options[1].getString('texto', true)
execute: async (ctx) => {
const option = ctx.options.getString('texto', true)
await ctx.reply({content: 'Cargando...'})

View File

@@ -1,12 +1,12 @@
import { commandModule, CommandType } from '@sern/handler'
import { ActionRowBuilder, ApplicationCommandOptionType, ButtonBuilder, ButtonStyle, ComponentType, EmbedBuilder, GuildMember } from "discord.js";
import { publish } from "#plugins";
import rockpaperscissors from "rockpaperscissors-checker";
export default commandModule({
name: 'rps',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Juega piedra papel tijeras con los panas',
//alias : [],
options: [
@@ -17,10 +17,10 @@ export default commandModule({
required: true
}
],
execute: async (ctx, options) => {
execute: async (ctx) => {
// also the code is mine, I didn't steal from anyone
let player1, player2, winner, bothResponded
const option = options[1].getMember('usuario') as GuildMember
let player1: number, player2: number, winner, bothResponded: boolean
const option = ctx.options.getMember('usuario') as unknown as GuildMember
if (ctx.user.id === option.id) {
return await ctx.reply({content: `no puedes jugar contigo mismo 💀`, ephemeral: true})
} else if (option.user.bot) {

View File

@@ -1,14 +1,13 @@
import TicTacToe from 'discord-tictactoe';
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";
import { ApplicationCommandOptionType, ChatInputCommandInteraction, CommandInteraction, Interaction } from "discord.js";
import { ApplicationCommandOptionType } from "discord.js";
const game = new TicTacToe({language: 'en'})
export default commandModule({
name: 'tictactoe',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'tres en raya',
//alias : [],
options: [

View File

@@ -8,7 +8,7 @@ import { execa } from 'execa';
export default commandModule({
type: CommandType.CtxMsg,
plugins: [publish()],
plugins: [],
execute: async (ctx) => {
await ctx.deferReply({ fetchReply: true })
@@ -22,11 +22,11 @@ export default commandModule({
const randomnumber = random(5)
const randomnumber_wav = `bonzi-wav-${randomnumber}.wav`
const randomnumber_mp3 = `bonzi-mp3-${randomnumber}.mp3`
fs.writeFileSync(`./util/bonzi_temp/${randomnumber_wav}`, new Uint8Array(request))
fs.writeFileSync(`./src/util/bonzi_temp/${randomnumber_wav}`, new Uint8Array(request))
const command = execa('ffmpeg', [
'-i', `./util/bonzi_temp/${randomnumber_wav}`,
'-i', `./src/util/bonzi_temp/${randomnumber_wav}`,
'-vn',
`./util/bonzi_temp/${randomnumber_mp3}`
`./src/util/bonzi_temp/${randomnumber_mp3}`
], { shell: true })
await new Promise((resolve) => {
command.on('close', resolve)
@@ -34,12 +34,12 @@ export default commandModule({
const stream = new Readable();
stream._read = () => {};
stream.push(Buffer.from(new Uint8Array(fs.readFileSync(`./util/bonzi_temp/${randomnumber_mp3}`))));
stream.push(Buffer.from(new Uint8Array(fs.readFileSync(`./src/util/bonzi_temp/${randomnumber_mp3}`))));
stream.push(null)
const attachment = new AttachmentBuilder(stream, { name: 'bonzied.mp3' })
fs.unlinkSync(`./util/bonzi_temp/${randomnumber_mp3}`)
fs.unlinkSync(`./util/bonzi_temp/${randomnumber_wav}`)
fs.unlinkSync(`./src/util/bonzi_temp/${randomnumber_mp3}`)
fs.unlinkSync(`./src/util/bonzi_temp/${randomnumber_wav}`)
await ctx.editReply({ files: [attachment] })
},

View File

@@ -5,7 +5,6 @@ import {
ButtonBuilder,
ButtonStyle,
EmbedBuilder,
TextChannel,
} from 'discord.js';
export default commandModule({
@@ -53,11 +52,14 @@ export default commandModule({
.setLabel('Añadido a la whitelist!')
.setStyle(ButtonStyle.Success)
);
(
(await (
await modal.client.guilds.fetch(process.env.GUILDID!)
).channels.fetch(process.env.MCFORM_CHANNEL!)) as TextChannel
).send({ embeds: [embed], components: [button] });
const guild = await modal.client.guilds.fetch(process.env.GUILDID!);
const channel = await guild.channels.fetch(process.env.MCFORM_CHANNEL!);
if (channel && channel.isTextBased()) {
await channel.send({
embeds: [embed],
components: [button],
});
}
} catch {
await modal.reply({
content:

View File

@@ -15,7 +15,7 @@ export default commandModule({
// @ts-ignore
).options.map((o: { label: string; value: string }) => o.value);
const member = interaction.member as GuildMember;
const member = interaction.member! as unknown as GuildMember;
if (!member) return;
let content = `Los roles han sido actualizados. Te he dado estos:\n${roles

View File

@@ -1,7 +1,6 @@
import { commandModule, CommandType } from '@sern/handler';
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js';
import {
TextChannel,
ThreadAutoArchiveDuration,
} from 'discord.js';
@@ -52,10 +51,17 @@ export default commandModule({
.setStyle(ButtonStyle.Secondary),
)
const message1 = await (await modal.client.guilds.fetch(process.env.GUILDID!))
.channels.fetch(process.env.SUGGESTIONS_CHANNEL!) as TextChannel;
const message2 = await message1.send({ embeds: [embed], components: [row, row2] });
message2.startThread({
const guild = await modal.client.guilds.fetch(process.env.GUILDID!);
const channel = await guild.channels.fetch(process.env.SUGGESTIONS_CHANNEL!);
if (!channel || !channel.isTextBased()) {
return await modal.reply({
content: 'ERROR: Canal de sugerencias no encontrado.',
ephemeral: true,
});
}
const msg = await channel.send({ embeds: [embed], components: [row, row2] });
msg.startThread({
name: `Sugerencia de ${modal.user.username}`,
autoArchiveDuration: ThreadAutoArchiveDuration.ThreeDays,
reason: 'AUTOMATIZADO: Hilo para discutir sobre la sugerencia.',

View File

@@ -1,11 +1,11 @@
import { commandModule, CommandType } from "@sern/handler";
import { ActionRowBuilder, ButtonBuilder, ButtonComponentData, ButtonInteraction, ButtonStyle, ComponentType } from "discord.js";
import { ActionRowBuilder, ButtonBuilder, ButtonComponent, ButtonComponentData, ButtonStyle } from "discord.js";
import db from "../../schemas/suggestions.js";
export default commandModule({
type: CommandType.Button,
async execute(interaction) {
const convertToNumber = Number(interaction.component.label!)
const convertToNumber = Number((interaction.component as ButtonComponent).label!)
const row2 = new ActionRowBuilder<ButtonBuilder>().setComponents(
new ButtonBuilder(interaction.message!.components[1].components[0].data as ButtonComponentData),
new ButtonBuilder(interaction.message!.components[1].components[1].data as ButtonComponentData)

View File

@@ -1,5 +1,4 @@
import { commandModule, CommandType } from "@sern/handler";
import { ActionRowBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, ComponentType } from "discord.js";
import db from "../../schemas/suggestions.js";
export default commandModule({

View File

@@ -1,11 +1,11 @@
import { commandModule, CommandType } from "@sern/handler";
import { ActionRowBuilder, ButtonBuilder, ButtonComponentData, ButtonInteraction, ButtonStyle, ComponentType } from "discord.js";
import { ActionRowBuilder, ButtonBuilder, ButtonComponent, ButtonComponentData, ButtonStyle } from "discord.js";
import db from "../../schemas/suggestions.js";
export default commandModule({
type: CommandType.Button,
async execute(interaction) {
const convertToNumber = Number(interaction.component.label!)
const convertToNumber = Number((interaction.component as ButtonComponent).label!)
const row2 = new ActionRowBuilder<ButtonBuilder>().setComponents(
new ButtonBuilder(interaction.message!.components[1].components[0].data as ButtonComponentData),
new ButtonBuilder(interaction.message!.components[1].components[1].data as ButtonComponentData)

View File

@@ -1,16 +1,10 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";
import { ApplicationCommandOptionType } from "discord.js";
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
name: 'ip',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
//
description: 'La IP del servidor de Minecraft',
options: [
@@ -22,7 +16,7 @@ export default commandModule({
],
//alias : [],
execute: async (ctx, options) => {
const usuario = options[1].getMember('usuario');
const usuario = ctx.options.getMember('usuario');
if (!usuario) {
await ctx.reply({content: "La IP del servidor de Minecraft es `minecraft.maraturing.com`,\nPide acceso con el comando </mcform:1000747672690499594>.", ephemeral: true})

View File

@@ -6,13 +6,11 @@ import {
TextInputStyle,
ModalActionRowComponentBuilder,
} from 'discord.js';
import { publish } from '#plugins';
import { ownerOnly } from '#plugins';
export default commandModule({
name: 'mcform',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Envia el formulario para entrar al servidor.',
//alias : [],
execute: async (ctx) => {

View File

@@ -1,16 +1,10 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
import db from '../../schemas/afk.js';
import { ApplicationCommandOptionType, EmbedBuilder } from 'discord.js';
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'afk command',
//alias : [],
options: [
@@ -38,11 +32,11 @@ export default commandModule({
type: ApplicationCommandOptionType.Subcommand
}
],
execute: async (ctx, options) => {
switch (options[1].getSubcommand()) {
execute: async (ctx) => {
switch (ctx.options.getSubcommand()) {
case 'añadir': {
if (await db.exists({ id: ctx.user.id })) return ctx.reply({ content: 'Ya existes en la base de datos!', ephemeral: true })
const reason = options[1].getString('motivo', true);
const reason = ctx.options.getString('motivo');
await (new db({ id: ctx.user.id, reason: reason })).save()
const embed = new EmbedBuilder()

View File

@@ -11,16 +11,15 @@ import {
ModalSubmitInteraction,
ApplicationCommandOptionType,
} from 'discord.js';
import { publish } from '#plugins';
import { ownerOnly } from '#plugins';
import padyama from '../../schemas/padyama.js';
import { random } from '../../util/randomstring.js';
import { disable } from '#plugins';
export default commandModule({
name: 'askjavi',
type: CommandType.Slash,
plugins: [
// publish(),
disable()
],
description: 'TEMP: Pregunta a Javi LO QUE SEA!',
//alias : [],
@@ -63,7 +62,7 @@ export default commandModule({
],
},
],
execute: async (ctx, options) => {
execute: async (ctx) => {
switch (ctx.interaction.options.getSubcommand()) {
case 'new': {
const modal = new ModalBuilder()
@@ -110,7 +109,7 @@ export default commandModule({
time: 180000,
filter: (i) => i.user.id === ctx.user.id,
})
.catch((error) => {})) as ModalSubmitInteraction;
.catch((error) => {})) as unknown as ModalSubmitInteraction;
const db = new padyama({
id: i.user.id,
user: i.user.username,
@@ -132,7 +131,7 @@ export default commandModule({
});
}
case 'get': {
const option = options[1].getString('id');
const option = ctx.options.getString('id');
const db = await padyama.findOne({ suggestionid: option });
if (db?.suggestion !== undefined)
return await ctx.reply({
@@ -160,7 +159,7 @@ export default commandModule({
content: `No puedes usar este comando.`,
ephemeral: true,
});
const option = options[1].getString('id');
const option = ctx.options.getString('id');
const db = await padyama.findOne({ suggestionid: option });
if (db?.user !== undefined) {
try {

View File

@@ -1,16 +1,14 @@
import { commandModule, CommandType } from '@sern/handler'
import { Context, SlashOptions } from "@sern/handler";
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
import { publish } from "#plugins";
export default commandModule({
name: 'creditos',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Créditos del bot (en inglés)',
//alias : [],
options: [],
execute: async (ctx, options) => {
execute: async (ctx) => {
const baseEmbed = new EmbedBuilder()
.setColor('Blurple')
.setTitle(`Without these people, the bot wouldn't exist!`)

View File

@@ -1,18 +1,13 @@
import { commandModule, CommandType } from "@sern/handler";
import { publish } from "#plugins";
import { ApplicationCommandOptionType } from "discord.js";
import { readFileSync } from "node:fs";
import birthdays from "../../schemas/birthdays.js";
import { acceptingBirthday } from "#plugins";
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
name: "cumple",
type: CommandType.Slash,
plugins: [publish(), acceptingBirthday()],
plugins: [acceptingBirthday()],
description: "Pon tu cumpleaños en la base de datos para ser felicitado!",
//alias : [],
options: [
@@ -43,7 +38,7 @@ export default commandModule({
},
},
],
execute: async (ctx, options) => {
execute: async (ctx) => {
const option = ctx.interaction.options.getString("fecha")
const array = JSON.parse(
String(readFileSync("./util/daysinyear.txt"))

View File

@@ -1,12 +1,10 @@
import { commandModule, CommandType } from '@sern/handler'
import { ApplicationCommandOptionType, ColorResolvable, EmbedBuilder } from 'discord.js';
import { publish } from "#plugins";
import fs from 'node:fs';
import mctags from '../../util/tags/minecraft.json' assert { type: 'json' }
import mctags from '../../../assets/mcTags.json' with { type: "json" };
export default commandModule({
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Preguntas normalmente preguntadas :pepega:',
//alias : [],
options: [
@@ -40,11 +38,11 @@ export default commandModule({
]
}
],
execute: async (ctx, options) => {
switch (options[1].getSubcommand()) {
execute: async (ctx) => {
switch (ctx.options.getSubcommand()) {
case 'minecraft': {
const option = options[1].getString('pregunta', true)
const forusr = options[1].getMember('para')
const option = ctx.options.getString('pregunta', true)
const forusr = ctx.options.getMember('para')
const filter = mctags.filter(obj => obj.title.includes(option))[0]
const embed = new EmbedBuilder()

View File

@@ -1,15 +1,10 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import google from 'googlethis'
import { ApplicationCommandOptionType } from 'discord.js';
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Busca cosas en Google.',
//alias : [],
options: [
@@ -20,9 +15,9 @@ export default commandModule({
required: true,
}
],
execute: async (ctx, options) => {
execute: async (ctx) => {
await ctx.interaction.deferReply()
const prompt = options[1].getString('busqueda', true)
const prompt = ctx.options.getString('busqueda', true)
const search = await Promise.all((await google.search(prompt)).results.map(res => {
return `[${res.title}](<${res.url}>)`

View File

@@ -0,0 +1,56 @@
import { commandModule, CommandType } from '@sern/handler';
import { publish } from '#plugins';
import { AttachmentBuilder, codeBlock } from 'discord.js';
import { createCanvas, loadImage } from '@napi-rs/canvas';
import sharp from 'sharp';
export default commandModule({
name: 'Clasifica una imagen',
type: CommandType.CtxMsg,
plugins: [],
execute: async (ctx) => {
await ctx.deferReply()
if (ctx.targetMessage.attachments.size === 0) return ctx.editReply('No hay ninguna imagen para clasificar!');
const image = ctx.targetMessage.attachments.first()!;
if (!image.contentType!.startsWith('image/') && image.contentType !== 'image/gif') return ctx.editReply('El archivo no es una imagen!');
const imageBuffer = await fetch(image.url).then(async res => await res.arrayBuffer());
const compressed = sharp(imageBuffer)
.png({quality: 70})
.jpeg({quality: 70})
.webp({quality: 70})
.tiff({quality: 70});
const metadata = await compressed.metadata();
const imageUint8Array = new Uint8Array(await compressed.toBuffer());
const request = await fetch(`https://api.cloudflare.com/client/v4/accounts/${process.env.CF_AI_ACC}/ai/run/@cf/facebook/detr-resnet-50`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CF_AI_TOKEN}`,
},
body: imageUint8Array,
}).then(async res => await res.json());
if (request.errors.length > 0) return ctx.editReply(`Hubo un error! ${codeBlock(JSON.stringify(request.errors))}`);
// all canvas stuff, this was fun to make
const canvas = createCanvas(metadata.width!, metadata.height!);
const ctxCanvas = canvas.getContext('2d');
const img = await loadImage(image.url);
ctxCanvas.drawImage(img, 0, 0, metadata.width!, metadata.height!);
ctxCanvas.font = '40px sans-serif';
ctxCanvas.fillStyle = 'red';
ctxCanvas.strokeStyle = 'red';
ctxCanvas.lineWidth = 3;
for (const result of request.result) {
if (result.score < 0.5) continue;
const box = result.box;
ctxCanvas.strokeRect(box.xmin, box.ymin, box.xmax - box.xmin, box.ymax - box.ymin);
ctxCanvas.fillText(result.label, box.xmin, box.ymin - 5);
}
const canvasBuffer = canvas.toBuffer('image/png');
const attachment = new AttachmentBuilder(canvasBuffer, { name: 'generatedImage.png' });
await ctx.editReply({ files: [attachment] })
},
});

View File

@@ -0,0 +1,97 @@
import { commandModule, CommandType } from '@sern/handler';
import { ApplicationCommandOptionType, bold, codeBlock, EmbedBuilder, inlineCode } from 'discord.js';
import fs from 'node:fs/promises'
import { ICFile } from '../../util/infinitecraft/decompress.js';
import Finder from '../../util/infinitecraft/finder.js';
const recipeFile = JSON.parse(await fs.readFile('./assets/icRecipes.json', 'utf-8')) as ICFile;
export default commandModule({
type: CommandType.Slash,
plugins: [],
description: 'Descifra con un algoritmo cómo llegar a la receta de un objeto en InfiniteCraft',
options: [
{
name: 'objeto',
description: 'El objeto que quieres descifrar',
type: ApplicationCommandOptionType.String,
required: true,
autocomplete: true,
command: {
onEvent: [],
async execute(ctx) {
const input = ctx.options.getFocused();
const choices = recipeFile.items
.filter(item => item.toLowerCase().includes(input.toLowerCase()))
.slice(0, 25)
if (choices.length === 0) return ctx.respond([{ name: 'No se encontraron resultados', value: 'SSError' }]);
if (input.length === 0) return ctx.respond([{ name: 'Empieza a escribir', value: 'SSError' }]);
await ctx.respond(
choices.map(choice => {
return ({ name: choice, value: choice })
})
)
},
}
}
],
execute: async (ctx) => {
await ctx.interaction.deferReply()
const object = ctx.options.getString('objeto', true)
if (object === 'SSError') return ctx.reply({
content: 'Has escogido el mensaje de error 💀',
ephemeral: true
});
if (!recipeFile.items.includes(ctx.options.getString('objeto', true))) return ctx.reply({
content: 'No se encontró el objeto. Asegúrate de escogerlo del autocompletado.',
ephemeral: true
});
let processed = 0
let timesBacked = 0
const infinitePath = new Finder((progress) => {
switch (progress.phase) {
case 0:
processed = progress.current
break;
case 1:
timesBacked = timesBacked + 1
break;
}
})
const interval = setInterval(() => {
ctx.interaction.editReply({
content: `Procesando...\n${inlineCode(processed.toString())} recetas procesadas, ${inlineCode(timesBacked.toString())} veces retrocedido.`,
})
}, 1250)
ctx.interaction.editReply({
content: `Procesando...\n${inlineCode(processed.toString())} recetas procesadas, ${inlineCode(timesBacked.toString())} veces retrocedido.`,
})
const initialTime = performance.now()
const path = await infinitePath.findItem(object)
const finalTime = performance.now()
clearInterval(interval)
if (path.length === 0) return ctx.interaction.editReply({
content: 'No se encontró la receta de este objeto.'
});
const recipe = path.map(({ first, second, result }) => `${first} + ${second} = ${result}`).join('\n')
let paste: string | undefined;
if (recipe.length >= 1500) {
paste = await fetch('https://fb.srizan.dev/paste', {
method: 'POST',
body: recipe
}).then(async res => await res.text())
}
const embed = new EmbedBuilder()
.setTitle(`Receta de ${object.toLowerCase()}`)
.setColor('Green')
.setDescription(paste ? `La ruta es demasiado grande, así que lo he puesto en un pastebin:\nhttps://fb.srizan.dev/${paste}` : codeBlock(recipe))
.setFooter({ text: 'Ya que se usa un algoritmo, puede o no ser la ruta más rápida para llegar al ítem' })
return ctx.interaction.editReply({
embeds: [embed],
content: `Por fin encontrado! La búsqueda tomó ${bold(((finalTime - initialTime) / 1000).toFixed(2))} segundos.`
})
},
});

View File

@@ -1,5 +1,4 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import Genius from 'genius-lyrics'
import { ActionRowBuilder, ApplicationCommandOptionType, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js';
@@ -7,7 +6,7 @@ const genius = new Genius.Client(process.env.GENIUS)
export default commandModule({
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Busca la letra de una canción (Genius)',
//alias : [],
options: [
@@ -34,9 +33,9 @@ export default commandModule({
}
}
],
execute: async (ctx, options) => {
execute: async (ctx) => {
await ctx.interaction.deferReply({ ephemeral: true })
const prompt = options[1].getString('busqueda', true)
const prompt = ctx.options.getString('busqueda', true)
const result = await genius.songs.get(Number(prompt))

View File

@@ -1,17 +1,13 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { createAudioPlayer, createAudioResource, DiscordGatewayAdapterCreator, joinVoiceChannel } from "@discordjs/voice";
import got from "got";
import { ApplicationCommandOptionType, EmbedBuilder } from "discord.js";
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
const choices = ['Rock FM', 'Cadena 100', 'Cadena Dial', 'Gensokyo Radio', 'BBC 1', 'RNE 1', 'RNE 5', 'Los 40'];
export default commandModule({
name: 'radio',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Reproduce la radio',
options: [
{
@@ -24,7 +20,6 @@ export default commandModule({
onEvent: [],
async execute(ctx){
const focusedValue = ctx.options.getFocused();
const choices = ['Rock FM', 'Cadena 100', 'Cadena Dial', 'Gensokyo Radio', 'BBC 1', 'RNE 1', 'RNE 5', 'Los 40', 'Flaixbac', 'FlaixFM'];
const filtered = choices.filter(choice => choice.startsWith(focusedValue));
await ctx.respond(
filtered.map(choice => ({ name: choice, value: choice })),
@@ -33,8 +28,9 @@ export default commandModule({
}
}
],
execute: async (ctx, options) => {
const radioname = options[1].getString("reproducir", true)
execute: async (ctx) => {
const radioname = ctx.options.getString("reproducir", true)
if (!choices.includes(radioname)) return await ctx.reply('Asegúrate de escoger una radio de la lista.')
const embed = new EmbedBuilder()
.setColor("Green")
.setTitle(`Reproduciendo ${radioname} en Vinci Radio.`)
@@ -44,18 +40,14 @@ export default commandModule({
.setTitle(`Radio ${radioname} no encontrada.`)
.setDescription(`La radio no ha sido encontrada, asegúrate que la radio está escogida de la lista.`);
async function playRadio(radioname: string, isFlaix?: boolean) {
async function playRadio(radioname: string) {
const stream = got.stream(radioname)
const connection = joinVoiceChannel({adapterCreator: ctx.interaction.guild!.voiceAdapterCreator as DiscordGatewayAdapterCreator, channelId: '1008730592835281009',guildId: '928018226330337280',selfDeaf: true});
const resource = createAudioResource(stream, { inlineVolume: true });
const player = createAudioPlayer();
connection.subscribe(player)
player.play(resource)
if (isFlaix === true) {
resource.volume!.setVolume(0.3)
} else {
resource.volume!.setVolume(0.7)
}
resource.volume!.setVolume(0.7)
await ctx.reply({embeds: [embed], ephemeral: true})
}
@@ -84,12 +76,6 @@ export default commandModule({
case 'Gensokyo Radio': {
playRadio('https://stream.gensokyoradio.net/3')
} break;
case 'Flaixbac': {
playRadio('https://nodo07-cloud01.streaming-pro.com:8005/flaixbac.mp3', true)
} break;
case 'FlaixFM': {
playRadio('https://nodo07-cloud01.streaming-pro.com:8001/flaixfm.mp3', true)
} break;
default: {
await ctx.reply({embeds: [notFoundEmbed], ephemeral: true})
} break;

View File

@@ -1,13 +1,13 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";
import { ActionRowBuilder, ApplicationCommandOptionType, ChannelType, Collection, EmbedBuilder, Role, SelectMenuBuilder, TextChannel } from "discord.js";
import { ActionRowBuilder, ApplicationCommandOptionType, ChannelType, ChatInputCommandInteraction, Collection, EmbedBuilder, Role, StringSelectMenuBuilder, TextChannel } from "discord.js";
import { Resolver } from "../../util/resolver.js";
export default commandModule({
name: 'rolemenu',
type: CommandType.Slash,
plugins: [publish(), ownerOnly()],
plugins: [ownerOnly()],
description: 'ADMIN: Spawnea un menú de roles',
//alias : [],
options: [
@@ -31,11 +31,13 @@ export default commandModule({
required: true,
},
],
execute: async (ctx, options) => {
const channel = options[1].getChannel("channel", true) as TextChannel;
const role = new Resolver(options[1].getString("role", true), ctx.interaction)
execute: async (ctx) => {
const channel = ctx.options.getChannel("channel", true) as unknown as TextChannel;
if (!channel.isSendable()) return ctx.reply("Channel is not sendable");
// @ts-ignore it should still be a correct interaction
const role = new Resolver(ctx.options.getString("role", true), ctx.interaction)
.roles;
const message = options[1].getString("message", true);
const message = ctx.options.getString("message", true);
if (role.size > 25) return ctx.reply("Too many roles");
@@ -50,6 +52,7 @@ export default commandModule({
);
}
await ctx.interaction.deferReply();
// @ts-ignore it should still be a textchannel
const row = createMenu(channel, role);
const embed = new EmbedBuilder()
.setTitle(message)
@@ -67,7 +70,7 @@ export default commandModule({
function createMenu(channel: TextChannel, role: Collection<string, Role>) {
if (!channel || !role) throw new Error("Missing channel or role");
const menu = new SelectMenuBuilder()
const menu = new StringSelectMenuBuilder()
.setCustomId("role-menu")
.setMaxValues(role.size)
.setMinValues(0)
@@ -80,6 +83,6 @@ function createMenu(channel: TextChannel, role: Collection<string, Role>) {
};
})
);
const row = new ActionRowBuilder<SelectMenuBuilder>().setComponents(menu);
const row = new ActionRowBuilder<StringSelectMenuBuilder>().setComponents(menu);
return row;
};

View File

@@ -1,13 +1,13 @@
import { commandModule, CommandType } from "@sern/handler";
import axios, { AxiosError, AxiosResponse } from "axios";
import axios, { AxiosResponse } from "axios";
import { ApplicationCommandOptionType } from "discord.js";
import { publish } from "#plugins";
import { disable } from "#plugins";
export default commandModule({
name: "acortar",
type: CommandType.Slash,
plugins: [
publish(),
disable()
],
description: "Acorta una URL a vinci.tk",
options: [
@@ -20,7 +20,7 @@ export default commandModule({
],
//alias : [],
execute: async (ctx, options) => {
const url = options[1].getString("url", true);
const url = ctx.options.getString("url", true);
const request = await axios(
`https://vinci.tk/yourls-api.php?signature=${process.env.YOURLS_KEY}&action=shorturl&format=json&url=${url}`,
{

View File

@@ -1,14 +1,14 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";
import { disable } from "#plugins";
import { EmbedBuilder } from "discord.js";
import axios from "axios";
// @ts-ignore
import prettySeconds from 'pretty-seconds-spanish'
export default commandModule({
name: 'stats',
type: CommandType.Slash,
plugins: [publish()],
plugins: [disable()],
description: 'Enseña estadísticas del bot.',
//alias : [],
options: [],

View File

@@ -1,13 +1,11 @@
import { commandModule, CommandType } from '@sern/handler'
import { ActionRowBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, ModalActionRowComponentBuilder } from 'discord.js'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";
export default commandModule({
name: 'sugerencias',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Envia una sugerencia.',
//alias : [],
execute: async (ctx) => {

View File

@@ -1,18 +1,12 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";
import axios from 'axios';
import { readFileSync } from 'node:fs'
import { ActionRowBuilder, ApplicationCommandOptionType, ButtonBuilder, ButtonStyle, ComponentType, EmbedBuilder } from 'discord.js';
const choices = ['es', 'en', 'fr', 'de', 'hi', 'it', 'ja', 'ko', 'pl']
/*
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
*/
export default commandModule({
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
// , '928018226330337280'
description: 'Traduce lo que quieras!',
//alias : [],
@@ -44,9 +38,9 @@ export default commandModule({
}
}
],
execute: async (ctx, options) => {
const langToTranslate = options[1].getString('idioma', true)
const stringToTranslate = options[1].getString('frase', true)
execute: async (ctx) => {
const langToTranslate = ctx.options.getString('idioma', true)
const stringToTranslate = ctx.options.getString('frase', true)
if (choices.indexOf(langToTranslate) === -1)
return ctx.reply({content: 'Elige un idioma del autocompletado.'})

View File

@@ -1,15 +1,15 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
// @ts-ignore
import prettySeconds from 'pretty-seconds-spanish'
export default commandModule({
name: 'uptime',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Enseña el tiempo que ha estado encendido el bot.',
//alias : [],
options: [],
execute: async (ctx, options) => {
execute: async (ctx) => {
// const uptime = prettyMilliseconds(ctx.client.uptime!)
const uptime = prettySeconds(process.uptime())
await ctx.reply(`El bot lleva encendido ${uptime}`);

View File

@@ -1,11 +1,10 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ApplicationCommandOptionType } from 'discord.js';
import { getWikipedia, searchWikipedia, SearchWikipediaObject } from '../../util/wikipedia.js';
import { ApplicationCommandOptionType, AutocompleteInteraction, CacheType, CommandInteractionOptionResolver } from 'discord.js';
import { getWikipedia, searchWikipedia } from '../../util/wikipedia.js';
export default commandModule({
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'Busca cosas por wikipedia',
//alias : [],
options: [
@@ -23,7 +22,7 @@ export default commandModule({
command: {
onEvent: [],
execute: async (ctx) => {
const search = await searchWikipedia('es', ctx)
const search = await searchWikipedia('es', ctx as unknown as AutocompleteInteraction)
await ctx.respond(
search.map(res => ({ name: res.title.toString(), value: res.pageid.toString() }))
)
@@ -46,7 +45,7 @@ export default commandModule({
command: {
onEvent: [],
execute: async (ctx) => {
const search = await searchWikipedia('en', ctx)
const search = await searchWikipedia('en', ctx as unknown as AutocompleteInteraction)
await ctx.respond(
search.map(res => ({ name: res.title.toString(), value: res.pageid.toString() }))
)
@@ -56,13 +55,14 @@ export default commandModule({
]
}
],
execute: async (ctx, [, options]) => {
switch (options.getSubcommand()) {
execute: async (ctx) => {
const options = ctx.options as unknown as CommandInteractionOptionResolver<CacheType>
switch (ctx.options.getSubcommand()) {
case 'español': {
getWikipedia('es', ctx, options)
getWikipedia('es', ctx, options);
} break;
case 'ingles': {
getWikipedia('en', ctx, options)
getWikipedia('en', ctx, options);
} break;
}
},

View File

@@ -1,12 +1,12 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";;
import { ApplicationCommandOptionType, TextChannel } from 'discord.js'
export default commandModule({
name: 'prune',
type: CommandType.Slash,
plugins: [publish(), ownerOnly()],
plugins: [ownerOnly()],
description: 'ADMIN: Elimina hasta 100 mensajes',
options: [{
name: 'numero',
@@ -17,14 +17,14 @@ export default commandModule({
max_value: 100
}],
//alias : [],
execute: async (ctx, options) => {
execute: async (ctx) => {
try {
const amount = options[1].getNumber('numero', true) as number
(ctx.channel as TextChannel).bulkDelete(amount).catch(err => {
const amount = ctx.options.getNumber('numero', true) as number
(ctx.channel as unknown as TextChannel).bulkDelete(amount).catch(err => {
console.error(err);
ctx.reply({content: 'Ha habido un error eliminando mensajes! (mira la consola, Sr Izan)', ephemeral: true});});
await ctx.reply({content: `Se han eliminado ${amount} mensajes.`})
const sendToMods = ctx.client.guilds.cache.get(process.env.GUILDID!)!.channels.cache.get(process.env.MODLOGS_CHANNEL!) as TextChannel
const sendToMods = ctx.client.guilds.cache.get(process.env.GUILDID!)!.channels.cache.get(process.env.MODLOGS_CHANNEL!) as unknown as TextChannel
await sendToMods.send({content: `Se han eliminado ${amount} mensajes en ${ctx.channel}\nEfectuado por ${ctx.user}.`})
} catch {
ctx.reply({content: 'Ha habido un error eliminando mensajes! Error reportado automáticamente.', ephemeral: true})};

View File

@@ -1,12 +1,12 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins";
import { ApplicationCommandOptionType, TextChannel } from "discord.js";
export default commandModule({
name: 'slowmode',
type: CommandType.Slash,
plugins: [publish(), ownerOnly()],
plugins: [ownerOnly()],
description: 'ADMIN: Pon modo lento a canales de texto',
options: [
{
@@ -23,15 +23,15 @@ export default commandModule({
}
],
//alias : [],
execute: async (ctx, options) => {
execute: async (ctx) => {
try {
const seconds = options[1].getNumber("segundos", true);
const reason = options[1].getString("razon", true);
const seconds = ctx.options.getNumber("segundos", true);
const reason = ctx.options.getString("razon", true);
(ctx.channel as TextChannel).setRateLimitPerUser(seconds, reason)
(ctx.channel as unknown as TextChannel).setRateLimitPerUser(seconds, reason)
ctx.reply({content: `Se han añadido ${seconds} segundos de modo lento al canal de voz actual`})
const sendToMods = ctx.client.guilds.cache.get(process.env.GUILDID!)!.channels.cache.get(process.env.MODLOGS_CHANNEL!) as TextChannel
const sendToMods = ctx.client.guilds.cache.get(process.env.GUILDID!)!.channels.cache.get(process.env.MODLOGS_CHANNEL!) as unknown as TextChannel
await sendToMods.send({content: `Se ha aplicado modo lento al canal ${ctx.channel}.\nEfectuado por ${ctx.user} con ${seconds} segundos de retardo.\nRazón: ${reason}`})
} catch {
ctx.reply({content: `No se ha podido aplicar modo lento al canal.`})

View File

@@ -1,11 +1,11 @@
import { commandModule, CommandType } from '@sern/handler'
import { publish } from "#plugins";
import { ownerOnly } from "#plugins"
export default commandModule({
name: 'ping',
type: CommandType.Slash,
plugins: [publish()],
plugins: [],
description: 'A ping command',
//alias : [],
options: [],

12
src/config.ts Normal file
View File

@@ -0,0 +1,12 @@
//CONFIG FILE: export only data here and do not cause side effects. Feel free to add your own configuration to this file.
//commands directory. REQUIRED
export const commands = './dist/commands'
// events directory.
export const events = './dist/events'
// schedule tasks and declare them here
//export const tasks = './dist/tasks'
// defaultPrefix: if omitted, sern will disable all text/prefix commands
//export const defaultPrefix = '?'

24
src/dependencies.d.ts vendored Normal file
View File

@@ -0,0 +1,24 @@
/*
* This file serves as intellisense for sern projects.
* Types are declared here for dependencies to function properly
* Service(s) api rely on this file to provide a better developer experience.
*/
import type { CoreDependencies } from '@sern/handler';
import type { Client } from 'discord.js';
import type { Publisher } from '@sern/publisher';
import type Spotify from 'spotify-api.js';
/**
* Note: You usually would not need to modify this unless there is an urgent need to break the contracts provided.
* You would need to modify this to add your custom Services, however.
*/
declare global {
interface Dependencies extends CoreDependencies {
'@sern/client': Client;
'publisher': Publisher;
'spotify-api-client': Spotify.Client;
}
}
export {}

View File

@@ -1,11 +1,10 @@
import { EventType, eventModule } from '@sern/handler';
import { EventType, discordEvent, eventModule } from '@sern/handler';
import { EmbedBuilder, Message } from 'discord.js';
import db from '../schemas/afk.js';
export default eventModule({
type: EventType.Discord,
export default discordEvent({
name: 'messageCreate',
execute: async (message: Message) => {
execute: async (message) => {
const dbEntries = await db.find()
dbEntries.forEach(async (doc) => {

View File

@@ -0,0 +1,101 @@
import { discordEvent } from '@sern/handler';
import db from '../schemas/chatgpt.js';
import { fetchEventSource } from '@ai-zen/node-fetch-event-source';
export default discordEvent({
name: 'messageCreate',
async execute(message) {
if (message.channel.id !== process.env.CHATGPT_CHANNEL) return;
if (message.author.bot) return;
if (message.content.includes('v!ig')) return;
const systemMsg =
"You are Vinci, a helpful Discord bot assistant which tries to answer all questions that your users ask. You MUST speak naturally, if you were texting somebody. Don't tell the user that you are an assistant as they already know. Markdown is supported, including headers, codeblocks, etc. You will also chat with spanish speaking users, so your responses MUST, without exception, be in the spanish language, including your responses down the line.";
try {
await message.channel.sendTyping();
const messages = [
{ role: 'system', content: systemMsg },
{ role: 'user', content: message.content },
];
const ctrl = new AbortController();
let msg = '';
let isDone = false;
const sentMsg = await message.reply(':sparkles: Pensando...');
message.channel.sendTyping();
const sendInterval = setInterval(() => {
if (msg.length > 2000 || msg.length === 0) return;
message.channel.sendTyping();
sentMsg.edit(msg);
if (isDone) {
clearTimeout(sendInterval);
ctrl.abort();
return;
}
}, 1000);
fetchEventSource(
`https://api.cloudflare.com/client/v4/accounts/${process.env.CF_AI_ACC}/ai/run/@cf/meta/llama-2-7b-chat-int8`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CF_AI_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
stream: true,
messages,
}),
onmessage: async (ev) => {
if (ev.data === '[DONE]') {
ctrl.abort();
isDone = true;
await sentMsg.edit({ content: msg });
messages.push({ role: 'assistant', content: msg });
const titleResponse = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${process.env.CF_AI_ACC}/ai/run/@hf/mistral/mistral-7b-instruct-v0.2`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CF_AI_TOKEN}`,
},
body: JSON.stringify({
messages: [
{
role: 'user',
// the "else you'll die" part actually works well for the prompt!
content: `Generate a title for the following conversation. Only respond with the title, else you'll die:\\nUser: ${message.content}\\nAssistant: ${msg}`,
},
],
}),
}
).then(
async (res) =>
(await res.json()).result.response.replaceAll('"', '') as string
);
const thread = await sentMsg.startThread({ name: titleResponse.substring(0, 100) });
const dbData = new db({
messageid: message.id,
threadid: thread.id,
messages: [
{ role: 'system', content: systemMsg },
{ role: 'user', content: message.content },
{ role: 'assistant', content: msg.replace(/^\n{2}/, '') },
],
});
await dbData.save();
} else {
const data = JSON.parse(ev.data);
msg = msg + data.response;
}
},
signal: ctrl.signal,
}
);
} catch (e) {
await message.reply({ content: 'Algo ha ido mal :(' }).catch(() => {});
console.log(e);
}
},
});

View File

@@ -0,0 +1,72 @@
import { discordEvent } from '@sern/handler';
import { PublicThreadChannel } from 'discord.js';
import database from '../schemas/chatgpt.js';
import { fetchEventSource } from '@ai-zen/node-fetch-event-source';
export default discordEvent({
name: 'messageCreate',
async execute(message) {
// @ts-ignore idk man
const thread = message.channel as PublicThreadChannel<false>;
if (thread.parentId !== process.env.CHATGPT_CHANNEL) return;
if (message.author.bot) return;
if (message.content.includes('v!ig')) return;
try {
await thread.sendTyping()
const newObj = [{ role: 'user', content: message.content }]
const db = await database.findOne({ threadid: thread.id }).exec()
const messages = db!.messages.map((message) => {
const { _id, ...rest } = message.toObject();
return rest
})
const ctrl = new AbortController();
let msg = ''
let isDone = false
const sentMsg = await message.reply(':sparkles: Pensando...')
const sendInterval = setInterval(() => {
if (msg.length > 2000 || msg.length === 0) return
thread.sendTyping()
sentMsg.edit(msg)
if (isDone) {
clearTimeout(sendInterval)
ctrl.abort()
return
}
}, 1000)
fetchEventSource(`https://api.cloudflare.com/client/v4/accounts/${process.env.CF_AI_ACC}/ai/run/@hf/mistral/mistral-7b-instruct-v0.2`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CF_AI_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
stream: true,
messages: messages.concat(newObj)
}),
onmessage: async (ev) => {
if (ev.data === '[DONE]') {
ctrl.abort()
isDone = true
newObj.push({ role: 'assistant', content: msg })
await database.findOneAndUpdate({ threadid: thread.id }, {
$push: {
messages: newObj
}
}).exec()
await sentMsg.edit({ content: msg })
} else {
const data = JSON.parse(ev.data)
msg = msg + data.response
}
},
signal: ctrl.signal
})
} catch (e) {
await message.reply('Algo ha ido mal.')
console.log(e)
}
},
});

View File

@@ -0,0 +1,29 @@
import { Service, discordEvent } from '@sern/handler';
import removeURLParameter from '../util/removeUrlParameter.js';
export default discordEvent({
name: 'messageCreate',
async execute(message) {
const spotify = Service('spotify-api-client')
if (message.author.bot) return;
if (!message.content.includes('https://open.spotify.com/intl-es/track')) return;
const index = message.content.indexOf("https://open.spotify.com/intl-es/track");
let link: string = ''
if (index !== -1) {
let endIndex = message.content.indexOf(" ", index);
if (endIndex === -1) {
endIndex = message.content.length;
}
link = message.content.substring(index, endIndex);
}
const croppedUrl = removeURLParameter(link.replace('intl-es/', '').replaceAll(/([^:]\/)\/+/g, "$1"), 'si');
const song = await spotify.tracks.get(croppedUrl.split('/').pop()!);
await message.delete();
message.channel.send({
content: `Oye <@${message.author.id}>, tu canción \`${song!.name}\` de \`${song!.artists.map(a => a.name).join(', ')}\` es muy buena, pero si quitas \`intl-es\` es mejor.\n${croppedUrl}`,
})
}
})

View File

@@ -6,4 +6,4 @@ export default eventModule({
execute(err) {
console.log(err);
}
})
})

View File

@@ -1,20 +1,19 @@
import { EmbedBuilder, GuildMember, TextChannel } from "discord.js";
import { EventType, eventModule } from "@sern/handler";
import { EventType, discordEvent, eventModule } from "@sern/handler";
export default eventModule({
type: EventType.Discord,
export default discordEvent({
name: 'guildMemberAdd',
execute(member: GuildMember) {
execute(member) {
if (member.guild.id !== process.env.GUILDID) return;
const newMemberEmbed = new EmbedBuilder()
const newMemberEmbed = new EmbedBuilder()
.setColor("Random")
.setTitle("Nuevo miembro!")
.setDescription(`${member.user} acaba de entrar al servidor!`)
.setThumbnail(member.user.displayAvatarURL())
.setTimestamp();
const channel = member.client.guilds.cache.get(process.env.GUILDID!)!.channels.cache.get(process.env.JOINSANDLEAVES_CHANNEL!) as TextChannel
const channel = member.client.guilds.cache.get(process.env.GUILDID!)!.channels.cache.get(process.env.JOINSANDLEAVES_CHANNEL!) as unknown as TextChannel
channel.send({embeds: [newMemberEmbed]})
}
});

View File

@@ -1,20 +1,19 @@
import { EmbedBuilder, GuildMember, TextChannel } from "discord.js";
import { EventType, eventModule } from "@sern/handler";
import { EventType, discordEvent, eventModule } from "@sern/handler";
export default eventModule({
type: EventType.Discord,
export default discordEvent({
name: 'guildMemberRemove',
execute(member: GuildMember) {
execute(member) {
if (member.guild.id !== process.env.GUILDID) return;
const leaveEmbed = new EmbedBuilder()
const leaveEmbed = new EmbedBuilder()
.setColor("Random")
.setTitle("Un miembro se ha ido :(")
.setDescription(`${member.user} acaba de salir del servidor!`)
.setThumbnail(member.user.displayAvatarURL())
.setTimestamp();
const channel = member.client.guilds.cache.get(process.env.GUILDID!)!.channels.cache.get(process.env.JOINSANDLEAVES_CHANNEL!) as TextChannel
const channel = member.client.guilds.cache.get(process.env.GUILDID!)!.channels.cache.get(process.env.JOINSANDLEAVES_CHANNEL!) as unknown as TextChannel
channel.send({embeds: [leaveEmbed]})
}
});

10
src/events/t.ts Normal file
View File

@@ -0,0 +1,10 @@
import { discordEvent } from '@sern/handler';
export default discordEvent({
name: 'messageCreate',
async execute(msg) {
if (msg.channel.id !== process.env.T_CHANNEL) return;
if (msg.content !== 'T')
return await msg.delete();
}
});

View File

@@ -1,4 +1,4 @@
import { DefaultLogging, makeDependencies, single, Singleton } from '@sern/handler';
import { makeDependencies } from '@sern/handler';
import { ActivityOptions, ActivityType } from 'discord.js';
import { Client, GatewayIntentBits } from 'discord.js';
import { Sern } from '@sern/handler';
@@ -8,10 +8,11 @@ import youtubenotifications from './util/youtubenotifications.js';
import { setIntervalAsync } from 'set-interval-async';
import birthdays from './util/birthdays.js';
import minecraftstatus from './util/minecraftstatus.js';
import axios from 'axios';
// import giveawaychecker from './util/giveawaychecker.js';
import Spotify from 'spotify-api.js';
import { Publisher } from '@sern/publisher';
import * as sernconfig from './config.js'
let devMode: boolean
export let devMode: boolean
if (process.argv[2] === '--dev') {
devMode = true
dotenv({path: '.env.dev'})
@@ -36,20 +37,21 @@ mongoose.connect(process.env.MONGODB!).then(() => {
console.log('Connected to MongoDB');
});
interface MyDependencies extends Dependencies {
'@sern/client' : Singleton<Client>;
'@sern/logger' : Singleton<DefaultLogging>
}
const spotifyClient = await Spotify.Client.create({
token: { clientID: process.env.SPOTIFY_CLIENT!, clientSecret: process.env.SPOTIFY_SECRET! },
})
await makeDependencies<MyDependencies>({
build: (root) => root.add({ '@sern/client': single(() => client) }),
await makeDependencies(({ add }) => {
add('@sern/client', client);
add('publisher', deps => new Publisher(
deps['@sern/modules'],
deps['@sern/emitter'],
deps['@sern/logger']!
));
add('spotify-api-client', spotifyClient);
});
Sern.init({
commands: 'dist/commands',
events: 'dist/events',
defaultPrefix: process.env.PREFIX,
});
Sern.init(sernconfig);
client.on('ready', async () => {
console.log('Logged on!');
@@ -58,14 +60,14 @@ client.on('ready', async () => {
const statuses = [
{ name: 'Minecraft', type: ActivityType.Playing },
{ name: 'cómo escribe Javi', type: ActivityType.Watching },
{ name: 'quinto libro when', type: ActivityType.Watching },
{ name: 'sexto libro when', type: ActivityType.Watching },
{ name: 'a Hermes', type: ActivityType.Watching },
{ name: 'tus comandos', type: ActivityType.Listening },
{ name: 'tu voz', type: ActivityType.Listening },
{ name: 'ahora v1.0!', type: ActivityType.Playing },
] as ActivityOptions[];
const randomStatus = statuses[Math.floor(Math.random() * statuses.length)];
client.user.setActivity(randomStatus);
client.user!.setActivity(randomStatus);
}, 10000);
if (!devMode) {
@@ -85,6 +87,6 @@ client.on('ready', async () => {
}
});
export const scamLinks = await axios.get('https://api.hyperphish.com/gimme-domains').then(res => res.data as Array<string>)
// export const scamLinks = await axios.get('https://api.hyperphish.com/gimme-domains').then(res => res.data as Array<string>)
client.login(process.env.TOKEN);
client.login();

80
src/plugins/disable.ts Normal file
View File

@@ -0,0 +1,80 @@
//@ts-nocheck
/**
* @plugin
* Disables a command entirely, for whatever reasons you may need.
*
* @author @jacoobes [<@182326315813306368>]
* @author @Peter-MJ-Parker [<@371759410009341952>]
* @version 2.1.0
* @example
* ```ts
* import { disable } from "../plugins/disable";
* import { commandModule } from "@sern/handler";
* export default commandModule({
* plugins: [ disable() ],
* execute: (ctx) => {
* //your code here
* }
* })
* ```
* @end
*/
import { CommandType, CommandControlPlugin, controller } from "@sern/handler";
import { InteractionReplyOptions, MessageReplyOptions } from "discord.js";
export function disable(
onFail?:
| string
| Omit<InteractionReplyOptions, "fetchReply">
| MessageReplyOptions
) {
return CommandControlPlugin<CommandType.Both>(async (ctx, [args]) => {
if (onFail !== undefined) {
switch (args) {
case "text":
try {
//reply to text command
const msg = await ctx.reply(onFail);
setTimeout(() => {
//deletes the bots reply to the user
msg.delete();
//deletes the original authors message (text command).
ctx.message.delete();
//waits 5 seconds before deleting messages
}, 5000);
} catch (error) {
console.log(
"Could not delete disabled response due to: " +
error
);
}
break;
case "slash":
//response to say the command is disabled with users response.
let reply = await ctx.reply(onFail);
try {
setTimeout(async () => {
await reply.delete();
}, 5000);
} catch (error) {
console.log(
"Could not delete disabled response due to it being ephemeral."
);
}
break;
default:
break;
}
}
//this function tells the bot to reply to an interaction so it doesn't seem like it fails (in case there is no onFail message).
if (onFail === undefined && args === "slash") {
onFail = "This command is disabled.";
await ctx.reply({ content: onFail, ephemeral: true });
}
//stop the command from running
return controller.stop();
});
}

View File

@@ -2,3 +2,4 @@ export * from './publish.js'
export * from './ownerOnly.js'
export * from './srIzanOnly.js'
export * from './acceptingBirthday.js'
export * from './disable.js'

View File

@@ -1,7 +1,7 @@
import mongoose from 'mongoose'
const messageSchema = new mongoose.Schema({
role: { type: String, required: true },
content: { type: String, required: true },
content: { type: String, reqmuired: true },
});
const schema = new mongoose.Schema({
messageid: { type: String, required: true },

View File

@@ -0,0 +1,20 @@
export async function decompressRecipes(file: ICFile) {
const recipes = file.recipes;
const items = file.items;
return recipes.map((recipe) => ({
first: items[recipe[0]]!,
second: items[recipe[1]]!,
result: items[recipe[2]]!,
})) as Recipe[];
}
export interface Recipe {
first: string;
second: string;
result: string;
}
export interface ICFile {
recipes: number[][];
items: string[];
}

View File

@@ -0,0 +1,203 @@
// code based off of: https://github.com/vantezzen/infinite-craft-solver/blob/main/apps/web/lib/Finder.ts
// thanks to @vantezzen for such a cool algo
import fs from 'fs/promises'
import { ICFile, decompressRecipes } from './decompress.js';
export interface Recipe {
first: string;
second: string;
result: string;
}
export enum FinderPhase {
Search,
Backtrack,
}
export interface FinderProgess {
current: number;
phase: FinderPhase;
}
export default class Finder {
private DEFAULT_ITEMS = ["Water", "Fire", "Wind", "Earth"];
private recipes: Recipe[] = []; // Array to store all recipes
private recipeMap: Map<string, Recipe[]> = new Map(); // Map to store recipes for each item
private recipesLoaded: boolean = false; // Flag to check if recipes are loaded
public items = new Set<string>(this.DEFAULT_ITEMS);
constructor(
private onProgress: (progress: FinderProgess) => void = () => {}
) {}
private async loadRecipes() {
const file = JSON.parse(await fs.readFile("./assets/icRecipes.json", "utf-8")) as ICFile
this.recipes = await decompressRecipes(file);
this.items = new Set<string>(file.items);
for (const recipe of this.recipes) {
if (!this.recipeMap.has(recipe.result)) {
this.recipeMap.set(recipe.result, []);
}
this.recipeMap.get(recipe.result)!.push(recipe);
}
this.recipesLoaded = true;
}
async findItem(targetItem: string) {
if (this.DEFAULT_ITEMS.includes(targetItem)) {
return [];
}
if (!this.recipesLoaded) {
await this.loadRecipes();
}
if (!this.items.has(targetItem)) {
throw new Error("Item not found");
}
const path = await this.findShortestPath(targetItem);
if (!path) {
throw new Error("Item cannot be crafted");
}
return path;
};
private async findShortestPath(targetItem: string): Promise<Recipe[] | null> {
const itemQueue: {
item: string;
recipe: Recipe | null;
}[] = this.DEFAULT_ITEMS.map((item) => ({
item,
recipe: null,
}));
const recipesUsed = new Set<Recipe>();
const discoveredItems = new Set<string>(this.DEFAULT_ITEMS);
let itemsProcessed = 0;
const updateInterval = setInterval(() => {
this.onProgress({
current: itemsProcessed,
phase: FinderPhase.Search,
});
}, 100);
let cleanedRecipes = this.recipes.filter((recipe) => {
const isCircularRecipe =
recipe.first === recipe.result || recipe.second === recipe.result;
const containsNothing =
recipe.first === "Nothing" ||
recipe.second === "Nothing" ||
recipe.result === "Nothing";
return !isCircularRecipe && !containsNothing;
});
while (itemQueue.length > 0) {
itemsProcessed++;
const { item, recipe } = itemQueue.shift()!;
if (item === targetItem) {
// console.log("Found path", recipesUsed.size);
clearInterval(updateInterval);
return await this.backtrackPath(targetItem, recipe, [...recipesUsed]);
}
cleanedRecipes.forEach((recipe) => {
const hasDiscoveredItems =
discoveredItems.has(recipe.first) &&
discoveredItems.has(recipe.second);
if (!hasDiscoveredItems) return;
const resultAlreadyDiscovered = discoveredItems.has(recipe.result);
if (resultAlreadyDiscovered) return;
discoveredItems.add(recipe.result);
recipesUsed.add(recipe);
itemQueue.push({
item: recipe.result,
recipe,
});
});
cleanedRecipes = cleanedRecipes.filter(
(r) => !recipesUsed.has(r) && !discoveredItems.has(r.result)
);
await new Promise((resolve) => setTimeout(resolve, 0));
}
clearInterval(updateInterval);
return null;
}
private async backtrackPath(
targetItem: string,
recipe: Recipe | null,
recipesUsed: Recipe[]
): Promise<Recipe[]> {
if (!recipe) {
return [];
}
// "recipesUsed" contains recipes that are not part of the shortest path to the target item
// We want to backtrack from the target item to the source items (this.DEFAULT_ITEMS) to find the shortest path
const recipes: Recipe[] = [recipe];
const itemQueue = [recipe?.first, recipe?.second];
const discoveredItems = new Set<string>([targetItem]);
const updateInfo = () => {
this.onProgress({
current: recipes.length,
phase: FinderPhase.Backtrack,
});
};
const updateInterval = setInterval(() => {
updateInfo();
}, 50);
updateInfo();
await new Promise((resolve) => setTimeout(resolve, 0));
while (itemQueue.length > 0) {
const item = itemQueue.shift()!;
if (this.DEFAULT_ITEMS.includes(item)) {
continue;
}
const recipeUsedForItem = recipesUsed.find(
(recipe) => recipe.result === item
);
if (!recipeUsedForItem) {
continue;
}
recipes.push(recipeUsedForItem);
discoveredItems.add(recipeUsedForItem.first);
discoveredItems.add(recipeUsedForItem.second);
itemQueue.push(recipeUsedForItem.first, recipeUsedForItem.second);
await new Promise((resolve) => setTimeout(resolve, 0));
}
clearInterval(updateInterval);
return this.removeDuplicates(recipes.reverse());
}
private removeDuplicates(path: Recipe[]): Recipe[] {
const seen = new Set<string>();
return path.filter((recipe) => {
const key = `${recipe.first}-${recipe.second}-${recipe.result}`;
if (seen.has(key)) {
return false;
}
seen.add(key);
return true;
});
}
}

Some files were not shown because too many files have changed in this diff Show More