feat: almost blank slate

This commit is contained in:
2025-06-27 17:09:04 +02:00
parent de31d41743
commit 459c6f9892
104 changed files with 71 additions and 12030 deletions

View File

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

View File

@@ -1,8 +0,0 @@
# THIS IS OUTDATED
TOKEN=token
PREFIX=v!
MONGODB=mongodb://
HYPIXEL_API=API_KEY
YOURLS_KEY=YOURLS_SIGNATURE
YOUTUBE_API=

8
.idea/.gitignore generated vendored
View File

@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@@ -1,65 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_QUOTE_STYLE" value="Single" />
<option name="HTML_ENFORCE_QUOTES" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="USE_TAB_CHARACTER" value="true" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

7
.idea/discord.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

View File

@@ -1,10 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

6
.idea/misc.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/vinci.iml" filepath="$PROJECT_DIR$/.idea/vinci.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

9
.idea/vinci.iml generated
View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

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

@@ -1,124 +0,0 @@
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

View File

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

Binary file not shown.

View File

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

71
TODO.md Normal file
View File

@@ -0,0 +1,71 @@
# Slash Commands
## Fun Commands
- [ ] /animal - Animal pictures with voting system (cat, dog, capybara, fox, raccoon)
- [ ] /chiste - Joke command fetching from API
- [ ] /rps - Rock Paper Scissors game
- [ ] /tictactoe - Tic-tac-toe game (currently disabled)
- [ ] /8ball - Magic 8-ball responses
- [ ] /megamind - Megamind meme generator with canvas
- [ ] /makesweet - Heart locket image generator
- [ ] /gogoanime - Anime search and episode links
- [ ] /a - Custom command with user autocomplete
- [ ] /cursivify - Context menu command to italicize text
## Miscellaneous Commands
- [ ] /rolemenu - Role selection menu (owner only)
- [ ] /creditos - Bot credits and acknowledgments
- [ ] /infinitecraft - InfiniteCraft recipe solver
- [ ] /letra - Song lyrics search via Genius API
- [ ] /google - Google search results
- [ ] /wikipedia - Wikipedia search (Spanish/English)
- [ ] /faq - FAQ system with Minecraft questions
- [ ] /afk - AFK status management
- [ ] /stats - Bot statistics (currently disabled)
- [ ] /acortar - URL shortener (currently disabled)
- [ ] /askjavi - Question system (disabled)
## Minecraft Commands
- [ ] /ip - Minecraft server IP information
# Button Handlers
- [ ] suggestions-yes - Upvote button handler
- [ ] suggestions-no - Downvote button handler
- [ ] suggestions-yes-who - Show upvoters
- [ ] suggestions-no-who - Show downvoters
# Context Menu Commands
- [ ] bonzify - Text-to-speech with Bonzi Buddy voice
- [ ] cursivify - Italicize message text
# Utility Systems to Rewrite
- [ ] Resolver - Role/user resolution utility
- [ ] InfiniteCraft Finder - Recipe pathfinding algorithm
- [ ] Wikipedia utility - Wikipedia search helper
# Plugin Systems
- [ ] ownerOnly - Owner-only command restriction
- [ ] srIzanOnly - Specific user restriction
- [ ] disable - Command disabling plugin
# Event Handlers
- [ ] afknotify - AFK notification system
# Background Services
- [ ] YouTube notifications system
- [ ] Birthday checker service
- [ ] Minecraft server status checker
- [ ] Activity status rotation
# Database Schemas to Rewrite
- [ ] Suggestions voting system
- [ ] AFK status tracking
- [ ] Question/answer system (askjavi)
# Command Features to Preserve
- [ ] Autocomplete functionality for various commands
- [ ] Interactive components (buttons, select menus)
- [ ] File attachments and image processing
- [ ] API integrations (TheCatAPI, TheDogAPI, Genius, etc.)
- [ ] Canvas-based image generation
- [ ] Modal forms and user input handling

12
dependencies.d.ts vendored
View File

@@ -1,12 +0,0 @@
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>
}
}
export {}

View File

@@ -1,76 +0,0 @@
{
"name": "vinci",
"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": "sern build && node ./dist/index.js --dev",
"prod": "tsc-watch -p \"./tsconfig.json\" --onSuccess \"node ./dist/index.js\"",
"compile": "tsc --build",
"build": "sern build",
"web": "node webserver.js",
"watch": "tsc --watch",
"start": "nodemon dist/index.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/SrIzan10/vinci.git"
},
"imports": {
"#plugins": [
"./dist/plugins/index.js"
]
},
"keywords": [
"discord-bot"
],
"type": "module",
"author": "Sr Izan",
"license": "MIT",
"bugs": {
"url": "https://github.com/SrIzan10/vinci/issues"
},
"homepage": "https://github.com/SrIzan10/vinci#readme",
"dependencies": {
"@ai-zen/node-fetch-event-source": "^2.1.4",
"@consumet/extensions": "^1.7.0",
"@discordjs/opus": "^0.9.0",
"@discordjs/voice": "^0.15.0",
"@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.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.8.0",
"got": "^12.5.3",
"libsodium-wrappers": "^0.7.10",
"mongoose": "^6.11.3",
"node-fetch": "^3.3.1",
"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.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.6.2"
},
"packageManager": "yarn@4.1.1"
}

View File

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

View File

@@ -1,30 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { ApplicationCommandOptionType } from "discord.js";
export default commandModule({
name: '8ball',
type: CommandType.Slash,
plugins: [],
description: 'Preguntale a la 8-ball cosas.',
//alias : [],
options: [{
name: "pregunta",
description: "Escribe lo que le quieres preguntar.",
type: ApplicationCommandOptionType.String,
required: true
}],
execute: async (ctx) => {
// yes, the question argument is never used. There is no reason to use it.
var eightballwords = [
'Probablemente',
'Sí',
'No',
'Dudable',
'Como lo veo, todo indica a que sí',
'A lo mejor',
'No cuentes con ello',
'Buena suerte'
]
await ctx.reply({content: `La bola tiene respuesta: ${eightballwords[Math.floor(Math.random() * eightballwords.length)]}.`, ephemeral: true})
},
});

View File

@@ -1,93 +0,0 @@
import { commandModule, CommandType } from '@sern/handler';
import {
ApplicationCommandOptionType,
AttachmentBuilder,
EmbedBuilder,
} from 'discord.js';
const choices = [
'XaviXE',
'Paula',
'William',
'Espejito2500',
'Wheelook',
'MarioCabrera',
'Paticama',
'Vinci',
'SrIzan',
'ItsAdrian',
'ByHGT',
'Irene',
'Boniato64',
'Tormentarosa',
'H',
'SpRaY',
];
export default commandModule({
name: 'a',
type: CommandType.Slash,
plugins: [],
description: 'A',
options: [
{
name: 'usuario',
description: 'Usuario que debería aparecer',
type: ApplicationCommandOptionType.String,
autocomplete: true,
command: {
onEvent: [],
async execute(ctx) {
const focusedValue = ctx.options.getFocused();
const filtered = choices.filter((choice) =>
choice.startsWith(focusedValue)
);
await ctx.respond(
filtered.map((choice) => ({ name: choice, value: choice }))
);
},
}
},
],
execute: async (ctx) => {
const option = ctx.interaction.options.getString('usuario');
if (!option) {
const imagesArray = [
'./images/a/XaviXE.png',
'./images/a/Paula.png',
'./images/a/William.png',
'./images/a/Espejito2500.png',
'./images/a/Wheelook.png',
'./images/a/MarioCabrera.png',
'./images/a/Paticama.png',
'./images/a/Vinci.png',
'./images/a/SrIzan.png',
'./images/a/ItsAdrian.png',
'./images/a/ByHGT.png',
'./images/a/Irene.png',
'./images/a/Boniato64.png',
'./images/a/Tormentarosa.png',
'./images/a/H.png',
'./images/a/SpRaY.png',
];
const images =
imagesArray[Math.floor(Math.random() * imagesArray.length)];
await ctx.reply({ content: 'A', files: [images] });
} else {
if (choices.indexOf(option) > -1) {
const attachmentbuilder = new AttachmentBuilder(
`./images/a/${option}.png`
);
await ctx.reply({ content: 'A', files: [attachmentbuilder] });
} else {
const embed = new EmbedBuilder()
.setTitle('A no encontrado!')
.setDescription(
`Qué raro, no se ha encontrado ese /a...\nPorqué no pruebas a poner uno del autocompletado?`
)
.setColor('Red');
await ctx.reply({ embeds: [embed], ephemeral: true });
}
}
},
});

View File

@@ -1,183 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import axios from "axios";
import { ActionRowBuilder, ApplicationCommandOptionType, ButtonBuilder, ButtonStyle, ComponentType, EmbedBuilder } from "discord.js";
export default commandModule({
name: 'animal',
type: CommandType.Slash,
plugins: [],
description: 'Enseña un animal',
//alias : [],
options: [
{
name: 'gato',
description: 'Enseña un gato',
type: ApplicationCommandOptionType.Subcommand
},
{
name: 'capybara',
description: 'Enseña un capybara',
type: ApplicationCommandOptionType.Subcommand
},
{
name: 'zorro',
description: 'Enseña un zorro',
type: ApplicationCommandOptionType.Subcommand
},
{
name: 'perro',
description: 'what the dog doin',
type: ApplicationCommandOptionType.Subcommand
},
{
name: 'mapache',
description: 'Enseña un mapache',
type: ApplicationCommandOptionType.Subcommand
}
],
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()
.setAuthor({name: ctx.user.username, iconURL: ctx.user.displayAvatarURL()})
.setColor("Random")
.setImage(request[0].url)
.setFooter({text: `ID: ${request[0].id}`})
.setTitle('Gato')
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId("cat-upvote")
.setEmoji("⬆️")
.setStyle(ButtonStyle.Success),
new ButtonBuilder()
.setCustomId("cat-downvote")
.setEmoji("⬇️")
.setStyle(ButtonStyle.Danger),
)
const rowdisabled = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId("cat-upvote")
.setEmoji("⬆️")
.setStyle(ButtonStyle.Success)
.setDisabled(true),
new ButtonBuilder()
.setCustomId("cat-downvote")
.setEmoji("⬇️")
.setStyle(ButtonStyle.Danger)
.setDisabled(true),
)
const message = await ctx.reply({embeds: [embed], components: [row]})
const collector = message.createMessageComponentCollector({time: 30000, componentType: ComponentType.Button})
collector.on('collect', async (i) => {
await i.deferReply({ephemeral: true})
if (i.customId === "cat-upvote") {
await axios.post(`https://api.thecatapi.com/v1/votes?api_key=${process.env.CATAPI}`, {
"image_id": request[0].id,
"sub_id": i.user.id,
"value": 1
})
i.editReply({content: "Has votado positivamente al gato con ID " + "`" + request[0].id + "`"})
}
if (i.customId === "cat-downvote") {
await axios.post(`https://api.thecatapi.com/v1/votes?api_key=${process.env.CATAPI}`, {
"image_id": request[0].id,
"sub_id": i.user.id,
"value": -1
})
i.editReply({content: `Has votado negativamente al gato con ID \`${request[0].id}\``})
}
})
collector.on('end', async (i) => {
await message.edit({components: [rowdisabled]})
})
}
case 'capybara': {
const embed = new EmbedBuilder()
.setAuthor({name: ctx.user.username, iconURL: ctx.user.displayAvatarURL()})
.setTitle('Capybara')
.setColor('Random')
.setImage('https://api.capy.lol/v1/capybara');
await ctx.interaction.reply({embeds: [embed]})
}
case 'zorro': {
const request = await axios('https://randomfox.ca/floof/').then(res => res.data)
const embed = new EmbedBuilder()
.setAuthor({name: ctx.user.username, iconURL: ctx.user.displayAvatarURL()})
.setTitle('Zorro')
.setColor('Random')
.setImage(request.image)
await ctx.interaction.reply({embeds: [embed]})
}
case 'perro': {
const request = await axios.get(`https://api.thedogapi.com/v1/images/search?api_key=${process.env.DOGAPI}`).then(res => res.data)
const embed = new EmbedBuilder()
.setAuthor({name: ctx.user.username, iconURL: ctx.user.displayAvatarURL()})
.setColor("Random")
.setImage(request[0].url)
.setFooter({text: `ID: ${request[0].id}`})
.setTitle('Perro')
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId("dog-upvote")
.setEmoji("⬆️")
.setStyle(ButtonStyle.Success),
new ButtonBuilder()
.setCustomId("dog-downvote")
.setEmoji("⬇️")
.setStyle(ButtonStyle.Danger),
)
const rowdisabled = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId("dog-upvote")
.setEmoji("⬆️")
.setStyle(ButtonStyle.Success)
.setDisabled(true),
new ButtonBuilder()
.setCustomId("dog-downvote")
.setEmoji("⬇️")
.setStyle(ButtonStyle.Danger)
.setDisabled(true),
)
const message = await ctx.reply({embeds: [embed], components: [row]})
const collector = message.createMessageComponentCollector({time: 30000, componentType: ComponentType.Button})
collector.on('collect', async (i) => {
await i.deferReply({ephemeral: true})
if (i.customId === "dog-upvote") {
await axios.post(`https://api.thedogapi.com/v1/votes?api_key=${process.env.DOGAPI}`, {
"image_id": request[0].id,
"sub_id": i.user.id,
"value": 1
})
i.editReply({content: "Has votado positivamente al gato con ID " + "`" + request[0].id + "`"})
}
if (i.customId === "dog-downvote") {
await axios.post(`https://api.thedogapi.com/v1/votes?api_key=${process.env.DOGAPI}`, {
"image_id": request[0].id,
"sub_id": i.user.id,
"value": -1
})
i.editReply({content: "Has votado negativamente al gato con ID " + "`" + request[0].id + "`"})
}
})
collector.on('end', async () => {
await message.edit({components: [rowdisabled]})
})
}
case 'mapache': {
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')
.setDescription(`Fun fact: ${request.fact}`)
.setColor('Random')
.setImage(request.image)
await ctx.interaction.reply({embeds: [embed]})
}
}
},
});

