Ausnahme gefangen: SSL certificate problem: certificate is not yet valid ๐Ÿ“Œ How to create a dynamic AI Discord bot with TypeScript

๐Ÿ  Team IT Security News

TSecurity.de ist eine Online-Plattform, die sich auf die Bereitstellung von Informationen,alle 15 Minuten neuste Nachrichten, Bildungsressourcen und Dienstleistungen rund um das Thema IT-Sicherheit spezialisiert hat.
Ob es sich um aktuelle Nachrichten, Fachartikel, Blogbeitrรคge, Webinare, Tutorials, oder Tipps & Tricks handelt, TSecurity.de bietet seinen Nutzern einen umfassenden รœberblick รผber die wichtigsten Aspekte der IT-Sicherheit in einer sich stรคndig verรคndernden digitalen Welt.

16.12.2023 - TIP: Wer den Cookie Consent Banner akzeptiert, kann z.B. von Englisch nach Deutsch รผbersetzen, erst Englisch auswรคhlen dann wieder Deutsch!

Google Android Playstore Download Button fรผr Team IT Security



๐Ÿ“š How to create a dynamic AI Discord bot with TypeScript


๐Ÿ’ก Newskategorie: Programmierung
๐Ÿ”— Quelle: dev.to

Learn how to create your own AI Discord bot (with command and event handling) that can be dynamically configurable through each guild.

Concepts that will be explored throughout this tutorial

Getting started