View File

@@ -1,16 +0,0 @@
import { commandModule, CommandType } from '@sern/handler';
export default commandModule({
name: 'cursivify',
type: CommandType.CtxMsg,
plugins: [],
execute: async (ctx) => {
await ctx.deferReply()
const trimmedstring = ctx.targetMessage.content.replaceAll('*', '');
if (trimmedstring.length === 0) {
await ctx.editReply('No hay nada que cursivificar!');
} else {
await ctx.editReply(`*${trimmedstring}*`);
}
},
});

View File

@@ -1,168 +0,0 @@
import { commandModule, CommandType } from '@sern/handler';
import { publish } from '#plugins';
import { ANIME } from '@consumet/extensions';
import {
ActionRowBuilder,
ApplicationCommandOptionType,
ButtonBuilder,
ButtonStyle,
ComponentType,
EmbedBuilder,
} from 'discord.js';
/*
import { ownerOnly } from "#plugins"
*/
export default commandModule({
type: CommandType.Slash,
plugins: [],
description: 'busca cosas en gogoanime',
//alias : [],
options: [
{
name: 'buscar',
description: 'Busca un anime',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'palabra-clave',
description: 'La palabra clave',
type: ApplicationCommandOptionType.String,
required: true,
},
],
},
{
name: 'capitulo',
description: 'Mira los links de directo de cualquier capítulo (con su ID)',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'id-serie',
description: 'El ID de la serie (búscalo primero)',
type: ApplicationCommandOptionType.String,
required: true
},
{
name: 'id-capitulo',
description: 'El ID del capítulo (usa el autocompletado)',
type: ApplicationCommandOptionType.String,
required: true,
autocomplete: true,
command: {
onEvent: [],
execute: async (autocomplete) => {
try {
const focusedOption = autocomplete.options.getFocused();
const gogoanime = new ANIME.Gogoanime();
const serieOption = autocomplete.options.getString('id-serie', true)
const fetch = await gogoanime.fetchAnimeInfo(serieOption)
let choices = fetch.episodes!.filter((choice) => choice.number.toString().startsWith(focusedOption))
choices = choices.slice(0, 25)
await autocomplete.respond(
choices.map((choice) => ({
name: choice.number.toString(),
value: choice.id.toString(),
}))
)
} catch {
await autocomplete.respond([{name: 'Algo malo ha ocurrido! Asegúrate que hayas puesto el ID correctamente', value: 'error'}])
}
}
}
}
],
},
{
name: 'info',
description: 'INGLÉS: Consigue información sobre alguna serie con su ID.',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'id',
description: 'El nombre de la serie',
type: ApplicationCommandOptionType.String,
required: true
}
]
}
],
execute: async (ctx, options) => {
const gogoanime = new ANIME.Gogoanime();
const doubleslashregex = new RegExp('(?<!:)\/\/+')
switch (ctx.options.getSubcommand()) {
case 'buscar': {
await ctx.interaction.deferReply()
const option = ctx.options.getString('palabra-clave', true);
const search = await gogoanime.search(option);
const editedarray = await Promise.all(
search.results
.map((results) => {
return `[${results.title}](<${results.url!.replace(doubleslashregex, '/')}>)`;
})
.slice(0, 5)
);
const editedarrayids = await Promise.all(
search.results
.map((results) => {
return `[${results.id}](<${results.url!.replace(doubleslashregex, '/')}>)`;
})
.slice(0, 5)
);
const button = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('gogoanime-search-toid')
.setLabel('Cambiar a ID')
.setStyle(ButtonStyle.Secondary)
);
if (editedarray.length === 0) return await ctx.interaction.editReply({content: 'No se ha encontrado nada con ese resultado de búsqueda, prueba a ser más general o concreto idk'})
const message = await ctx.interaction.editReply({
content: `Resultados de la búsqueda \`${option}\`:\n${editedarray.join('\n')}`,
components: [button],
});
const collector = message.createMessageComponentCollector({max: 1, componentType: ComponentType.Button, time: 30000})
collector.on('collect', async (i) => {
if (i.customId !== 'gogoanime-search-toid') return;
await ctx.interaction.editReply({
content: `Resultados de la búsqueda \`${option}\` (modo ID):\n${editedarrayids.join('\n')}`,
components: []
})
await i.deferUpdate()
})
} break;
case 'capitulo': {
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, '/')}>)`))
await ctx.reply({content: `Todos los servidores de \`${selepisode}\` (Vinci no se hace cargo de los enlaces):\n${arrayed.join('\n')}`})
} catch {
await ctx.reply({content: 'Ha ocurrido un error! Asegúrate que hayas seleccionado bien un capítulo.'})
}
} break;
case 'info': {
try {
const option = ctx.options.getString('id', true)
const info = await gogoanime.fetchAnimeInfo(option)
const embed = new EmbedBuilder()
.setColor('Random')
.setTitle(`${info.title}`)
.setURL(info.url!.replace(doubleslashregex, '/'))
.setThumbnail(info.image!)
.setFields(
{name: 'Géneros', value: `${info.genres!.join(', ')}`},
{name: 'Fecha de salida', value: `${info.releaseDate!}`, inline: true},
{name: 'Capítulos totales', value: `${info.totalEpisodes!}`, inline: true},
{name: '\u200B', value: '\u200B', inline: true},
{name: 'Tipo', value: `${info.type!}`, inline: true},
)
await ctx.reply({embeds: [embed]})
} catch {
await ctx.reply({content: 'Algo malo ha ocurrido, asegúrate que hayas escrito el ID correctamente\nTip: Usa el comando de buscar y conviértelos a ID.'})
}
} break;
}
},
});

View File

@@ -1,15 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import axios from "axios";
export default commandModule({
name: 'chiste',
type: CommandType.Slash,
plugins: [],
description: 'Enseña un chiste en inglés.',
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);
ctx.reply({content: `${jokeJSON.joke || jokeJSON.setup}\n${jokeJSON.delivery || ""}`})
}})

View File

@@ -1,164 +0,0 @@
import { commandModule, CommandType } from '@sern/handler';
import { publish } from '#plugins';
import FormData from 'form-data';
import {
ActionRowBuilder,
ApplicationCommandOptionType,
AttachmentBuilder,
ButtonBuilder,
ButtonStyle,
} from 'discord.js';
import axios from 'axios';
import https from 'node:https'
/*
import { ownerOnly } from "#plugins"
*/
export default commandModule({
type: CommandType.Slash,
plugins: [],
//alias : [],
description: 'no one will read this (i hope)',
options: [
{
name: 'heartlocket',
description: 'El corazón con una imagen que todos conocemos',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'imagen',
description: 'Imagen (jpg o png)',
type: ApplicationCommandOptionType.Attachment,
required: true,
},
{
name: 'texto',
description: 'El texto que poner',
type: ApplicationCommandOptionType.String,
},
],
},
],
execute: async (ctx) => {
await ctx.interaction.deferReply();
switch (ctx.options.getSubcommand()) {
case 'heartlocket':
{
try {
// get all options
const text = ctx.options.getString('texto', true);
const image = ctx.options.getAttachment('imagen', true);
// check file extension of attachment
if (
!image.contentType!.includes('image/png') &&
!image.contentType!.includes('image/jpeg')
)
return await ctx.interaction.editReply({
content:
'Tienes que usar una imagen con extensión `.png` o `.jpg`!',
});
// save in a const the content type
let fileExtension: string;
if (image.contentType!.includes('image/png')) {
fileExtension = 'png';
} else if (image.contentType!.includes('image/jpeg')) {
fileExtension = 'jpg';
} else {
fileExtension =
'this shouldnt be seen, but typescript is sometimes an idiot and it needs an else so it doesnt cry';
}
// upload to tmpfiles so it can be then uploaded to
const formDataTemp = new FormData();
const imageBinaryTemp = await axios
.get(image.url, { responseType: 'arraybuffer' })
.then((res) => res.data);
const bufferTemp = Buffer.from(imageBinaryTemp, 'binary');
formDataTemp.append('file', bufferTemp, `image.${fileExtension}`);
const tempupload = await axios
.post(
`https://tmpfiles.org/api/v1/upload`,
formDataTemp,
)
.then((res) => res.data);
// compress the image
const compress = await axios.get(`https://api.resmush.it/ws.php?img=${(tempupload.data.url as string).replace('https://tmpfiles.org/', 'https://tmpfiles.org/dl/')}&qlty=80`, {
httpsAgent: new https.Agent({ rejectUnauthorized: false })
}).then(res => res.data)
// convert image to a binary so it can be sent to the MakeSweet API.
const formData = new FormData();
const imageBinary = await axios
.get((compress.dest as string).replace(/\\\//g, '/'), { responseType: 'arraybuffer' })
.then((res) => res.data);
const buffer = Buffer.from(imageBinary, 'binary');
formData.append('images[]', buffer, `image.${fileExtension}`);
// make the request to the actual API, but first check if there's text.
let request: any;
if (text) {
request = await axios
.post(
`https://api.makesweet.com/make/heart-locket?text=${text}`,
formData,
{
headers: {
Authorization: process.env.MAKESWEET!,
},
responseType: 'arraybuffer',
httpsAgent: new https.Agent({ rejectUnauthorized: false })
}
)
.then((res) => res.data);
} else {
request = await axios
.post(`https://api.makesweet.com/make/heart-locket`, formData, {
headers: {
Authorization: process.env.MAKESWEET!,
},
responseType: 'arraybuffer',
httpsAgent: new https.Agent({ rejectUnauthorized: false })
})
.then((res) => res.data);
}
// make an attachment with the data that the MakeSweet API returned
const attachment = new AttachmentBuilder(request, {
name: 'makesweet.gif',
});
// finally, send the message
const message = await ctx.interaction.editReply({
content: 'Tu GIF está listo! 🎉',
files: [attachment],
});
// make an image link button
const button = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setLabel('Enlace al GIF')
.setEmoji('📲')
.setURL(`https://api.srizan.dev/misc/download?url=${message.attachments.first()!.url}&type=gif`)
.setStyle(ButtonStyle.Link)
);
await ctx.interaction.editReply({
content: 'Tu GIF está listo! 🎉',
files: [attachment],
components: [button]
});
} catch (e) {
await ctx.interaction.editReply({
content: `ERROR: He intentado comprimir la imagen, pero no ha sido suficiente. Intenta usar una imagen menos pesada (1mb o menos)\nSi no es eso, probablemente ha ocurrido un error del que no tengo conocimiento.`,
});
}
}
break;
}
},
});

View File

@@ -1,59 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { ownerOnly } from "#plugins";
import Canvas from '@napi-rs/canvas';
import { ApplicationCommandOptionType, AttachmentBuilder } from 'discord.js';
/*
import { ownerOnly } from "#plugins"
*/
export default commandModule({
type: CommandType.Slash,
plugins: [],
// , '928018226330337280'
description: 'Añade a una imagen de megamind "No ...?"',
//alias : [],
options: [
{
name: 'texto',
description: 'El texto SIN "No" ni "?".',
type: ApplicationCommandOptionType.String,
required: true
}
],
execute: async (ctx) => {
const option = ctx.options.getString('texto', true)
await ctx.reply({content: 'Cargando...'})
const before = performance.now()
const canvas = Canvas.createCanvas(535, 540)
const context = canvas.getContext('2d')
const background = await Canvas.loadImage('./images/megamind/megamind.png')
context.drawImage(background, 0, 0, canvas.width, canvas.height)
const text = `No ${option}?`
let fontsize = 60
do {
fontsize--;
context.font = fontsize + "px Impact";
} while (context.measureText(text).width > canvas.width)
context.fillStyle = 'white'
context.textAlign = 'center'
context.textBaseline = 'middle'
context.fillText(text, canvas.width / 2, canvas.height - 510)
const encode = await canvas.encode('png')
const after = performance.now()
const attachment = new AttachmentBuilder(encode, { name: 'megamind.png' });
await ctx.interaction.editReply({
content: `Aquí está tu megamind:\nLa generación de imagen ha tardado \`${(after - before).toFixed(2)}ms\`.`,
files: [attachment]
})
},
});

View File