Project initialization

  • Create an empty folder for your project and initialize it (for this project, I'll be using pnpm, but feel free to use whatever you prefer):
pnpm init
  • Install the dependencies and dev dependencies we'll be using to get started:
pnpm add -D typescript ts-node
pnpm add discord.js nodemon dotenv mongoose openai
  • Now let's set this up as a typescript project:
tsc --init
  • And make sure it has our specific configurations:
{
  "compilerOptions": {
      "lib": [
          "ESNext"
      ],
      "module": "CommonJS",
      "moduleResolution": "node",
      "target": "ESNext",
      "outDir": "dist",
      "sourceMap": false,
      "resolveJsonModule": true,
      "esModuleInterop": true,
      "experimentalDecorators": true,
      "emitDecoratorMetadata": true,
      "allowSyntheticDefaultImports": true,
      "skipLibCheck": true,
      "skipDefaultLibCheck": true,
      "importHelpers": true,
  },
  "include": [
      "src/**/*",
      "environment.d.ts"
  ],
  "exclude": [
      "node_modules",
      "**/*.spec.ts"
  ]
}
  • And let's add these scripts to our package.json so we can run the project:
"scripts": {
    "start": "ts-node src/index.ts",
    "start:dev": "ts-node-dev src/index.ts",
    "start:prod": "node dist/index.js",
    "dev": "nodemon ./src/index.ts",
    "build": "tsc",
    "watch": "tsc -w"
  },
  • Time to create a ~/src/index.ts file and test that our project runs properly:
console.log("Hello World");
  • If we run pnpm dev and see Hello World in the console, it seems like our project environment is ready!

Getting our .env variables

Discord

  • Navigate to the Discord Developer Portal
  • Create a new application
  • Reset and copy the Bot's token and add it to your .env
  • Make sure to enable the necessary presence intents that you'd like your Discord bot to be able to access.

OpenAI

  • Navigate to your OpenAI API Keys
  • Create a new API key and add it to your .env
  • Then copy your organization ID and add it as well (found here

MongoDB

  • Navigate to MongoDB Cloud
  • Create a new project and database then click Connect
    • Click Drivers
    • Copy your connection URI
    • Replace <password> with your password
  • Add the URI to your .env

  • Create a ~/src/lib/db.ts file which will contain your MongoDB connection:

import "colors";
import mongoose from "mongoose";

const mongoURI = process.env.MONGO_URI;

const db = async () => {
    if (!mongoURI) {
        console.log(`[WARNING] Missing MONGO_URI environment variable!`.bgRed);
    }

    mongoose.set("strictQuery", true);

    try {
        if (await mongoose.connect(mongoURI)) {
            console.log(`[INFO] Connected to the database!`.bgCyan);
        }
    } catch (err) {
        console.log(`[ERROR] Couldn't establish a MongoDB connection!\n${err}`.red);
    }
}

export default db;

Optional: install colors to add some color to your console.log's

Discord bot setup

We will need to set up a couple of folders and structures that will manage our Discord bot's events and commands.

Utility

  • Create a utils/ folder within your src/ directory which will contain multiple utility functions to help the management of our Discord bot.

We are going to create a couple utility files that will be useful within this project; but to start, we need a function that can read files within folders.

import fs from "fs";
import path from "path";

/* 
    this function will accept 2 (one is optional) parameters:
    (1) the directory of which to read the files
    (2) if the function should read folders only, which we'll set as false by default
*/

const getFiles = (directory: string, foldersOnly = false) => {
    let fileNames = [];

    const files = fs.readdirSync(directory, { withFileTypes: true });

    for (const file of files) {
        const filePath = path.join(directory, file.name);

        if (foldersOnly) {
            if (file.isDirectory()) {
                fileNames.push(filePath);
            }
        } else {
            if (file.isFile()) {
                fileNames.push(filePath);
            }
        }
    }

    return fileNames;
}

export default getFiles;

Handler(s)

  • Create a /handlers/index.ts within your src/ directory:
    • For now, we will just add this eventHandler() function, but this can be expanded later for your needs.

This function will accept a discord Client parameter which will then read and register events that will be located within an events/ folder

import { Client } from "discord.js";
import path from "path";
import getFiles from "../utils/getFiles";

const eventHandler = (client: Client) => {
    const eventFolders = getFiles(path.join(__dirname, "..", "events"), true);

    for (const eventFolder of eventFolders) {
        const eventFiles = getFiles(eventFolder);

        let eventName: string;

        eventName = eventFolder.replace(/\\/g, '/').split("/").pop();

        eventName === "validations" ? (eventName = "interactionCreate") : eventName;

        client.on(eventName, async (args) => {
            for (const eventFile of eventFiles) {
                const eventFunction = require(eventFile);
                await eventFunction(client, args);
            }
        })
    }
}

export default eventHandler;

Events

Now that we've established a function that can read and register events for the bot, let's set up some events we want to listen for.

Firstly we'll want our bot to listen for the ready event, if you've ever seen:

client.on("ready", () => {};

This is exactly what we're setting up.

  • Create a ready/ folder within events/. Then inside this folder, we can put a file for each function we want to run when the bot is ready.
    • To start, I want the bot to console.log() when it's ready, so I'm going to create a consoleLog.ts file:
import "colors";
import { Client } from "discord.js";

module.exports = (client: Client) => {
    console.log(`[INFO] ${client.user.username} is online!`.bgCyan);
}

IMPORTANT:

When exporting these functions so that they're registered, we need to use module.exports since our eventHandler() function uses require()

Before continuing, we should now test and see if our bot will listen for this event:

  • Navigate to your src/index.ts file and register events to your bot:
import { config } from "dotenv";
import { Client, GatewayIntentBits } from "discord.js";
import eventHandler from "@/handlers";

config() // Load environment variables

const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.GuildMembers
    ] // Specify all the intents you wish your bot to access
});

eventHandler(client) // Register events

client.login(process.env.BOT_TOKEN); // Login to the bot
  • If you run pnpm dev and see the console log, it looks like everything is working properly.

Let's also connect to the database whenever the bot is ready.

  • Add a dbConnect.ts file to events/ready
import "colors";
import db from "../../lib/db";

module.exports = async () => {
    await db().catch((err) => console.log(`[ERROR] Error connecting to database! \n${err}`.red));
}

Obviously, you're gonna want to have the ability to create/delete/edit commands. So, if we're gonna keep commands in, say, a commands/ folder, let's create some utility functions that can gather those for us.

Commands

  • Create a file utils/getCommands.ts where we're going to have 2 essential functions getApplicationCommands() and getLocalCommands()
    • getApplicationCommands() this function will find the commands that are already registered to the bot.
    • getLocalCommands() this function will fetch the commands from the commands/ folder.