@@ -1,113 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { ActionRowBuilder, ApplicationCommandOptionType, ButtonBuilder, ButtonStyle, ComponentType, EmbedBuilder, GuildMember } from "discord.js";
import rockpaperscissors from "rockpaperscissors-checker";
export default commandModule({
name: 'rps',
type: CommandType.Slash,
plugins: [],
description: 'Juega piedra papel tijeras con los panas',
//alias : [],
options: [
{
name: 'usuario',
description: 'El usuario con el que enfrentarse',
type: ApplicationCommandOptionType.User,
required: true
}
],
execute: async (ctx) => {
// also the code is mine, I didn't steal from anyone
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) {
return await ctx.reply({content: `no puedes seleccionar a un bot.`, ephemeral: true})
}
const waitingEmbed = new EmbedBuilder()
.setColor('Red')
.setAuthor({name: ctx.user.username, iconURL: ctx.user.displayAvatarURL()})
.setTitle(`Piedra, papel o tijera? <:PauseChamp:1030169623070519388>`)
.setDescription(`Esperando a que ambos jugadores eligan...\nJugador 1: ${ctx.user}\nJugador 2: ${option}`)
.setFooter({text: `Hay un máximo de 30 segundos para elegir.`})
const winEmbed = new EmbedBuilder()
.setColor('Green')
.setAuthor({name: ctx.user.username, iconURL: ctx.user.displayAvatarURL()})
.setFooter({text: `Gracias por jugar!`})
const tieEmbed = new EmbedBuilder()
.setColor('Yellow')
.setAuthor({name: ctx.user.username, iconURL: ctx.user.displayAvatarURL()})
.setTitle(`Ha habido un empate <:Sadge:1015764348385382451>`)
.setDescription(`Qué sadge, ha habido un empate...`)
.setFooter({text: `Volvemos a intentarlo?`})
const timeUpEmbed = new EmbedBuilder()
.setColor('Red')
.setAuthor({name: ctx.user.username, iconURL: ctx.user.displayAvatarURL()})
.setTitle(`Se acabó!`)
.setDescription(`Uno de los dos jugadores no han respondido en los 30 segundos, así que se acabó la partida!`)
.setFooter({text: `Volvemos a intentarlo?`})
const buttons = ["Piedra", "Papel", "Tijera"].map(choice => {
return new ButtonBuilder()
.setLabel(choice)
.setCustomId(`rps-${choice.toLowerCase()}`)
.setStyle(ButtonStyle.Secondary)
})
const row = new ActionRowBuilder<ButtonBuilder>();
const message = await ctx.interaction.reply({content: `${option}, te han retado a Piedra Papel o Tijera!`, embeds: [waitingEmbed], fetchReply: true, components: [row.setComponents(buttons)]})
const collector = message.createMessageComponentCollector({time: 30_000, componentType: ComponentType.Button, filter: (i) => [ctx.user.id, option.id].includes(i.user.id),})
collector.on('collect', async (i) => {
await i.deferReply({ephemeral: true})
if (i.customId === "rps-piedra") {
if (i.user.id === ctx.user.id) {
player1 = 1
await i.editReply({content: `Se ha respondido **piedra** correctamente, buena suerte!\n[Volver al mensaje](${message.url})`})
} else if (i.user.id === option.id) {
player2 = 1
await i.editReply({content: `Se ha respondido **piedra** correctamente, buena suerte!\n[Volver al mensaje](${message.url})`})
}
} else if (i.customId === "rps-papel") {
if (i.user.id === ctx.user.id) {
player1 = 2
await i.editReply({content: `Se ha respondido **papel** correctamente, buena suerte!\n[Volver al mensaje](${message.url})`})
} else if (i.user.id === option.id) {
player2 = 2
await i.editReply({content: `Se ha respondido **papel** correctamente, buena suerte!\n[Volver al mensaje](${message.url})`})
}
} else if (i.customId === "rps-tijera") {
if (i.user.id === ctx.user.id) {
player1 = 3
await i.editReply({content: `Se ha respondido **tijera** correctamente, buena suerte!\n[Volver al mensaje](${message.url})`})
} else if (i.user.id === option.id) {
player2 = 3
await i.editReply({content: `Se ha respondido **tijera** correctamente, buena suerte!\n[Volver al mensaje](${message.url})`})
}
}
if (player1 && player2) {
const checker = rockpaperscissors(player1, player2)
bothResponded = true
if (checker === "player1") {
winner = ctx.user.username
const setDescription = winEmbed.setDescription(`Tenemos resultados!\n**${winner}** ha ganado.`).setTitle(`Ha ganado ${winner}! <:Pog:1030169609178976346>`)
await message.edit({embeds: [setDescription], components: [], content: ``})
message.react('<:Pog:1030169609178976346>')
} else if (checker === "player2") {
winner = option.user.username
const setDescription = winEmbed.setDescription(`Tenemos resultados!\n**${winner}** ha ganado.`).setTitle(`Ha ganado ${winner}! <:Pog:1030169609178976346>`)
await message.edit({embeds: [setDescription], components: [], content: ``})
message.react('<:Pog:1030169609178976346>')
} else if (checker === "tie") {
await message.edit({embeds: [tieEmbed], components: [], content: ``})
}
}
})
collector.on('ignore', async (i) => {
await i.reply({content: 'No estás jugando!', ephemeral: true})
})
collector.on('end', async () => {
if (bothResponded) return;
await message.edit({embeds: [timeUpEmbed], components: [], content: ``})
})
},
});

View File

@@ -1,24 +0,0 @@
import TicTacToe from 'discord-tictactoe';
import { commandModule, CommandType } from '@sern/handler'
import { ApplicationCommandOptionType } from "discord.js";
const game = new TicTacToe({language: 'en'})
export default commandModule({
name: 'tictactoe',
type: CommandType.Slash,
plugins: [],
description: 'tres en raya',
//alias : [],
options: [
{
name: "opponent",
description: "opponent",
type: ApplicationCommandOptionType.User
}
],
execute: async (ctx, options) => {
ctx.reply({ content: 'comando desactivado temporalmente :(', ephemeral: true })
// game.handleInteraction(ctx.interaction as ChatInputCommandInteraction)
},
});

View File

@@ -1,46 +0,0 @@
import { commandModule, CommandType } from '@sern/handler';
import { AttachmentBuilder } from 'discord.js';
import { publish } from '#plugins';
import { Readable } from 'node:stream'
import { random } from '../../util/randomstring.js';
import fs from 'fs';
import { execa } from 'execa';
export default commandModule({
type: CommandType.CtxMsg,
plugins: [],
execute: async (ctx) => {
await ctx.deferReply({ fetchReply: true })
const text = ctx.targetMessage.content;
const encodedText = encodeURIComponent(text);
const url = `https://www.tetyys.com/SAPI4/SAPI4?text=${encodedText}&voice=Adult%20Male%20%232%2C%20American%20English%20(TruVoice)&pitch=140&speed=157`;
const request = await fetch(url).then(res => res.arrayBuffer())
await new Promise(resolve => setTimeout(resolve, 500));
const randomnumber = random(5)
const randomnumber_wav = `bonzi-wav-${randomnumber}.wav`
const randomnumber_mp3 = `bonzi-mp3-${randomnumber}.mp3`
fs.writeFileSync(`./src/util/bonzi_temp/${randomnumber_wav}`, new Uint8Array(request))
const command = execa('ffmpeg', [
'-i', `./src/util/bonzi_temp/${randomnumber_wav}`,
'-vn',
`./src/util/bonzi_temp/${randomnumber_mp3}`
], { shell: true })
await new Promise((resolve) => {
command.on('close', resolve)
})
const stream = new Readable();
stream._read = () => {};
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(`./src/util/bonzi_temp/${randomnumber_mp3}`)
fs.unlinkSync(`./src/util/bonzi_temp/${randomnumber_wav}`)
await ctx.editReply({ files: [attachment] })
},
});

View File

@@ -1,21 +0,0 @@
import { commandModule, CommandType } from '@sern/handler';
export default commandModule({
type: CommandType.Button,
execute: async (ctx) => {
await ctx.deferReply({ ephemeral: true })
const getUserID = await ctx.client.users.fetch((ctx.message.embeds[0].footer!.text).replace('Discord ID: ', ''))
await getUserID.send({
content: `Tu solicitud de entrada al servidor ha sido aceptada correctamente!\nYa puedes entrar al servidor con la IP \`minecraft.maraturing.com\``
}).catch(async () => {
await ctx.editReply({
content: `No se ha podido enviar un DM a ${getUserID} <:Sadge:1015764348385382451>`,
})
}).then(async () => {
await ctx.editReply({
content: `Se ha podido enviar un DM a ${getUserID} correctamente! <:Pog:1030169609178976346>`
})
})
},
});

View File

@@ -1,72 +0,0 @@
import { commandModule, CommandType } from '@sern/handler';
import axios from 'axios';
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
EmbedBuilder,
} from 'discord.js';
export default commandModule({
name: 'mcform-main',
type: CommandType.Modal,
plugins: [],
description: 'Envia el formulario para entrar al servidor.',
//alias : [],
async execute(modal) {
const value = modal.fields.getTextInputValue('mcUsernameInput');
const specialChars = /[`!@#$%^&*()+\-=\[\]{};':"\\|,.<>\/?~]/;
if (value.length > 16 || value.length < 3 || specialChars.test(value)) {
modal.reply({
content: `ERROR: El nombre de usuario no es válido.`,
ephemeral: true,
});
} else {
try {
const request = await axios
.get(`https://api.mojang.com/users/profiles/minecraft/${value}`, {
validateStatus: function (status) {
return status === 200 || status === 400;
},
})
.then((res) => res.data);
await modal.reply({
content:
'Enviado!\nSé paciente ya que el bot no es automático.',
ephemeral: true,
});
const embed = new EmbedBuilder()
.setAuthor({
name: modal.user.username,
iconURL: modal.user.displayAvatarURL(),
})
.setColor('Green')
.setTitle('Nueva solicitud de entrada al servidor!')
.setDescription(
`Su nombre de usuario en Minecraft es: \`${value}\`\nSu UUID: \`${request.id}\``
)
.setFooter({ text: `Discord ID: ${modal.user.id}` });
const button = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('mcform-dm')
.setLabel('Añadido a la whitelist!')
.setStyle(ButtonStyle.Success)
);
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:
'ERROR: No se ha podido enviar ya que eres un usuario no premium o de MC Bedrock.\nAsegúrate que has puesto bien el nombre de usuario.',
ephemeral: true,
});
}
}
},
});

View File

@@ -1,35 +0,0 @@
import { commandModule, CommandType } from "@sern/handler";
import type { APISelectMenuComponent, GuildMember } from "discord.js";
export default commandModule({
type: CommandType.StringSelect,
name: 'role-menu',
async execute(interaction) {
await interaction.deferReply({ ephemeral: true });
const roles = interaction.values;
const menuRoles: string[] = (
interaction.message.components[0].components[0]
.data as Readonly<APISelectMenuComponent>
// @ts-ignore
).options.map((o: { label: string; value: string }) => o.value);
const member = interaction.member! as unknown as GuildMember;
if (!member) return;
let content = `Los roles han sido actualizados. Te he dado estos:\n${roles
.map((r) => `<@&${r}>`)
.join("\n")}`;
if (roles.length === 0) content = "Se han actualizado los roles a ninguno o no se han seleccionado roles...";
const existing = member.roles.cache
.filter((r) => r.id !== interaction.guildId)
.map((r) => r.id)
.filter((r) => !menuRoles.includes(r));
await member.roles.set(roles.concat(existing)).catch(() => null);
await interaction.editReply(content);
},
});

View File

@@ -1,75 +0,0 @@
import { commandModule, CommandType } from '@sern/handler';
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js';
import {
ThreadAutoArchiveDuration,
} from 'discord.js';
export default commandModule({
type: CommandType.Modal,
async execute(modal) {
const value = modal.fields.getTextInputValue('sugerenciasInput');
function onlySpaces(str: string) {
return str.trim().length === 0;
}
if (onlySpaces(value) === true)
return await modal.reply({
content: 'Buen intento enviando un mensaje vacío >:D',
ephemeral: true,
});
const embed = new EmbedBuilder()
.setColor('Random')
.setTitle('Sugerencia')
.setAuthor({
name: `${modal.user.username}`,
iconURL: `${modal.user.displayAvatarURL()}`,
})
.setDescription(value);
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId('suggestions-yes')
.setEmoji('✅')
.setLabel('0')
.setStyle(ButtonStyle.Success),
new ButtonBuilder()
.setCustomId('suggestions-no')
.setEmoji('❎')
.setLabel('0')
.setStyle(ButtonStyle.Danger),
)
const row2 = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId('suggestions-yes-who')
.setEmoji('✅')
.setLabel('Quién')
.setStyle(ButtonStyle.Secondary),
new ButtonBuilder()
.setCustomId('suggestions-no-who')
.setEmoji('❎')
.setLabel('Quién')
.setStyle(ButtonStyle.Secondary),
)
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.',
});
modal.reply({
content:
'¡Enviado!\nRECUERDA QUE NO ESTÁ PERMITIDO ENVIAR MENSAJES VACÍOS.',
ephemeral: true,
});
},
});

View File

@@ -1,24 +0,0 @@
import { commandModule, CommandType } from "@sern/handler";
import db from "../../schemas/suggestions.js";
export default commandModule({
type: CommandType.Button,
async execute(interaction) {
let finalarray
await interaction.deferReply({ephemeral: true})
const findeverything = await db.find({msgid: interaction.message.id, upordown: -1})
const array = findeverything.filter(message => message.msgid)
const fetchedids = await Promise.all(array.map(async (user) => {
return interaction.client.users.fetch(user.userid)
}))
if (fetchedids.length === 0) {
finalarray = 'Nadie, de momento'
} else {
finalarray = fetchedids.join(', ')
}
await interaction.editReply({
content: `Gente que ha hecho downvote:\n${finalarray}`,
allowedMentions: {repliedUser: false}
})
}
})

View File