Get commands

import { ApplicationCommandManager, Client, GuildApplicationCommandManager } from "discord.js";
import path from "path";
import getFiles from "./getFiles";


const getApplicationCommands = async (client: Client, guildId?: string) => {
    let applicationCommands: GuildApplicationCommandManager | ApplicationCommandManager;

    if (guildId) { // if registering to a specific guild
        const guild = await client.guilds.fetch(guildId);
        applicationCommands = guild.commands;
    } else {
        applicationCommands = client.application.commands;
    }

    await applicationCommands.fetch({
        guildId: guildId
    });

    return applicationCommands;
}

const getLocalCommands = (exceptions = []) => {
    let localCommands = [];

    const commandCategories = getFiles(path.join(__dirname, "..", "commands"), true);

    for (const commandCategory of commandCategories) {
        const commandFiles = getFiles(commandCategory);

        for (const commandFile of commandFiles) {
            const commandObject = require(commandFile);

            if (exceptions.includes(commandObject.name)) continue;
            localCommands.push(commandObject);
        }
    }

    return localCommands;
}

export {
    getApplicationCommands,
    getLocalCommands
};

Command type

Suppose we want our commands to look like:

import { PermissionsBitField, SlashCommandBuilder } from "discord.js";

const ping = {
    data: new SlashCommandBuilder()
        .setName("ping")
        .setDescription("Pong!")
        .addUserOption((option) => option
            .setName("user")
            .setDescription("The user you want to ping")
    ),
    userPermissions: [PermissionsBitField.Flags.SendMessages], // array of permissions the user needs to execute the command
    botPermissions: [PermissionsBitField.Flags.SendMessages], // array of permissions the bot needs to execute the command
    run: async (client, interaction) => {
        // run the command
    }
}

module.exports = ping;

Since we're using TypeScript, let's go ahead and create a type for our commands:

import { ChatInputCommandInteraction, Client, RESTPostAPIChatInputApplicationCommandsJSONBody, SlashCommandBuilder, SlashCommandSubcommandsOnlyBuilder } from "discord.js";

export type SlashCommand = {
    data: RESTPostAPIChatInputApplicationCommandsJSONBody | Omit<SlashCommandBuilder, "addSubcommandGroup" | "addSubcommand">
    | SlashCommandSubcommandsOnlyBuilder;
    userPermissions: Array<bigint>;
    botPermissions: Array<bigint>;
    run: (client: Client, interaction: ChatInputCommandInteraction) => Promise<any>;
}

Alright, we're ALMOST ready to create an event that'll handle registering commands...

But, unless you wanna re-register every command from the commands folder every time the bot is online, we'll need some sort of function that's going to compare the locally existing commands (commands/) to the commands that have been already registered to the bot.

Command compare

  • Create a file within utils/ that will hold our commandCompare() function

commandCompare()

import { ApplicationCommand } from "discord.js";
import { SlashCommand } from "./types";

const commandCompare = (existing: ApplicationCommand, local: SlashCommand) => {
    const changed = (a, b) => JSON.stringify(a) !== JSON.stringify(b);

    if (changed(existing.name, local.data.name) || changed(existing.description, local.data.description)) {
        return true;
    }

    function optionsArray(cmd) {
        const cleanObject = obj => {
            for (const key in obj) {
                if (typeof obj[key] === 'object') {
                    cleanObject(obj[key]);

                    if (!obj[key] || (Array.isArray(obj[key]) && obj[key].length === 0)) {
                        delete obj[key];
                    }
                } else if (obj[key] === undefined) {
                    delete obj[key];
                }
            }
        };

        const normalizedObject = (input) => {
            if (Array.isArray(input)) {
                return input.map((item) => normalizedObject(item));
            }

            const normalizedItem = {
                type: input.type,
                name: input.name,
                description: input.description,
                options: input.options ? normalizedObject(input.options) : undefined,
                required: input.required
            }

            return normalizedItem;
        }

        return (cmd.options || []).map((option) => {
            let cleanedOption = JSON.parse(JSON.stringify(option));
            cleanedOption.options ? (cleanedOption.options = normalizedObject(cleanedOption.options)) : (cleanedOption = normalizedObject(cleanedOption));
            cleanObject(cleanedOption);
            return {
                ...cleanedOption,
                choices: cleanedOption.choices ? JSON.stringify(cleanedOption.choices.map((c) => c.value)) : null
            }
        })
    }

    const optionsChanged = changed(optionsArray(existing), optionsArray(local.data));

    return optionsChanged;
}

export default commandCompare;

Validations (interactionCreate)

Circling back to the eventHandler(), do you remember this line:

eventName === "validations" ? (eventName = "interactionCreate") : eventName;

This was intended so we can validate the commands. We'll add a file within events validations/command.ts which will attempt to notify users if the bot and/or the user has insufficient permissions to use the command, or otherwise run the command.

validations

import "colors";
import { Client, ColorResolvable, CommandInteraction, EmbedBuilder, Colors } from "discord.js";
import { getLocalCommands } from "../../utils/getCommands";
import { SlashCommand } from "../../utils/types";

module.exports = async (client: Client, interaction: CommandInteraction) => {

    if (!interaction.isChatInputCommand()) return;

    const localCommands = getLocalCommands();
    const commandObject: SlashCommand = localCommands.find((cmd: SlashCommand) => cmd.data.name === interaction.commandName);

    if (!commandObject) return;

    const createEmbed = (color: string | ColorResolvable, description: string) => new EmbedBuilder()
        .setColor(color as ColorResolvable)
        .setDescription(description);

    for (const permission of commandObject.userPermissions || []) {
        if (!interaction.memberPermissions.has(permission)) {
            const embed = createEmbed(Colors.Red, "You do not have permission to execute this command!");

            return await interaction.reply({ embeds: [embed], ephemeral: true });
        }
    }

    const bot = interaction.guild.members.me;

    for (const permission of commandObject.botPermissions || []) {
        if (!bot.permissions.has(permission)) {
            const embed = createEmbed(Colors.Red, "I don't have permission to execute this command!");

            return await interaction.reply({ embeds: [embed], ephemeral: true });
        }
    }

    try {
        await commandObject.run(client, interaction);
    } catch (err) {
        console.log(`[ERROR] An error occured while validating commands!\n ${err}`.red);
        console.error(err);
    }
}

Register commands

Now we can add an event within events/ready/ that will register (add, delete, edit) the commands!

registerCommands()

import "colors";
import { Client } from "discord.js";
import commandCompare from "../../utils/commandCompare";
import { getApplicationCommands, getLocalCommands } from "../../utils/getCommands";

module.exports = async (client: Client) => {
    try {

        const [localCommands, applicationCommands] = await Promise.all([
            getLocalCommands(),
            getApplicationCommands(client)
        ]);

        for (const localCommand of localCommands) {
            const { data, deleted } = localCommand;
            const { name: commandName, description: commandDescription, options: commandOptions } = data;

            const existingCommand = applicationCommands.cache.find((cmd) => cmd.name === commandName);

            if (deleted) {
                if (existingCommand) {
                    await applicationCommands.delete(existingCommand.id);
                    console.log(`[COMMAND] Application command ${commandName} has been deleted!`.grey);
                } else {
                    console.log(`[COMMAND] Application command ${commandName} has been skipped!`.grey);
                }
            } else if (existingCommand) {
                if (commandCompare(existingCommand, localCommand)) {
                    await applicationCommands.edit(existingCommand.id, {
                        name: commandName, description: commandDescription, options: commandOptions
                    });
                    console.log(`[COMMAND] Application command ${commandName} has been edited!`.grey);
                }
            } else {
                await applicationCommands.create({
                    name: commandName, description: commandDescription, options: commandOptions
                });
                console.log(`[COMMAND] Application command ${commandName} has been registered!`.grey);
            }
        }

    } catch (err) {
        console.log(`[ERROR] There was an error inside the command registry!\n ${err}`.red);
    }
}

Creating commands