@@ -1,50 +0,0 @@
import { commandModule, CommandType } from "@sern/handler";
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 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)
)
if (await db.exists({msgid: interaction.message.id, userid: interaction.user.id, upordown: 1})) {
await db.findOneAndUpdate({msgid: interaction.message.id, userid: interaction.user.id, upordown: 1}, {upordown: -1}, {returnOriginal: false})
// god forbid I use any! I'm literally done with trying to solve this dude
const upvoteLabel = JSON.stringify(interaction.message!.components[0].components[0].data) as string
const downvotebuttons = new ActionRowBuilder<ButtonBuilder>().setComponents(
new ButtonBuilder(interaction.message!.components[0].components[0].data as ButtonComponentData)
.setLabel((Number(JSON.parse(upvoteLabel).label) - 1).toString()),
new ButtonBuilder()
.setCustomId('suggestions-no')
.setEmoji('❎')
.setLabel((convertToNumber + 1).toString())
.setStyle(ButtonStyle.Danger),
)
await interaction.message.edit({components: [downvotebuttons, row2]})
await interaction.deferUpdate()
} else if (await db.exists({msgid: interaction.message.id, userid: interaction.user.id, upordown: -1})) {
return await interaction.reply({content: 'Ya has hecho downvote.', ephemeral: true})
} else {
const downvotebuttons = new ActionRowBuilder<ButtonBuilder>().setComponents(
new ButtonBuilder(interaction.message!.components[0].components[0].data as ButtonComponentData),
new ButtonBuilder()
.setCustomId('suggestions-no')
.setEmoji('❎')
.setLabel((convertToNumber + 1).toString())
.setStyle(ButtonStyle.Danger),
)
const addToDB = new db({
msgid: interaction.message.id,
userid: interaction.user.id,
upordown: -1
})
await addToDB.save()
await interaction.message.edit({components: [downvotebuttons, row2]})
await interaction.deferUpdate()
}
}
})

View File

@@ -1,24 +0,0 @@
import { commandModule, CommandType } from "@sern/handler";
import db from "../../schemas/suggestions.js";
export default commandModule({
type: CommandType.Button,
async execute(interaction) {
let finalarray
await interaction.deferReply({ephemeral: true})
const findeverything = await db.find({msgid: interaction.message.id, upordown: 1})
const array = findeverything.filter(message => message.msgid)
const fetchedids = await Promise.all(array.map(async (user) => {
return interaction.client.users.fetch(user.userid)
}))
if (fetchedids.length === 0) {
finalarray = 'Nadie, de momento'
} else {
finalarray = fetchedids.join(', ')
}
await interaction.editReply({
content: `Gente que ha hecho upvote:\n${finalarray}`,
allowedMentions: {repliedUser: false}
})
}
})

View File

@@ -1,50 +0,0 @@
import { commandModule, CommandType } from "@sern/handler";
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 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)
)
if (await db.exists({msgid: interaction.message.id, userid: interaction.user.id, upordown: -1})) {
await db.findOneAndUpdate({msgid: interaction.message.id, userid: interaction.user.id, upordown: -1}, {upordown: 1}, {returnOriginal: false})
// god forbid I use any! I'm literally done with trying to solve this dude
const upvoteLabel = JSON.stringify(interaction.message!.components[0].components[1].data) as string
const downvotebuttons = new ActionRowBuilder<ButtonBuilder>().setComponents(
new ButtonBuilder()
.setCustomId('suggestions-yes')
.setEmoji('✅')
.setLabel((convertToNumber + 1).toString())
.setStyle(ButtonStyle.Success),
new ButtonBuilder(interaction.message!.components[0].components[1].data as ButtonComponentData)
.setLabel((Number(JSON.parse(upvoteLabel).label) - 1).toString()),
)
await interaction.message.edit({components: [downvotebuttons, row2]})
await interaction.deferUpdate()
} else if (await db.exists({msgid: interaction.message.id, userid: interaction.user.id, upordown: 1})) {
return await interaction.reply({content: 'Ya has hecho upvote.', ephemeral: true})
} else {
const downvotebuttons = new ActionRowBuilder<ButtonBuilder>().setComponents(
new ButtonBuilder()
.setCustomId('suggestions-yes')
.setEmoji('✅')
.setLabel((convertToNumber + 1).toString())
.setStyle(ButtonStyle.Success),
new ButtonBuilder(interaction.message!.components[0].components[1].data as ButtonComponentData)
)
const addToDB = new db({
msgid: interaction.message.id,
userid: interaction.user.id,
upordown: 1
})
await addToDB.save()
await interaction.message.edit({components: [downvotebuttons, row2]})
await interaction.deferUpdate()
}
}
})

View File

@@ -1,27 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { ApplicationCommandOptionType } from "discord.js";
export default commandModule({
name: 'ip',
type: CommandType.Slash,
plugins: [],
//
description: 'La IP del servidor de Minecraft',
options: [
{
name: 'usuario',
description: 'Menciona al usuario al que va dirigido el comando.',
type: ApplicationCommandOptionType.User
}
],
//alias : [],
execute: async (ctx, options) => {
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})
} else {
await ctx.reply({content: `${usuario}` + ", la IP del servidor de Minecraft es `minecraft.maraturing.com`,\nPide acceso con el comando </mcform:1000747672690499594>."})
}
},
});

View File

@@ -1,31 +0,0 @@
import { commandModule, CommandType } from '@sern/handler';
import {
ActionRowBuilder,
ModalBuilder,
TextInputBuilder,
TextInputStyle,
ModalActionRowComponentBuilder,
} from 'discord.js';
export default commandModule({
name: 'mcform',
type: CommandType.Slash,
plugins: [],
description: 'Envia el formulario para entrar al servidor.',
//alias : [],
execute: async (ctx) => {
const modal = new ModalBuilder()
.setCustomId('mcform-main')
.setTitle('Formulario para entrar al servidor');
const input = new TextInputBuilder()
.setCustomId('mcUsernameInput')
.setLabel('Cuál es tu nombre de usuario de Minecraft?')
.setStyle(TextInputStyle.Short);
const usernameActionRow =
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
input
);
modal.addComponents(usernameActionRow);
await ctx.interaction.showModal(modal);
},
});

View File

@@ -1,69 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import db from '../../schemas/afk.js';
import { ApplicationCommandOptionType, EmbedBuilder } from 'discord.js';
export default commandModule({
type: CommandType.Slash,
plugins: [],
description: 'afk command',
//alias : [],
options: [
{
name: 'añadir',
description: 'Di que estás AFK o inactivo por la razón que quieras.',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'motivo',
description: 'El motivo por el que estarás AFK',
type: ApplicationCommandOptionType.String,
required: true,
}
]
},
{
name: 'eliminar',
description: 'Elimina tu AFK',
type: ApplicationCommandOptionType.Subcommand
},
{
name: 'lista',
description: 'Listado de todas las personas AFK',
type: ApplicationCommandOptionType.Subcommand
}
],
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 = ctx.options.getString('motivo');
await (new db({ id: ctx.user.id, reason: reason })).save()
const embed = new EmbedBuilder()
.setAuthor({ name: ctx.user.username, iconURL: ctx.user.displayAvatarURL() })
.setTitle('AFK añadido!')
.setColor('Green')
.setDescription(`Razón: ${reason}`)
await ctx.reply({ embeds: [embed] })
} break;
case 'eliminar': {
if (!await db.exists({ id: ctx.user.id })) return ctx.reply({ content: 'No existes en la base de datos!', ephemeral: true })
await db.deleteOne({ id: ctx.user.id })
await ctx.reply('Ok, has sido eliminado correctamente de la base de datos.')
} break;
case 'lista': {
const map = await Promise.all((await db.find()).map(async (doc) => {
return `${await ctx.client.users.fetch(doc.id)}`
}))
await ctx.reply({
content: `Lista de usuarios AFK:\n${(map.length === 0) ? 'Nadie' : map.join(', ')}`,
allowedMentions: { repliedUser: false }
})
} break;
}
},
});

View File

@@ -1,191 +0,0 @@
import { commandModule, CommandType } from '@sern/handler';
import {
ActionRowBuilder,
ModalBuilder,
TextInputBuilder,
TextInputStyle,
ModalActionRowComponentBuilder,
ButtonBuilder,
ButtonStyle,
ComponentType,
ModalSubmitInteraction,
ApplicationCommandOptionType,
} from 'discord.js';
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: [
disable()
],
description: 'TEMP: Pregunta a Javi LO QUE SEA!',
//alias : [],
options: [
{
name: 'new',
description: 'Haz una nueva pregunta',
type: ApplicationCommandOptionType.Subcommand,
},
{
name: 'get',
description: 'Mira una pregunta teniendo su ID.',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'id',
description: 'El ID de la pregunta',
type: ApplicationCommandOptionType.String,
required: true,
},
],
},
{
name: 'you',
description: 'Todos los IDs de las preguntas que hayas hecho',
type: ApplicationCommandOptionType.Subcommand,
},
{
name: 'answered',
description:
'ORGANIZADOR: Todos los IDs de las preguntas que hayas hecho',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'id',
description: 'El ID de la pregunta',
type: ApplicationCommandOptionType.String,
required: true,
},
],
},
],
execute: async (ctx) => {
switch (ctx.interaction.options.getSubcommand()) {
case 'new': {
const modal = new ModalBuilder()
.setCustomId('askjavi')
.setTitle('Sugerencias');
const input = new TextInputBuilder()
.setCustomId('askjavi-prompt')
.setLabel('Qué quieres preguntarle?')
.setStyle(TextInputStyle.Paragraph);
const suggestionsActionRow =
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
input
);
modal.addComponents(suggestionsActionRow);
const buttons = new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setCustomId('askjavi-buttons-yes')
.setLabel('Sí!')
.setStyle(ButtonStyle.Success),
new ButtonBuilder()
.setCustomId('askjavi-buttons-no')
.setLabel('Ok mejor no')
.setStyle(ButtonStyle.Danger)
);
const message = await ctx.reply({
content: `No puedes enviar sugerencias inútiles o spam.\nSi haces esto, serás descalificado.\nContinuas?`,
components: [buttons],
ephemeral: true,
});
const collector = message.createMessageComponentCollector({
max: 1,
componentType: ComponentType.Button,
time: 60_000,
});
collector.on('collect', async (i) => {
if (i.customId === 'askjavi-buttons-yes') {
const suggestionid = random(5);
await i.showModal(modal);
await ctx.interaction.editReply({
components: [],
});
const submitted = (await ctx.interaction
.awaitModalSubmit({
time: 180000,
filter: (i) => i.user.id === ctx.user.id,
})
.catch((error) => {})) as unknown as ModalSubmitInteraction;
const db = new padyama({
id: i.user.id,
user: i.user.username,
suggestionid: suggestionid,
suggestion: submitted.fields.getTextInputValue('askjavi-prompt'),
});
await db.save();
await submitted.reply({
content: `Tu pregunta ha sido registrada en la base de datos correctamente.\nEl ID de esta pregunta es: \`${suggestionid}\`. Se te contactará por DMs cuando se responda la pregunta.\nPuedes ver tus IDs de preguntas con el comando </askjavi you:1040938647001776199>.`,
ephemeral: true,
});
}
if (i.customId === 'askjavi-buttons-no') {
await ctx.interaction.editReply({
content: 'Ok pues...',
components: [],
});
}
});
}
case 'get': {
const option = ctx.options.getString('id');
const db = await padyama.findOne({ suggestionid: option });
if (db?.suggestion !== undefined)
return await ctx.reply({
content: `La sugerencia es:\n${db?.suggestion}`,
ephemeral: true,
});
else
return await ctx.reply({
content: `Parece que ese ID no se ha encontrado...`,
ephemeral: true,
});
}
case 'you': {
const db = await padyama.find({ id: ctx.user.id });
await ctx.reply({
content: `Los IDs de las preguntas que has hecho:\n${db.map(
(doc) => `\`${doc.suggestionid}\``
)}`,
ephemeral: true,
});
}
case 'answered': {
if (ctx.user.id !== '703974042700611634')
return await ctx.reply({
content: `No puedes usar este comando.`,
ephemeral: true,
});
const option = ctx.options.getString('id');
const db = await padyama.findOne({ suggestionid: option });
if (db?.user !== undefined) {
try {
await (await ctx.user.fetch(db?.id))
.send({
content: `Hola!\nRespecto al AMA del sevidor de Mara:\nTu pregunta con ID \`${option}\` ha sido respondida correctamente!`,
})
.then(async () => {
await ctx.reply({
content: `DM enviado correctamente!`,
ephemeral: true,
});
});
} catch {
await ctx.reply({
content: `Parece que no se ha podido enviar el DM...`,
ephemeral: true,
});
}
} else {
await ctx.reply({
content: `No se ha encontrado el usuario enlazado con el ID en cuestión...`,
ephemeral: true,
});
}
}
}
},
});

View File

@@ -1,48 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
export default commandModule({
name: 'creditos',
type: CommandType.Slash,
plugins: [],
description: 'Créditos del bot (en inglés)',
//alias : [],
options: [],
execute: async (ctx) => {
const baseEmbed = new EmbedBuilder()
.setColor('Blurple')
.setTitle(`Without these people, the bot wouldn't exist!`)
.setAuthor({name: ctx.user.username, iconURL: ctx.user.displayAvatarURL()})
.setFooter({text: `Created by Sr Izan | This list will be expanded`, iconURL: ctx.client.user?.displayAvatarURL()})
const page1 = baseEmbed
.setDescription(`**Development**\n
<@703974042700611634>: Main development.\n
<@464397024247152640>: Trusting me and inviting the bot.\n
**For helping me out**\n
<@182326315813306368>: sern handler dev, helper at WOK, such a cool guy and helper <3\n
<@697795666373640213>: sern handler dev, also helper at WOK, helped me out a ton\n
*Some people at the D.JS discord*: yeah\n
**Motivation**\n
<@719678368173523015>: WHAT ARE YOU DOING ON VINCI RN?!?!?!\n
<@530870655005097995>: Gave some ideas on the *original* roadmap\n
<@678000774441336842>: My good'ol friend, always has been trying new Vinci stuff\n
<@758743564879659069>: Believe it or not, this looper has alvays been beta-testing stuff\n
<@697146020647403651>: For always thanking all my work\n
<@630502288154427414>: he bans everyone on my twitch\n
**And, of course, you <3**\n
Thanks everyone, this has been an absolute ride, I don't have words to express my appreciation! <:Pepelove:1030904410307563542>
`)
const buttons = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setLabel('lol')
.setURL('https://discord.com/channels/928018226330337280/928018227156643857/1030480463690731530')
.setStyle(ButtonStyle.Link),
new ButtonBuilder()
.setLabel('<3')
.setURL('https://discord.com/channels/928018226330337280/1030913456846680134')
.setStyle(ButtonStyle.Link)
)
await ctx.reply({embeds: [page1], components: [buttons], ephemeral: true})
},
});