It's time to create the first command to see if it registers when our bot is ready.

NOTE:

You can create sub-folders for categories of commands.

commands/misc/ping.ts
import { SlashCommand } from "../../utils/types";
import { EmbedBuilder, SlashCommandBuilder, userMention, Colors } from "discord.js";

const ping: SlashCommand = {
    data: new SlashCommandBuilder()
        .setName("ping")
        .setDescription("Ping a user")
        .setDMPermission(false)
        .addUserOption((option) => option
            .setName("user")
            .setDescription("The user you wish to ping")
            .setRequired(true),
    ),
    userPermissions: [],
    botPermissions: [],
    run: async (client, interaction) => {
        const options = interaction.options;
        const target = options.getUser("user");

        const embed = new EmbedBuilder()
            .setDescription(userMention(target.id))
            .setColor(Colors.Default)

        return await interaction.reply({ embeds: [embed] });
    },
}

module.exports = ping;

You should be able to start up your bot and see:

commands registered

And if I run the command:

ping command

We're ready to start implementing the key features!

AI (OpenAI)

  • Create a file inside your lib/ directory to hold your OpenAI object:
import { OpenAI } from "openai";

const openai = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY,
    organization: process.env.OPENAI_ORGANIZATION_ID,
});

export default openai;
  • Create another file for your OpenAI query:

query()

import openai from "./openai";

/*
    a sleep function to make sure the AI gets a good night's rest before it has to get back to work
*/
function sleep(ms: number) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

const query = async (prompt: string, guildId: string) => {

    /* 
        the `guildId` paramater will come in handy later when we want the bot to respond dynamically based on the guild's settings
    */

    if (!prompt || prompt.length < 1) return false;

    /*
        this variable directs the AI how to respond.
        it will be made dynamic later (along with all the other configurations)
    */
    const tempSystemRoleContent = "Respond to the given prompt in a funny and/or witty way."

    const res = await openai.chat.completions.create({
        model: "gpt-3.5-turbo",
        messages: [
            {
                role: "system",
                content: tempSystemRoleContent
            },
            {
                role: "user",
                content: prompt
            }
        ],
        temperature: 0.86,
        presence_penalty: 0
    })
        .then((res) => res.choices[0].message)
        .catch((err) => `Error with query!\n${err}`);

    await sleep(1000);

    if (typeof res === 'object') {
        return res.content;
    }

    return res;
}

export default query;

  • Create a command just to test that the AI is working, I'm just going to call it /ai

/ai

import query from "../../lib/query";
import { SlashCommand } from "../../utils/types";
import { SlashCommandBuilder } from "discord.js";

const ai: SlashCommand = {
    data: new SlashCommandBuilder()
        .setName("ai")
        .setDescription("Say or ask something to an AI")
        .addStringOption((option) => option
            .setName("prompt")
            .setDescription("The prompt to give")
            .setRequired(true)
            .setMinLength(5)
            .setMaxLength(500)
    ),
    userPermissions: [],
    botPermissions: [],
    run: async (client, interaction) => {
        const { guildId } = interaction;

        if (!interaction.isCommand()) return;

        const prompt = interaction.options.getString("prompt");

        // defer the reply to give the openai query time
        await interaction.deferReply().catch(() => null)

        const response = await query(prompt, guildId);

        if (response === undefined || response === null || !response) {
            return await interaction.editReply({ content: "An error occured" })
        }
        if (interaction.replied) {
            return;
        }
        if (interaction.deferred) {
            return await interaction.editReply({ content: response });
        }

        return;
    }
}

module.exports = ai;

And as you can see, this should work for you with absolutely no errors:

ai command gif

Mongoose

When setting up the query() from before, we manually passed in certain options like:

Instead of having these variables set in stone, we're going to allow administrators of guilds to alter the settings. To do that, we need to be able to store a guild's settings within a database.

Firstly I'm going to envision the type of data to work with by creating a model.

models/guild.ts
import { model, Schema } from "mongoose";

const guildSchema = new Schema({
    GuildID: String,
    SystemRoleContent: String,
    Temperature: Number,
    PresencePenalty: Number,
}, { strict: false });