View File

@@ -1,56 +0,0 @@
import { commandModule, CommandType } from "@sern/handler";
import { ApplicationCommandOptionType } from "discord.js";
import { readFileSync } from "node:fs";
import birthdays from "../../schemas/birthdays.js";
import { acceptingBirthday } from "#plugins";
export default commandModule({
name: "cumple",
type: CommandType.Slash,
plugins: [acceptingBirthday()],
description: "Pon tu cumpleaños en la base de datos para ser felicitado!",
//alias : [],
options: [
{
name: "fecha",
description: "La fecha de tu cumple (D-M) (elige en el autocompletado)",
type: ApplicationCommandOptionType.String,
autocomplete: true,
required: true,
command: {
onEvent: [],
execute: async (autocomplete) => {
const focusedValue = autocomplete.options.getFocused();
let choices = JSON.parse(
String(readFileSync("./util/daysinyear.txt"))
) as Array<string>;
choices = choices.filter((choice) =>
choice.toString().startsWith(focusedValue)
);
choices = choices.slice(0, 25);
await autocomplete.respond(
choices.map((choice) => ({
name: choice.toString(),
value: choice,
}))
);
},
},
},
],
execute: async (ctx) => {
const option = ctx.interaction.options.getString("fecha")
const array = JSON.parse(
String(readFileSync("./util/daysinyear.txt"))
) as Array<string>;
if (!array.includes(option!)) return await ctx.interaction.editReply('Asegúrate que estás eligiendo una fecha del autocompletado!')
if (await birthdays.exists({id: ctx.user.id})) return await ctx.interaction.editReply('No puedes poner tu fecha de nuevo!')
const db = new birthdays({
id: ctx.user.id,
date: option,
alreadysent: false
});
await db.save();
await ctx.interaction.editReply('Ok, guardado correctamente. No puedes volver a cambiar la fecha.')
},
});

View File

@@ -1,64 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { ApplicationCommandOptionType, ColorResolvable, EmbedBuilder } from 'discord.js';
import mctags from '../../../assets/mcTags.json' with { type: "json" };
export default commandModule({
type: CommandType.Slash,
plugins: [],
description: 'Preguntas normalmente preguntadas :pepega:',
//alias : [],
options: [
{
name: 'minecraft',
description: 'Preguntas normalmente preguntadas de Minecraft',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'pregunta',
description: 'La pregunta',
type: ApplicationCommandOptionType.String,
autocomplete: true,
required: true,
command: {
onEvent: [],
execute: async (ctx) => {
const query = ctx.options.getFocused()
const filter = mctags.filter(obj => obj.title.includes(query))
await ctx.respond(
filter.map(map => ({ name: map.title.toString(), value: map.title.toString() }))
)
}
}
},
{
name: 'para',
description: 'Menciona a la persona a la que vaya esto.',
type: ApplicationCommandOptionType.User,
}
]
}
],
execute: async (ctx) => {
switch (ctx.options.getSubcommand()) {
case 'minecraft': {
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()
.setAuthor({ name: ctx.user.username, iconURL: ctx.user.displayAvatarURL() })
.setColor(filter.color as ColorResolvable)
.setTitle(filter.title)
.setDescription(filter.text)
if (forusr) {
await ctx.reply({
content: `Esto es para tí ${forusr}:`,
embeds: [embed]
})
} else {
await ctx.reply({ embeds: [embed] })
}
} break;
}
},
});

View File

@@ -1,29 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import google from 'googlethis'
import { ApplicationCommandOptionType } from 'discord.js';
export default commandModule({
type: CommandType.Slash,
plugins: [],
description: 'Busca cosas en Google.',
//alias : [],
options: [
{
name: 'busqueda',
description: 'Escribe qué quieres buscar',
type: ApplicationCommandOptionType.String,
required: true,
}
],
execute: async (ctx) => {
await ctx.interaction.deferReply()
const prompt = ctx.options.getString('busqueda', true)
const search = await Promise.all((await google.search(prompt)).results.map(res => {
return `[${res.title}](<${res.url}>)`
}).slice(0, 5))
await ctx.interaction.editReply({
content: `Resultados para la búsqueda \`${prompt}\`:\n${search.join('\n')}`
})
},
});

View File

@@ -1,56 +0,0 @@
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

@@ -1,97 +0,0 @@
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,59 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import Genius from 'genius-lyrics'
import { ActionRowBuilder, ApplicationCommandOptionType, ButtonBuilder, ButtonStyle, EmbedBuilder } from 'discord.js';
const genius = new Genius.Client(process.env.GENIUS)
export default commandModule({
type: CommandType.Slash,
plugins: [],
description: 'Busca la letra de una canción (Genius)',
//alias : [],
options: [
{
name: 'busqueda',
description: 'Qué buscar',
type: ApplicationCommandOptionType.String,
required: true,
autocomplete: true,
command: {
onEvent: [],
execute: async (ctx) => {
const input = ctx.options.getFocused();
const choices = (await genius.songs.search(input)).map(res => {
return `${res.title} - ${res.artist.name}|${res.id}`
})
await ctx.respond(
choices.map(choice => {
const [title, id] = choice.split('|')
return ({name: title, value: id})
})
)
}
}
}
],
execute: async (ctx) => {
await ctx.interaction.deferReply({ ephemeral: true })
const prompt = ctx.options.getString('busqueda', true)
const result = await genius.songs.get(Number(prompt))
const embed = new EmbedBuilder()
.setTitle(`${result.title} - ${result.artist.name}`)
.setColor('Random')
.setDescription((await result.lyrics()).slice(0, 3000))
const button = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setLabel('URL de Genius')
.setStyle(ButtonStyle.Link)
.setURL(result.url)
)
await ctx.interaction.editReply({
embeds: [embed],
components: [button]
})
},
});

View File

@@ -1,84 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { createAudioPlayer, createAudioResource, DiscordGatewayAdapterCreator, joinVoiceChannel } from "@discordjs/voice";
import got from "got";
import { ApplicationCommandOptionType, EmbedBuilder } from "discord.js";
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: [],
description: 'Reproduce la radio',
options: [
{
name: 'reproducir',
description: 'Reproduce una radio de la lista',
type: ApplicationCommandOptionType.String,
autocomplete: true,
required: true,
command: {
onEvent: [],
async execute(ctx){
const focusedValue = ctx.options.getFocused();
const filtered = choices.filter(choice => choice.startsWith(focusedValue));
await ctx.respond(
filtered.map(choice => ({ name: choice, value: choice })),
);
}
}
}
],
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.`)
.setDescription(`A veces la radio tarda en cargar, sé paciente :'D`);
const notFoundEmbed = new EmbedBuilder()
.setColor("Red")
.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) {
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)
resource.volume!.setVolume(0.7)
await ctx.reply({embeds: [embed], ephemeral: true})
}
switch (radioname) {
case 'Rock FM': {
playRadio("https://flucast-m04-06.flumotion.com/cope/rockfm.mp3")
} break;
case 'Cadena 100': {
playRadio("https://server8.emitironline.com:18196/stream")
} break;
case 'Cadena Dial': {
playRadio("http://20853.live.streamtheworld.com/CADENADIAL.mp3")
} break;
case 'BBC 1': {
playRadio("http://stream.live.vc.bbcmedia.co.uk/bbc_radio_one")
} break;
case 'RNE 1': {
playRadio("https://crtve--di--crtve-ice--01--cdn.cast.addradio.de/crtve/rne1/main/mp3/high")
} break;
case 'RNE 5': {
playRadio("https://crtve--di--crtve-ice--01--cdn.cast.addradio.de/crtve/rne5/main/mp3/high")
} break;
case 'Los 40': {
playRadio('http://stream.ondaceronoroeste.es:8000/stream')
} break;
case 'Gensokyo Radio': {
playRadio('https://stream.gensokyoradio.net/3')
} break;
default: {
await ctx.reply({embeds: [notFoundEmbed], ephemeral: true})
} break;
}
},
});

View File

@@ -1,88 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { ownerOnly } from "#plugins";
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: [ownerOnly()],
description: 'ADMIN: Spawnea un menú de roles',
//alias : [],
options: [
{
name: "channel",
type: ApplicationCommandOptionType.Channel,
description: "The channel to send the message to",
channel_types: [ChannelType.GuildText],
required: true,
},
{
name: "role",
type: ApplicationCommandOptionType.String,
description: "The roles to attach (upto 25)",
required: true,
},
{
name: "message",
type: ApplicationCommandOptionType.String,
description: "The message to send",
required: true,
},
],
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 = ctx.options.getString("message", true);
if (role.size > 25) return ctx.reply("Too many roles");
const cdn = role.filter(
(r) =>
r.managed ||
r.position > (ctx.guild?.members.me)!.roles.highest.position
).size;
if (cdn) {
return ctx.reply(
`Some roles are managed by integration or higher than my highest role.\nPlease try again`
);
}
await ctx.interaction.deferReply();
// @ts-ignore it should still be a textchannel
const row = createMenu(channel, role);
const embed = new EmbedBuilder()
.setTitle(message)
.setDescription(
`Por favor selecciona los roles que quieras.\nPuedes seleccionar varios roles también!`
)
.setColor(0xcc5279);
await channel.send({
embeds: [embed],
components: [row],
});
await ctx.interaction.editReply("✅ Done!");
},
});
function createMenu(channel: TextChannel, role: Collection<string, Role>) {
if (!channel || !role) throw new Error("Missing channel or role");
const menu = new StringSelectMenuBuilder()
.setCustomId("role-menu")
.setMaxValues(role.size)
.setMinValues(0)
.setPlaceholder("Pick some roles here!")
.setOptions(
role.map((r) => {
return {
label: r.name,
value: r.id,
};
})
);
const row = new ActionRowBuilder<StringSelectMenuBuilder>().setComponents(menu);
return row;
};

View File

@@ -1,37 +0,0 @@
import { commandModule, CommandType } from "@sern/handler";
import axios, { AxiosResponse } from "axios";
import { ApplicationCommandOptionType } from "discord.js";
import { disable } from "#plugins";
export default commandModule({
name: "acortar",
type: CommandType.Slash,
plugins: [
disable()
],
description: "Acorta una URL a vinci.tk",
options: [
{
name: "url",
description: "la URL larga",
type: ApplicationCommandOptionType.String,
required: true,
},
],
//alias : [],
execute: async (ctx, options) => {
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}`,
{
validateStatus: function (status) {
return status === 200 || status === 400;
},
}
).then((res: AxiosResponse) => res.data);
ctx.reply({
content: `URL acortada: <${request.shorturl}>\nURL original: <${url}>`,
ephemeral: true,
});
},
});

View File

@@ -1,44 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
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: [disable()],
description: 'Enseña estadísticas del bot.',
//alias : [],
options: [],
execute: async (ctx, options) => {
await ctx.interaction.deferReply({ ephemeral: true })
const cpubrand = await axios(`http://192.168.1.79:7271/cpubrand`)
const cpucores = await axios(`http://192.168.1.79:7271/cpucores`)
const ramtotal = await axios(`http://192.168.1.79:7271/ramtotal`)
const ramfree = await axios(`http://192.168.1.79:7271/ramfree`)
const dockertotal = await axios(`http://192.168.1.79:7271/dockertotal`)
const uptime = prettySeconds(process.uptime())
const embed = new EmbedBuilder()
.setAuthor({name: `${ctx.user.username}`, iconURL: `${ctx.user.displayAvatarURL()}`})
.setTitle(`Estadísticas de Vinci.`)
.setThumbnail(`https://i.imgur.com/UwC1x8T.png`)
.setURL('https://status.vinci.tk')
.setColor('Random')
.addFields(
{name: "Fabricante de CPU", value: `${cpubrand.data}`, inline: true},
{name: `Núcleos de CPU`, value: `${cpucores.data}`, inline: true},
{name: '\u200B', value: '\u200B', inline: true},
{name: `RAM total`, value: `${ramtotal.data}`, inline: true},
{name: `RAM libre`, value: `${ramfree.data}`, inline: true},
{name: '\u200B', value: '\u200B', inline: true},
{name: 'Contenedores de Docker', value: `${dockertotal.data}`, inline: true},
{name: '\u200B', value: '\u200B', inline: true},
{name: 'Tiempo encendido', value: `${uptime}`},
// {name: '\u200B', value: '\u200B', inline: true},
// {name: 'Uptime del servidor', value: `${prettySeconds(`${nodeuptime.data}`)}`}
)
await ctx.interaction.editReply({embeds: [embed]})
},
});

View File

@@ -1,30 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { ActionRowBuilder, ModalBuilder, TextInputBuilder, TextInputStyle, ModalActionRowComponentBuilder } from 'discord.js'
export default commandModule({
name: 'sugerencias',
type: CommandType.Slash,
plugins: [],
description: 'Envia una sugerencia.',
//alias : [],
execute: async (ctx) => {
const modal = new ModalBuilder()
.setCustomId('sugerencias')
.setTitle('Sugerencias');
// Create the text input components
const input = new TextInputBuilder()
.setCustomId('sugerenciasInput')
// The label is the prompt the user sees for this input
.setLabel("Tienes sugerencias?")
// Short means only a single line of text
.setStyle(TextInputStyle.Paragraph);
// An action row only holds one text input,
// so you need one action row per text input.
const suggestionsActionRow = new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(input);
// Add inputs to the modal
modal.addComponents(suggestionsActionRow);
await ctx.interaction.showModal(modal);
}
});

View File

@@ -1,91 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import axios from 'axios';
import { ActionRowBuilder, ApplicationCommandOptionType, ButtonBuilder, ButtonStyle, ComponentType, EmbedBuilder } from 'discord.js';
const choices = ['es', 'en', 'fr', 'de', 'hi', 'it', 'ja', 'ko', 'pl']
export default commandModule({
type: CommandType.Slash,
plugins: [],
// , '928018226330337280'
description: 'Traduce lo que quieras!',
//alias : [],
options: [
{
name: 'frase',
description: 'La frase que traducir',
type: ApplicationCommandOptionType.String,
required: true
},
{
name: 'idioma',
description: 'El idioma al que quieras traducir',
type: ApplicationCommandOptionType.String,
required: true,
autocomplete: true,
command: {
onEvent: [],
execute: async (interaction) => {
const focusedValue = interaction.options.getFocused();
const filtered = choices.filter(choice => choice.startsWith(focusedValue))
await interaction.respond(
filtered.map((choice) => ({
name: choice,
value: choice,
}))
);
}
}
}
],
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.'})
await ctx.interaction.deferReply()
const before = performance.now()
const request = await axios.post('https://translate.nvda.es/translate',
{
q: stringToTranslate,
source: "auto",
target: langToTranslate,
format: "text",
api_key: ""
},
{
headers: {
'Content-Type': 'application/json'
}
}
).then(res => res.data)
const button = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId('translate-original')
.setLabel('Texto original')
.setStyle(ButtonStyle.Primary)
)
const after = performance.now()
const embed = new EmbedBuilder()
.setAuthor({name: ctx.user.username, iconURL: ctx.user.displayAvatarURL()})
.setTitle(`La traducción pedida. He tardado \`${(after - before).toFixed(2)}ms\`.`)
.setDescription(request.translatedText as string)
.setFooter({text: 'Traducido por LibreTranslate'})
const message = await ctx.interaction.editReply({embeds: [embed], components: [button]})
const collector = message.createMessageComponentCollector({componentType: ComponentType.Button, time: 60_000})
collector.on('collect', async (i) => {
if (i.customId !== 'translate-original') return
await i.reply({content: `La traducción original es:\n\`\`\`${stringToTranslate}\`\`\``, ephemeral: true})
})
},
});

View File

@@ -1,17 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
// @ts-ignore
import prettySeconds from 'pretty-seconds-spanish'
export default commandModule({
name: 'uptime',
type: CommandType.Slash,
plugins: [],
description: 'Enseña el tiempo que ha estado encendido el bot.',
//alias : [],
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,69 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { ApplicationCommandOptionType, AutocompleteInteraction, CacheType, CommandInteractionOptionResolver } from 'discord.js';
import { getWikipedia, searchWikipedia } from '../../util/wikipedia.js';
export default commandModule({
type: CommandType.Slash,
plugins: [],
description: 'Busca cosas por wikipedia',
//alias : [],
options: [
{
name: 'español',
description: 'Busca cosas por Wikipedia en español',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'busqueda',
description: 'Escribe qué buscar.',
type: ApplicationCommandOptionType.String,
required: true,
autocomplete: true,
command: {
onEvent: [],
execute: async (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() }))
)
}
}
}
]
},
{
name: 'ingles',
description: 'Busca cosas por Wikipedia en inglés',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'search',
description: 'Escribe qué buscar.',
type: ApplicationCommandOptionType.String,
required: true,
autocomplete: true,
command: {
onEvent: [],
execute: async (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() }))
)
}
}
}
]
}
],
execute: async (ctx) => {
const options = ctx.options as unknown as CommandInteractionOptionResolver<CacheType>
switch (ctx.options.getSubcommand()) {
case 'español': {
getWikipedia('es', ctx, options);
} break;
case 'ingles': {
getWikipedia('en', ctx, options);
} break;
}
},
});

View File