export default model("guild", guildSchema);

Then I set up a configuration file which contains the default settings to use, while adding logic to the query() function to check for a guild's settings:

let guildData = await guild.findOne({ GuildID: guildId });

    if (!guildData) {
        guildData = new guild({
            GuildID: guildId,
            Temperature: config.openai.temperature,
            SystemRoleContent: config.openai.systemRoleContent,
            PresencePenalty: config.openai.presence_penalty,
            Model: config.openai.model,
        });

        await guildData.save();
    }

Then use those values within the chat completion function:

await openai.chat.completions.create({
        model: guildData.Model,
        messages: [
            {
                role: "system",
                content: guildData.SystemRoleContent
            },
            {
                role: "user",
                content: prompt
            }
        ],
        temperature: guildData.Temperature,
        presence_penalty: guildData.PresencePenalty
    })

Finally, create a command in which:

  • Only admins can use
  • Has sub commands:
    • /settings view
    • /settings config
    • /settings reset

/settings command

  • Only admins can use
userPermissions: [PermissionsBitField.Flags.Administrator]
  • Has sub commands:
.addSubcommand((sub) => sub
            .setName("reset")
            .setDescription("Reset your guild to default settings")
        )

For configuration options, make sure to add the parameters you want to the ability to be altered:

  • presence_penalty
.addNumberOption((option) => option
                .setName("presence_penalty")
                .setDescription("How diverse the responses are")
                .setMinValue(-2)
                .setMaxValue(2)
            )

run()

Get the sub command used & the guild, then fetch the guild's data model

const { options, guildId } = interaction;

        const subCommand = options.getSubcommand(true);

        if (!["config", "view", "reset", "help"].includes(subCommand)) return;
import Guild from "../../models/guild";
// ...

let guildData = await Guild.findOne({
            GuildID: guildId
        });

        if (!guildData) {
            guildData = new Guild({
                GuildID: guildId,
                // ...
            });

            await guildData.save();
        }

Then you can configure the guild's settings by utilizing .updateOne():

await guildData.updateOne({ /* 
*/
})

View the complete code for the way I set up my settings command here.

You now have a customizable AI bot!

Ideas to expand

  • Apply additional functionality to check for models/configure the model.
  • Integrate into a website (nextjs?)
  • Create a custom authentication page
  • Set up logic so the default settings change based on prompts
  • ...

Thank you for reading my first post on here! The full code can be found on my github here.

The bot is live and running now on an AWS instance. There are steps listed in the repository's readme with how to go about getting your bot live at all times.

Feel free to make any issues and/or pull requests, or leave feedback with any thoughts!

...



๐Ÿ“Œ How to create a dynamic AI Discord bot with TypeScript


๐Ÿ“ˆ 52.36 Punkte

๐Ÿ“Œ How to Create a Discord Bot on Arch Linux


๐Ÿ“ˆ 29.14 Punkte

๐Ÿ“Œ How to Create a Discord Bot on Arch Linux [Tutorial]


๐Ÿ“ˆ 29.14 Punkte

๐Ÿ“Œ Create a Discord bot with NodeJS


๐Ÿ“ˆ 29.14 Punkte

๐Ÿ“Œ How to Create a Discord Bot to Get GitHub Repository Issues


๐Ÿ“ˆ 29.14 Punkte

๐Ÿ“Œ How to create a discord bot with javascript


๐Ÿ“ˆ 29.14 Punkte

๐Ÿ“Œ How to Create a Music Bot Using Discord.js โ€“ Step-by-Step Tutorial


๐Ÿ“ˆ 29.14 Punkte

๐Ÿ“Œ 10 typescript developers you must follow to become typescript expert in 2024


๐Ÿ“ˆ 24.3 Punkte

๐Ÿ“Œ I made "TypeScript Swagger Editor", new type of Swagger UI writing TypeScript code in the browser


๐Ÿ“ˆ 24.3 Punkte

๐Ÿ“Œ How Types Work in TypeScript โ€“ Explained with JavaScript + TypeScript Code