@@ -1,32 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { ownerOnly } from "#plugins";;
import { ApplicationCommandOptionType, TextChannel } from 'discord.js'
export default commandModule({
name: 'prune',
type: CommandType.Slash,
plugins: [ownerOnly()],
description: 'ADMIN: Elimina hasta 100 mensajes',
options: [{
name: 'numero',
description: 'Escribe un número',
type: ApplicationCommandOptionType.Number,
required: true,
min_value: 1,
max_value: 100
}],
//alias : [],
execute: async (ctx) => {
try {
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 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,40 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { ownerOnly } from "#plugins";
import { ApplicationCommandOptionType, TextChannel } from "discord.js";
export default commandModule({
name: 'slowmode',
type: CommandType.Slash,
plugins: [ownerOnly()],
description: 'ADMIN: Pon modo lento a canales de texto',
options: [
{
name: "segundos",
description: "Los segundos de modo lento",
type: ApplicationCommandOptionType.Number,
required: true
},
{
name: "razon",
description: "La razón del modo lento",
type: ApplicationCommandOptionType.String,
required: true
}
],
//alias : [],
execute: async (ctx) => {
try {
const seconds = ctx.options.getNumber("segundos", true);
const reason = ctx.options.getString("razon", true);
(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 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,15 +0,0 @@
import { commandModule, CommandType } from '@sern/handler'
import { ownerOnly } from "#plugins"
export default commandModule({
name: 'ping',
type: CommandType.Slash,
plugins: [],
description: 'A ping command',
//alias : [],
options: [],
execute: async (ctx, options) => {
await ctx.reply('Hello World!');
},
});

View File

@@ -1,12 +0,0 @@
//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
View File

@@ -1,24 +0,0 @@
/*
* 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,24 +0,0 @@
import { EventType, discordEvent, eventModule } from '@sern/handler';
import { EmbedBuilder, Message } from 'discord.js';
import db from '../schemas/afk.js';
export default discordEvent({
name: 'messageCreate',
execute: async (message) => {
const dbEntries = await db.find()
dbEntries.forEach(async (doc) => {
if (!message.content.includes(`<@${doc.id}`)) return;
if (message.author.bot) return;
const username = (await message.client.users.fetch(doc.id)).username
const embed = new EmbedBuilder()
.setColor('Red')
.setTitle(`Usuario ${username} está AFK`)
.setDescription(`El usuario que has mencionado en tu mensaje ha marcado su estado como AFK\nRazón: ${doc.reason}`)
.setFooter({ text: 'Este mensaje se eliminará en 10 segundos (wepa, como una bomba!)' })
const sentMessage = await message.reply({ embeds: [embed] })
setTimeout(async () => { await sentMessage.delete() }, 10_000)
})
},
});

View File

@@ -1,62 +0,0 @@
import { discordEvent } from '@sern/handler';
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder, Message, TextChannel } from 'discord.js';
import tf from '@tensorflow/tfjs-node'
import axios from 'axios';
import { nsfwModel } from '../index.js';
export default discordEvent({
name: 'messageCreate',
execute(message: Message) {
message.attachments.forEach(async (attachment) => {
switch (attachment.contentType) {
case 'image/png':
break;
case 'image/jpeg':
break;
default:
return;
}
const pic = await axios.get(attachment.url,
{ responseType: 'arraybuffer' }
)
const image = tf.node.decodeImage(pic.data, 3) as tf.Tensor3D
// @ts-ignore
const predictions = await nsfwModel.classify(image)
switch (predictions[0].className) {
case 'Hentai':
case 'Porn':
if (predictions[0].probability > 0.75) {
const embed = new EmbedBuilder()
.setTitle(`Se ha detectado una imagen NSFW en tus adjuntos.`)
.setDescription('Por eso, se ha eliminado tu mensaje.\nPor si es un falso positivo, te vamos a dejar abajo el contenido del mensaje para recuperarlo.')
.setFields(
{ name: 'Contenido del mensaje', value: message.content || '(nada)' },
{ name: 'Tipo', value: predictions[0].className.toString() },
)
.setFooter({ text: 'Esta detección ha sido automatizada.' })
const modLogsEmbed = new EmbedBuilder()
.setAuthor({ name: message.author.username, iconURL: message.author.displayAvatarURL() })
.setTitle(`Se ha detectado una imagen NSFW en los adjuntos de un mensaje.`)
.setDescription('Aquí está toda la información:')
.setFields(
{ name: 'Contenido del mensaje', value: message.content || '(nada)' },
{ name: 'Tipo', value: predictions[0].className.toString() },
)
.setFooter({ text: 'Esta detección ha sido automatizada.' })
const button = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setLabel('Imagen adjuntada que saltó las alarmas')
.setURL(attachment.url)
.setStyle(ButtonStyle.Link)
)
await message.delete()
await message.author.send({ embeds: [embed], components: [button] })
await (await message.client.channels.fetch(process.env.MODLOGS_CHANNEL!) as TextChannel).send({ embeds: [modLogsEmbed], components: [button] })
}
break;
}
})
},
});

View File

@@ -1,101 +0,0 @@
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

@@ -1,72 +0,0 @@
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

@@ -1,29 +0,0 @@
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

@@ -1,9 +0,0 @@
import { EventType, eventModule } from "@sern/handler";
export default eventModule({
type: EventType.Sern,
name : 'error',
execute(err) {
console.log(err);
}
})

View File

@@ -1,19 +0,0 @@
import { EmbedBuilder, GuildMember, TextChannel } from "discord.js";
import { EventType, discordEvent, eventModule } from "@sern/handler";
export default discordEvent({
name: 'guildMemberAdd',
execute(member) {
if (member.guild.id !== process.env.GUILDID) return;
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 unknown as TextChannel
channel.send({embeds: [newMemberEmbed]})
}
});

View File

@@ -1,19 +0,0 @@
import { EmbedBuilder, GuildMember, TextChannel } from "discord.js";
import { EventType, discordEvent, eventModule } from "@sern/handler";
export default discordEvent({
name: 'guildMemberRemove',
execute(member) {
if (member.guild.id !== process.env.GUILDID) return;
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 unknown as TextChannel
channel.send({embeds: [leaveEmbed]})
}
});

View File

@@ -1,53 +0,0 @@
import { discordEvent } from '@sern/handler';
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
EmbedBuilder,
TextChannel,
} from 'discord.js';
import { scamLinks } from '../index.js';
export default discordEvent({
name: 'messageCreate',
async execute(message) {
if (!message.content.includes('https://')) return;
const index = message.content.indexOf("https://");
let link = 'some goofy ahh string that is gonna be replaced'
if (index !== -1) {
let endIndex = message.content.indexOf(" ", index);
if (endIndex === -1) {
endIndex = message.content.length;
}
link = message.content.substring(index, endIndex);
}
const url = new URL(link);
if (scamLinks.includes(url.hostname)) {
const embed = new EmbedBuilder()
.setTitle(`Se ha detectado un enlace scam en tu mensaje.`)
.setDescription('Por eso, se ha eliminado tu mensaje.\nPor si es un falso positivo, te vamos a dejar abajo el contenido del mensaje para recuperarlo.')
.setFields(
{ name: 'Contenido del mensaje', value: message.content || '(nada)' },
)
.setFooter({ text: 'Esta detección ha sido automatizada por Vinci.' })
const modLogsEmbed = new EmbedBuilder()
.setTitle(`Se ha detectado un enlace scam el mensaje de un usuario.`)
.setDescription('Por eso, se ha eliminado el mensaje.')
.setFields(
{ name: 'Contenido del mensaje', value: message.content || '(nada)' },
)
.setFooter({ text: 'Esta detección ha sido automatizada por Vinci.' })
const button = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setLabel('Enlace que saltó las alarmas.')
.setURL(link)
.setStyle(ButtonStyle.Link)
)
await message.delete()
await message.author.send({ embeds: [embed], components: [button] })
await (await message.client.channels.fetch(process.env.MODLOGS_CHANNEL!) as TextChannel).send({ embeds: [modLogsEmbed], components: [button] })
}
},
});

View File

@@ -1,10 +0,0 @@
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,92 +0,0 @@
import { makeDependencies } from '@sern/handler';
import { ActivityOptions, ActivityType } from 'discord.js';
import { Client, GatewayIntentBits } from 'discord.js';
import { Sern } from '@sern/handler';
import { config as dotenv } from 'dotenv';
import mongoose from 'mongoose';
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 Spotify from 'spotify-api.js';
import { Publisher } from '@sern/publisher';
import * as sernconfig from './config.js'
export let devMode: boolean
if (process.argv[2] === '--dev') {
devMode = true
dotenv({path: '.env.dev'})
console.clear()
} else {
dotenv()
}
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildMessageReactions,
GatewayIntentBits.GuildVoiceStates,
],
});
mongoose.connect(process.env.MONGODB!).then(() => {
console.log('Connected to MongoDB');
});
const spotifyClient = await Spotify.Client.create({
token: { clientID: process.env.SPOTIFY_CLIENT!, clientSecret: process.env.SPOTIFY_SECRET! },
})
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(sernconfig);
client.on('ready', async () => {
console.log('Logged on!');
setInterval(() => {
const statuses = [
{ name: 'Minecraft', type: ActivityType.Playing },
{ name: 'cómo escribe Javi', 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);
}, 10000);
if (!devMode) {
setIntervalAsync(async () => {
await youtubenotifications(client);
}, 120_000);
setIntervalAsync(async () => {
await birthdays(client);
}, 3_600_000);
setIntervalAsync(async () => {
await minecraftstatus(client);
}, 20_000);
} else {
console.log('DevMode got activated, there are no checkers in this version.')
}
});
// export const scamLinks = await axios.get('https://api.hyperphish.com/gimme-domains').then(res => res.data as Array<string>)
client.login();

View File

@@ -1,107 +0,0 @@
//@ts-nocheck
/**
* This is buttonConfirmation plugin, it runs confirmation prompt in the form of buttons.
* Note that you need to use edit/editReply in the command itself because we are already replying in the plugin!
* Credits to original plugin of confirmation using reactions and its author!
*
* @author @EvolutionX-10 [<@697795666373640213>]
* @version 1.0.0
* @example
* ```ts
* import { buttonConfirmation } from "../plugins/buttonConfirmation";
* import { commandModule } from "@sern/handler";
* export default commandModule({
* plugins: [ buttonConfirmation() ],
* execute: (ctx) => {
* //your code here
* }
* })
* ```
*/
import { CommandControlPlugin, CommandType, controller } from "@sern/handler";
import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ComponentType,
} from "discord.js";
export function acceptingBirthday(options?: Partial<ConfirmationOptions>) {
return CommandControlPlugin<CommandType.Both>(async (ctx, args) => {
options = {
content: "Se va a guardar tu cumpleaños en la base de datos de Vinci.\nAceptas?",
denialMessage: "Ok pues...",
labels: ["No", "Sí"],
time: 60_000,
wrongUserResponse: "Esto no es para tí!",
...options,
};
const buttons = options.labels!.map((l, i) => {
return new ButtonBuilder()
.setCustomId(l)
.setLabel(l)
.setStyle(
i === 0 ? ButtonStyle.Danger : ButtonStyle.Success
);
});
const sent = await ctx.reply({
content: options.content,
components: [
new ActionRowBuilder<ButtonBuilder>().setComponents(
buttons
),
],
ephemeral: true
});
const collector = sent.createMessageComponentCollector({
componentType: ComponentType.Button,
filter: (i) => i.user.id === ctx.user.id,
time: options.time,
});
return new Promise((resolve) => {
collector.on("collect", async (i) => {
await i.update({ components: [] });
collector.stop();
if (i.customId === options!.labels![1]) {
resolve(controller.next());
return;
}
await i.editReply({
content: options?.denialMessage,
});
resolve(controller.stop());
});
collector.on("end", async (c) => {
if (c.size) return;
buttons.forEach((b) => b.setDisabled());
await sent.edit({
components: [
new ActionRowBuilder<ButtonBuilder>().setComponents(
buttons
),
],
});
});
collector.on("ignore", async (i) => {
await i.reply({
content: options?.wrongUserResponse,
ephemeral: true,
});
});
});
});
}
interface ConfirmationOptions {
content: string;
denialMessage: string;
time: number;
labels: [string, string];
wrongUserResponse: string;
}

View File

@@ -1,80 +0,0 @@
//@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

@@ -1,5 +0,0 @@
export * from './publish.js'
export * from './ownerOnly.js'
export * from './srIzanOnly.js'
export * from './acceptingBirthday.js'
export * from './disable.js'

View File

@@ -1,29 +0,0 @@
// @ts-nocheck
/**
* This is OwnerOnly plugin, it allows only bot owners to run the command, like eval.
*
* @author @EvolutionX-10 [<@697795666373640213>]
* @version 1.0.0
* @example
* ```ts
* import { ownerOnly } from "../plugins/ownerOnly";
* import { commandModule } from "@sern/handler";
* export default commandModule({
* plugins: [ ownerOnly() ],
* execute: (ctx) => {
* //your code here
* }
* })
* ```
*/
import { CommandType, CommandControlPlugin, controller } from "@sern/handler";
const ownerIDs = ["464397024247152640", "703974042700611634", '252679156465139722', '370918560446545922', '375984365181599744', '785836117630910485', '368107342140801025']; //! Fill your ID
export function ownerOnly() {
return CommandControlPlugin<CommandType.Both>((ctx, args) => {
if (ownerIDs.includes(ctx.user.id)) return controller.next();
//* If you want to reply when the command fails due to user not being owner, you can use following
ctx.reply("**ERROR**: Sólo los administradores pueden correr este comando.");
return controller.stop(); //! Important: It stops the execution of command!
});
}

View File

@@ -1,215 +0,0 @@
// @ts-nocheck
/**
* @plugin
* [DEPRECATED] It allows you to publish your application commands using the discord.js library with ease.
*
* @author @EvolutionX-10 [<@697795666373640213>]
* @version 2.0.0
* @example
* ```ts
* import { publish } from "../plugins/publish";
* import { commandModule } from "@sern/handler";
* export default commandModule({
* plugins: [ publish() ], // put an object containing permissions, ids for guild commands, boolean for dmPermission
* // plugins: [ publish({ guildIds: ['guildId'], defaultMemberPermissions: 'Administrator'})]
* execute: (ctx) => {
* //your code here
* }
* })
* ```
* @end
*/
import {
CommandInitPlugin,
CommandType,
controller,
SernOptionsData,
SlashCommand,
Service,
} from "@sern/handler";
import {
ApplicationCommandData,
ApplicationCommandType,
ApplicationCommandOptionType,
PermissionResolvable,
} from "discord.js";
export const CommandTypeRaw = {
[CommandType.Both]: ApplicationCommandType.ChatInput,
[CommandType.CtxUser]: ApplicationCommandType.User,
[CommandType.CtxMsg]: ApplicationCommandType.Message,
[CommandType.Slash]: ApplicationCommandType.ChatInput,
} as const;
export function publish<
T extends
| CommandType.Both
| CommandType.Slash
| CommandType.CtxMsg
| CommandType.CtxUser,
>(options?: PublishOptions) {
return CommandInitPlugin<T>(async ({ module }) => {
// Users need to provide their own useContainer function.
let client;
try {
client = (await import("@sern/handler")).Service("@sern/client");
} catch {
const { useContainer } = await import("../index.js");
client = useContainer("@sern/client")[0];
}
const defaultOptions = {
guildIds: [],
dmPermission: undefined,
defaultMemberPermissions: null,
};
options = { ...defaultOptions, ...options } as PublishOptions &
ValidPublishOptions;
let { defaultMemberPermissions, dmPermission, guildIds } =
options as unknown as ValidPublishOptions;
function c(e: unknown) {
console.error("publish command didnt work for", module.name);
console.error(e);
}
const log =
(...message: any[]) =>
() =>
console.log(...message);
const logged = (...message: any[]) => log(message);
/**
* a local function that returns either one value or the other,
* depending on {t}'s CommandType. If the commandtype of
* this module is CommandType.Both or CommandType.Text or CommandType.Slash,
* return 'is', else return 'els'
* @param t
* @returns S | T
*/
const appCmd = <V extends CommandType, S, T>(t: V) => {
return (is: S, els: T) => ((t & CommandType.Both) !== 0 ? is : els);
};
const curAppType = CommandTypeRaw[module.type];
const createCommandData = () => {
const cmd = appCmd(module.type);
return {
name: module.name,
type: curAppType,
description: cmd(module.description, ""),
options: cmd(
optionsTransformer((module as SlashCommand).options ?? []),
[],
),
defaultMemberPermissions,
dmPermission,
} as ApplicationCommandData;
};
try {
const commandData = createCommandData();
if (!guildIds.length) {
const cmd = (await client.application!.commands.fetch()).find(
(c) => c.name === module.name && c.type === curAppType,
);
if (cmd) {
if (!cmd.equals(commandData, true)) {
logged(
`Found differences in global command ${module.name}`,
);
cmd.edit(commandData).then(
log(
`${module.name} updated with new data successfully!`,
),
);
}
return controller.next();
}
client
.application!.commands.create(commandData)
.then(log("Command created", module.name))
.catch(c);
return controller.next();
}
for (const id of guildIds) {
const guild = await client.guilds.fetch(id).catch(c);
if (!guild) continue;
const guildCmd = (await guild.commands.fetch()).find(
(c) => c.name === module.name && c.type === curAppType,
);
if (guildCmd) {
if (!guildCmd.equals(commandData, true)) {
logged(`Found differences in command ${module.name}`);
guildCmd
.edit(commandData)
.then(
log(
`${module.name} updated with new data successfully!`,
),
)
.catch(c);
continue;
}
continue;
}
guild.commands
.create(commandData)
.then(log("Guild Command created", module.name, guild.name))
.catch(c);
}
return controller.next();
} catch (e) {
logged("Command did not register" + module.name);
logged(e);
return controller.stop();
}
});
}
export function optionsTransformer(ops: Array<SernOptionsData>) {
return ops.map((el) => {
switch (el.type) {
case ApplicationCommandOptionType.String:
case ApplicationCommandOptionType.Number:
case ApplicationCommandOptionType.Integer: {
return el.autocomplete && "command" in el
? (({ command, ...el }) => el)(el)
: el;
}
default:
return el;
}
});
}
export type NonEmptyArray<T extends `${number}` = `${number}`> = [T, ...T[]];
export interface ValidPublishOptions {
guildIds: string[];
dmPermission: boolean;
defaultMemberPermissions: PermissionResolvable;
}
interface GuildPublishOptions {
guildIds?: NonEmptyArray;
defaultMemberPermissions?: PermissionResolvable;
dmPermission?: never;
}
interface GlobalPublishOptions {
defaultMemberPermissions?: PermissionResolvable;
dmPermission?: false;
guildIds?: never;
}
type BasePublishOptions = GuildPublishOptions | GlobalPublishOptions;
export type PublishOptions = BasePublishOptions &
(
| Required<Pick<BasePublishOptions, "defaultMemberPermissions">>
| (
| Required<Pick<BasePublishOptions, "dmPermission">>
| Required<Pick<BasePublishOptions, "guildIds">>
)
);

View File

@@ -1,29 +0,0 @@
// @ts-nocheck
/**
* This is OwnerOnly plugin, it allows only bot owners to run the command, like eval.
*
* @author @EvolutionX-10 [<@697795666373640213>]
* @version 1.0.0
* @example
* ```ts
* import { ownerOnly } from "../plugins/ownerOnly";
* import { commandModule } from "@sern/handler";
* export default commandModule({
* plugins: [ ownerOnly() ],
* execute: (ctx) => {
* //your code here
* }
* })
* ```
*/
import { CommandType, CommandControlPlugin, controller } from "@sern/handler";
const ownerIDs = ['703974042700611634']; //! Fill your ID
export function srIzanOnly() {
return CommandControlPlugin<CommandType.Both>((ctx, args) => {
if (ownerIDs.includes(ctx.user.id)) return controller.next();
//* If you want to reply when the command fails due to user not being owner, you can use following
ctx.reply("**ERROR**: Sólo Sr Izan puede correr este comando.");
return controller.stop(); //! Important: It stops the execution of command!
});
}

View File

@@ -1,7 +0,0 @@
import mongoose from 'mongoose'
const schema = new mongoose.Schema({
id: {type: String, required: true},
reason: {type: String, required: true},
});
const db = mongoose.model('afk', schema, 'afk');
export default db

View File

@@ -1,8 +0,0 @@
import mongoose from 'mongoose'
const schema = new mongoose.Schema({
id: {type: String, required: true},
date: {type: String, required: true},
alreadysent: {type: Boolean, required: true},
});
const db = mongoose.model('birthday', schema, 'birthdays');
export default db

View File

@@ -1,12 +0,0 @@
import mongoose from 'mongoose'
const messageSchema = new mongoose.Schema({
role: { type: String, required: true },
content: { type: String, reqmuired: true },
});
const schema = new mongoose.Schema({
messageid: { type: String, required: true },
threadid: { type: String, required: true },
messages: { type: [messageSchema], required: true },
});
const db = mongoose.model('chatgpt', schema, 'chatgpt');
export default db

View File

@@ -1,9 +0,0 @@
import mongoose from 'mongoose'
const schema = new mongoose.Schema({
id: {type: String, required: true},
user: {type: String, required: true},
suggestionid: {type: String, required: true},
suggestion: {type: String, required: true},
});
const db = mongoose.model('padyama', schema, 'padyama');
export default db

View File

@@ -1,8 +0,0 @@
import mongoose from 'mongoose'
const schema = new mongoose.Schema({
msgid: {type: String, required: true},
userid: {type: String, required: true},
upordown: {type: Number, required: true}
});
const db = mongoose.model('suggestions', schema, 'suggestions');
export default db

View File

@@ -1,7 +0,0 @@
import mongoose from 'mongoose'
const schema = new mongoose.Schema({
id: {type: String, required: true},
user: {type: String, required: true}
});
const db = mongoose.model('twitter', schema, 'twitter');
export default db

View File

@@ -1,7 +0,0 @@
import mongoose from "mongoose";
const schema = new mongoose.Schema({
id: {type: String, required: true},
times: {type: Number, required: true}
});
const db = mongoose.model("warn", schema);
export default db

View File

@@ -1,7 +0,0 @@
import mongoose from 'mongoose'
const schema = new mongoose.Schema({
id: {type: String, required: true},
user: {type: String, required: true}
});
const db = mongoose.model('youtube', schema, 'youtube');
export default db

View File

@@ -1,29 +0,0 @@
import dayjs from "dayjs";
import { Client, TextChannel, ThreadAutoArchiveDuration } from "discord.js";
import db from "../schemas/birthdays.js";
export default async function birthdays(client: Client) {
db.find({}, (err, user) => {
if (err) throw err
user.map(async user => {
const finduser = await db.findOne({ id: user.id });
async function saveit() {
finduser!.alreadysent = false
await finduser!.save()
}
if (finduser!.date === dayjs().format("D-M")) {} else return
if (finduser!.alreadysent === true && dayjs().format('D-M') !== finduser!.date) return saveit()
if (finduser!.alreadysent === true) return
const sendtochannel = (await (
await client.guilds.fetch(process.env.GUILDID!)
).channels.fetch(process.env.BIRTHDAYS_CHANNEL!)) as TextChannel;
const fetchuser = await client.users.fetch(user.id)
const message = await sendtochannel.send({ content: `Hola <@&1039613683422208020>!\nHoy es el cumpleaños de <@${finduser!.id}> 🎉🎉🎉\nMuchas felicidades!` })
message.react('🎉')
message.react('<:Pog:1030169609178976346>')
message.startThread({name: `Thread de felicitación a ${fetchuser.username}!`, autoArchiveDuration: ThreadAutoArchiveDuration.OneWeek})
finduser!.alreadysent = true
await finduser?.save()
})
})
}

View File

@@ -1 +0,0 @@
["1-1","2-1","3-1","4-1","5-1","6-1","7-1","8-1","9-1","10-1","11-1","12-1","13-1","14-1","15-1","16-1","17-1","18-1","19-1","20-1","21-1","22-1","23-1","24-1","25-1","26-1","27-1","28-1","29-1","30-1","31-1","1-2","2-2","3-2","4-2","5-2","6-2","7-2","8-2","9-2","10-2","11-2","12-2","13-2","14-2","15-2","16-2","17-2","18-2","19-2","20-2","21-2","22-2","23-2","24-2","25-2","26-2","27-2","28-2","1-3","2-3","3-3","4-3","5-3","6-3","7-3","8-3","9-3","10-3","11-3","12-3","13-3","14-3","15-3","16-3","17-3","18-3","19-3","20-3","21-3","22-3","23-3","24-3","25-3","26-3","27-3","28-3","29-3","30-3","31-3","1-4","2-4","3-4","4-4","5-4","6-4","7-4","8-4","9-4","10-4","11-4","12-4","13-4","14-4","15-4","16-4","17-4","18-4","19-4","20-4","21-4","22-4","23-4","24-4","25-4","26-4","27-4","28-4","29-4","30-4","1-5","2-5","3-5","4-5","5-5","6-5","7-5","8-5","9-5","10-5","11-5","12-5","13-5","14-5","15-5","16-5","17-5","18-5","19-5","20-5","21-5","22-5","23-5","24-5","25-5","26-5","27-5","28-5","29-5","30-5","31-5","1-6","2-6","3-6","4-6","5-6","6-6","7-6","8-6","9-6","10-6","11-6","12-6","13-6","14-6","15-6","16-6","17-6","18-6","19-6","20-6","21-6","22-6","23-6","24-6","25-6","26-6","27-6","28-6","29-6","30-6","1-7","2-7","3-7","4-7","5-7","6-7","7-7","8-7","9-7","10-7","11-7","12-7","13-7","14-7","15-7","16-7","17-7","18-7","19-7","20-7","21-7","22-7","23-7","24-7","25-7","26-7","27-7","28-7","29-7","30-7","31-7","1-8","2-8","3-8","4-8","5-8","6-8","7-8","8-8","9-8","10-8","11-8","12-8","13-8","14-8","15-8","16-8","17-8","18-8","19-8","20-8","21-8","22-8","23-8","24-8","25-8","26-8","27-8","28-8","29-8","30-8","31-8","1-9","2-9","3-9","4-9","5-9","6-9","7-9","8-9","9-9","10-9","11-9","12-9","13-9","14-9","15-9","16-9","17-9","18-9","19-9","20-9","21-9","22-9","23-9","24-9","25-9","26-9","27-9","28-9","29-9","30-9","1-10","2-10","3-10","4-10","5-10","6-10","7-10","8-10","9-10","10-10","11-10","12-10","13-10","14-10","15-10","16-10","17-10","18-10","19-10","20-10","21-10","22-10","23-10","24-10","25-10","26-10","27-10","28-10","29-10","30-10","31-10","1-11","2-11","3-11","4-11","5-11","6-11","7-11","8-11","9-11","10-11","11-11","12-11","13-11","14-11","15-11","16-11","17-11","18-11","19-11","20-11","21-11","22-11","23-11","24-11","25-11","26-11","27-11","28-11","29-11","30-11","1-12","2-12","3-12","4-12","5-12","6-12","7-12","8-12","9-12","10-12","11-12","12-12","13-12","14-12","15-12","16-12","17-12","18-12","19-12","20-12","21-12","22-12","23-12","24-12","25-12","26-12","27-12","28-12","29-12","30-12","31-12"]

View File

@@ -1,20 +0,0 @@
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

@@ -1,203 +0,0 @@
// 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;
});
}
}

View File

@@ -1,57 +0,0 @@
import { Client, ColorResolvable, EmbedBuilder, Message, TextChannel } from "discord.js";
import axios from 'axios'
import dayjs from "dayjs";
import timezone from 'dayjs/plugin/timezone.js'
import utc from 'dayjs/plugin/utc.js'
import https from 'node:https'
export default async function minecraftstatus(client: Client) {
dayjs.extend(utc)
dayjs.extend(timezone)
let request
try {
const req = await axios.get('https://api.minetools.eu/ping/minecraft.maraturing.com/25565', {
httpsAgent: new https.Agent({ rejectUnauthorized: false })
});
request = req.data;
} catch (error) {
return;
}
const fetchMsg = await (await client.channels.fetch('1063944267258662922')! as TextChannel).messages.fetch('1063950406474010674') as Message
let onlineorelse: string
let colorcode: ColorResolvable
if (request.error) {
onlineorelse = 'Offline'
colorcode = '#8B0000'
} else {
onlineorelse = 'Online'
colorcode = 'Green'
}
let embed = new EmbedBuilder()
.setThumbnail('https://api.minetools.eu/favicon/minecraft.maraturing.com/25565')
.setColor(colorcode)
.setTitle('Estado del servidor')
.setFooter({ text: `Última actualización: ${dayjs().tz('Europe/Madrid').format('DD/MM/YYYY HH:mm:ss')}` })
if (onlineorelse === 'Offline') {
embed = embed.setFields(
{ name: 'Estado', value: onlineorelse, inline: true }
)
} else {
embed = embed.setFields(
{ name: 'Estado', value: onlineorelse, inline: true },
{ name: 'Ping', value: parseInt(request.latency.toString()).toString(), inline: true },
{ name: '\u200B', value: '\u200B', inline: true },
{ name: 'Jugadores online', value: request.players.online.toString(), inline: true },
{ name: 'Jugadores máximos', value: request.players.max.toString(), inline: true },
)
}
await fetchMsg.edit({
content: '',
embeds: [embed]
})
}

View File

@@ -1,32 +0,0 @@
import axios from "axios";
import { Client, EmbedBuilder, TextChannel } from "discord.js";
export async function nowPlayingRadio(client: Client) {
const getAPI = await axios.get("https://opml.radiotime.com/Describe.ashx?id=s67006", {validateStatus: function (status) {return status === 200|| status === 403}}).then((res) => res.data).catch((err) => {console.log("now playing radio errored out? diesofcringe")})
var parser = new DOMParser()
var XMLDoc = parser.parseFromString(getAPI, "text/xml");
let getsong, getartist;
try {
getsong = XMLDoc.getElementsByTagName("current_song").item(0)!.textContent
getartist = XMLDoc.getElementsByTagName("current_artist").item(0)!.textContent
} catch (err) {
getsong = "Anuncios o cambio de canción"
getartist = "catJAM"
}
const embed = new EmbedBuilder()
.setColor("Blurple")
.setTitle(`Ahora reproduciendo: ${getsong}`)
.setAuthor({name: 'Rock FM', iconURL: 'https://cdn-profiles.tunein.com/s67006/images/logoq.png'})
.setDescription(`Artista: ${getartist}`)
.setFooter({text: `El nombre no cambia al instante, aparece 10 segundos después de terminar una canción.`})
const guild = await client.guilds.fetch("928018226330337280");
const channel = await guild.channels.fetch("1008730592835281009");
const edit = await (channel as TextChannel).messages.fetch("1008778179252596736")
await edit.edit({content: '', embeds: [embed]})
}
function nowPlayingInterval() {
setInterval(nowPlayingRadio, 4000)
}
nowPlayingInterval()

View File

@@ -1,9 +0,0 @@
export function random(length: number) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}

View File

@@ -1,20 +0,0 @@
export default function removeURLParameter(url: string, parameter: string) {
//prefer to use l.search if you have a location/link object
var urlparts = url.split('?');
if (urlparts.length >= 2) {
var prefix = encodeURIComponent(parameter) + '=';
var pars = urlparts[1].split(/[&;]/g);
//reverse iteration as may be destructive
for (var i = pars.length; i-- > 0;) {
//idiom for string.startsWith
if (pars[i].lastIndexOf(prefix, 0) !== -1) {
pars.splice(i, 1);
}
}
return urlparts[0] + (pars.length > 0 ? '?' + pars.join('&') : '');
}
return url;
}

View File

@@ -1,88 +0,0 @@
import {
Collection,
GuildBasedChannel,
GuildMember,
Role,
User,
} from 'discord.js';
import type { ChatInputCommandInteraction, Snowflake } from 'discord.js';
/**
* It resolves mentions from the content of a command
* @example
* ```ts
* const resolved = new Resolver(content, interaction);
* console.log(resolved.users); // Collection [Map] of users
* ```
*/
export class Resolver {
public constructor(
private readonly content: string,
private readonly interaction: ChatInputCommandInteraction
) {}
readonly #regex = {
Channel: /<#(?<id>\d{17,20})>/g,
Role: /<@&(?<id>\d{17,20})>/g,
User: /<@!?(?<id>\d{17,20})>/g,
};
private getIds(mentionType: 'Channel' | 'Role' | 'User'): string[] {
const matches = this.content.matchAll(this.#regex[mentionType]);
return Array.from(matches)
.map((match) => match.groups?.id)
.filter(Boolean) as string[];
}
/**
* Resolves a user from the content.
* @returns The collection of resolved {@link User users}.
*/
public get users(): Readonly<Collection<Snowflake, User>> {
const users = this.getIds('User')
.map((id) => this.interaction.client.users.cache.get(id))
.filter(Boolean)
.map((u) => [u!.id, u]) as [Snowflake, User][];
return new Collection<Snowflake, User>(users);
}
/**
* Resolves a member from the content.
* @returns The collection of resolved {@link GuildMember members}.
*/
public get members(): Readonly<Collection<Snowflake, GuildMember>> {
const members = this.getIds('User')
.map((id) => this.interaction.guild?.members.cache.get(id))
.filter(Boolean)
.map((m) => [m!.id, m]) as [Snowflake, GuildMember][];
return new Collection<Snowflake, GuildMember>(members);
}
/**
* Resolves a channel from the content.
* @returns The collection of resolved {@link GuildBasedChannel channels}.
*/
public get channels(): Readonly<Collection<Snowflake, GuildBasedChannel>> {
const channels = this.getIds('Channel')
.map((id) => this.interaction.guild?.channels.cache.get(id))
.filter(Boolean)
.map((c) => [c!.id, c]) as [Snowflake, GuildBasedChannel][];
return new Collection<Snowflake, GuildBasedChannel>(channels);
}
/**
* Resolves a role from the content.
* @returns The collection of resolved {@link Role roles}.
*/
public get roles(): Readonly<Collection<Snowflake, Role>> {
const roles = this.getIds('Role')
.map((id) => this.interaction.guild?.roles.cache.get(id))
.filter(Boolean)
.map((r) => [r!.id, r]) as [Snowflake, Role][];
return new Collection<Snowflake, Role>(roles);
}
}

View File

@@ -1,54 +0,0 @@
const port = 7271
const express = require('express');
const app = express();
const si = require('systeminformation');
const os = require('os');
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
app.get("/cpubrand", async function (req, res) {
const cpu = await si.cpu()
res.send(`${cpu.manufacturer} ${cpu.brand}`)
})
app.get("/cpucores", async function (req, res) {
const cpu = await si.cpu()
res.send(`${cpu.cores}`)
})
app.get("/cpucores", async function (req, res) {
const cpu = await si.cpuTemperature()
res.send(`${cpu.main}`)
})
app.get("/ramtotal", async function (req, res) {
const ram = await si.mem()
res.send(`${formatBytes(ram.total)}`)
})
app.get("/ramfree", async function (req, res) {
const ram = await si.mem()
res.send(`${formatBytes(ram.free)}`)
})
app.get("/dockertotal", async function (req, res) {
const docker = await si.dockerInfo()
res.send(`${docker.containers}`)
})
app.get("/uptime", async function (req, res) {
const uptime = os.uptime()
res.send(`${uptime}`)
})
// start the server listening for requests
app.listen(port, () => {console.log("The webserver is listening on port " + port)});

View File

@@ -1,14 +0,0 @@
import express from 'express'
export default function webserver() {
const app = express()
app.get('/', function (req, res) {
res.send(
'<p>This is the monitoring server for the Vinci discord bot!</p><br><p>If you see this, the bot is up and running.</p>'
);
});
app.listen(process.env.PORT || 7272, () =>
console.log('The webserver is listening')
);
}

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