๐Ÿ“ˆ 24.3 Punkte

๐Ÿ“Œ Introduction to TypeScript โ€” What is TypeScript?


๐Ÿ“ˆ 24.3 Punkte

๐Ÿ“Œ Dynamic return type based on input parameter in TypeScript like Prisma


๐Ÿ“ˆ 23.22 Punkte

๐Ÿ“Œ Creating Dynamic Forms with React, Typescript, React Hook Form and Zod


๐Ÿ“ˆ 23.22 Punkte

๐Ÿ“Œ TypeScript Index Signatures: 4 Examples Type-Safe Dynamic Objects


๐Ÿ“ˆ 23.22 Punkte

๐Ÿ“Œ How to Build a Telegram Bot using Typescript & Node.js


๐Ÿ“ˆ 22.89 Punkte

๐Ÿ“Œ Introduction to the ELF Format (Part VII): Dynamic Linking / Loading and the .dynamic section


๐Ÿ“ˆ 22.13 Punkte

๐Ÿ“Œ Medium CVE-2017-18604: Sitebuilder dynamic components project Sitebuilder dynamic components


๐Ÿ“ˆ 22.13 Punkte

๐Ÿ“Œ Dynamic Cow: App bringt Dynamic Island auf alte iPhones


๐Ÿ“ˆ 22.13 Punkte

๐Ÿ“Œ CVE-2023-31032 | NVIDIA DGX A100 prior 1.25 dynamic dynamic variable evaluation


๐Ÿ“ˆ 22.13 Punkte

๐Ÿ“Œ Discord: Eigener Spiele-Shop gestartet; fรผnf Titel im Programm "First on Discord"


๐Ÿ“ˆ 21.9 Punkte

๐Ÿ“Œ 7 Best Voice Changer For Discord In 2020|Discord Voice Changer Apps


๐Ÿ“ˆ 21.9 Punkte

๐Ÿ“Œ New infosec discord come join the discussion | Discord hacking server


๐Ÿ“ˆ 21.9 Punkte

๐Ÿ“Œ Discord TTS Not Working: How to Fix Text-to-Speech on Discord


๐Ÿ“ˆ 21.9 Punkte

๐Ÿ“Œ Discord TTS Not Working: How to Fix Text-to-Speech on Discord


๐Ÿ“ˆ 21.9 Punkte

๐Ÿ“Œ Miscreants aim to cause Discord discord with malicious npm packages


๐Ÿ“ˆ 21.9 Punkte

๐Ÿ“Œ How To Cross Out Text In Discord: Discord Crossout


๐Ÿ“ˆ 21.9 Punkte

๐Ÿ“Œ Why Discord Is Betting Big on Devs With Anjney Midha, VP of Platform Ecosystem at Discord


๐Ÿ“ˆ 21.9 Punkte

๐Ÿ“Œ Discord Webhook: So erstellst du einen Discord Webhook


๐Ÿ“ˆ 21.9 Punkte

๐Ÿ“Œ Discord mit Auth Code: So geht die Zwei-Faktor-Authentifizierung fรผr Discord


๐Ÿ“ˆ 21.9 Punkte

๐Ÿ“Œ Discord Pings: How to Ping on Discord


๐Ÿ“ˆ 21.9 Punkte

๐Ÿ“Œ Discord-Bot erstellen: So kreiert ihr automatisierte Programme fรผr euren Server


๐Ÿ“ˆ 21.69 Punkte

๐Ÿ“Œ Discord: Bot erstellen - eine Anleitung


๐Ÿ“ˆ 21.69 Punkte

๐Ÿ“Œ Red Discord Bot up to 3.3.11 Strams Messages code injection


๐Ÿ“ˆ 21.69 Punkte

๐Ÿ“Œ Discord-Bot erstellen: So kreiert ihr automatisierte Programme fรผr euren Server


๐Ÿ“ˆ 21.69 Punkte

๐Ÿ“Œ Medium CVE-2020-15278: Cogboard Red discord bot


๐Ÿ“ˆ 21.69 Punkte











matomo