commit 11a100eacf27f1a2a623c42df504702bad2ec1f1 Author: Ignacio PS Date: Tue Apr 15 16:15:56 2025 -0600 First commit for dscrdbt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32dff42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Dependencies +node_modules/ +package-lock.json +yarn.lock + +# Environment variables +.env +.env.local +.env.*.local + +# Build output +dist/ +build/ +out/ + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo +*.swn +.DS_Store + +# Runtime data +pids/ +*.pid +*.seed +*.pid.lock + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity diff --git a/commands/instagram/aipost.js b/commands/instagram/aipost.js new file mode 100644 index 0000000..d62570c --- /dev/null +++ b/commands/instagram/aipost.js @@ -0,0 +1,59 @@ +const { SlashCommandBuilder, ActionRowBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js'); +const fs = require('fs'); +const path = require('path'); + +// Configuration +const ALLOWED_CHANNEL_ID = '1354951205180150004'; + +module.exports = { + data: new SlashCommandBuilder() + .setName('aipost') + .setDescription('Generate an Instagram post from a news article'), + + async execute(interaction) { + // Check if the command is used in the allowed channel + if (interaction.channelId !== ALLOWED_CHANNEL_ID) { + return await interaction.reply({ + content: 'This command can only be used in the designated channel.', + ephemeral: true, + }); + } + + // Modal creation + const modal = new ModalBuilder() + .setCustomId('aipostModal') + .setTitle('News Link'); + + // Text input component + const newsLinkInput = new TextInputBuilder() + .setCustomId('newsLinkInput') + .setLabel('Please provide the news article URL') + .setStyle(TextInputStyle.Short) + .setPlaceholder('https://example.com/news-article') + .setRequired(true); + + // Add text input to the modal + const firstActionRow = new ActionRowBuilder().addComponents(newsLinkInput); + modal.addComponents(firstActionRow); + // Make the modal visible to the user + await interaction.showModal(modal); + }, + + // Handle the modal submission + async modalSubmit(interaction) { + if (interaction.customId === 'aipostModal') { + // Acknowledge the interaction + await interaction.deferReply(); + + // Placeholder for AI post generation + await interaction.editReply({ + content: 'Here is your generated Instagram post:\n\n' + + '📸 *[Generated Image Placeholder]*\n\n' + + '📝 **Generated Caption:**\n' + + 'Exciting news in tech! 🚀\n' + + 'Stay updated with the latest trends!\n\n' + + '#tech #innovation #news #trending #instagram', + }); + } + }, +}; \ No newline at end of file diff --git a/commands/utility/ping.js b/commands/utility/ping.js new file mode 100644 index 0000000..8d4963b --- /dev/null +++ b/commands/utility/ping.js @@ -0,0 +1,13 @@ +const { SlashCommandBuilder, MessageFlags } = require('discord.js'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('ping') + .setDescription('Replies with Pong!'), + async execute(interaction) { + await interaction.reply({ + content: 'Secret Pong!', + flags: MessageFlags.Ephemeral + }); + }, +}; diff --git a/commands/utility/server.js b/commands/utility/server.js new file mode 100644 index 0000000..6cc5b4b --- /dev/null +++ b/commands/utility/server.js @@ -0,0 +1,11 @@ +const { SlashCommandBuilder } = require('discord.js'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('server') + .setDescription('Provides information about the server.'), + async execute(interaction) { + await interaction.reply(`This server is ${interaction.guild.name} and has + ${interaction.guild.memberCount} members.`); + }, +}; \ No newline at end of file diff --git a/commands/utility/user.js b/commands/utility/user.js new file mode 100644 index 0000000..e070bb9 --- /dev/null +++ b/commands/utility/user.js @@ -0,0 +1,11 @@ +const { SlashCommandBuilder } = require('discord.js'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('user') + .setDescription('Provides information about the user.'), + async execute(interaction) { + await interaction.reply(`This command was run by ${interaction.user.username}, + who joined on ${interaction.member.joinedAt}.`); + }, +}; \ No newline at end of file diff --git a/deploy-commands.js b/deploy-commands.js new file mode 100644 index 0000000..35ef475 --- /dev/null +++ b/deploy-commands.js @@ -0,0 +1,39 @@ +const { REST, Routes } = require('discord.js'); +require('dotenv').config(); +const fs = require('fs'); +const path = require('path'); + +const commands = []; +const foldersPath = path.join(__dirname, 'commands'); +const commandFolders = fs.readdirSync(foldersPath); + +for (const folder of commandFolders) { + const commandsPath = path.join(foldersPath, folder); + const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); + for (const file of commandFiles) { + const filePath = path.join(commandsPath, file); + const command = require(filePath); + if ('data' in command && 'execute' in command) { + commands.push(command.data.toJSON()) + } else { + console.log(`[WARNING] The command at ${filePath} is missing a required + "data" or "execute" property.`); + } + } +} + +const rest = new REST().setToken(process.env.DISCORD_TOKEN); + +(async () => { + try { + console.log(`Started refreshing ${commands.length} application (/) commands`); + const data = await rest.put( + Routes.applicationGuildCommands(process.env.clientId, process.env.guildId), + { body: commands }, + ); + + console.log(`Successfully reloaded ${data.length} application (/) commands.`); + } catch (error) { + console.error(error); + } +})(); \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..4a9de8d --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,50 @@ +const js = require('@eslint/js'); + +module.exports = [ + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 'latest', + }, + rules: { + 'arrow-spacing': ['warn', { before: true, after: true }], + 'brace-style': ['error', 'stroustrup', { allowSingleLine: true }], + 'comma-dangle': ['error', 'always-multiline'], + 'comma-spacing': 'error', + 'comma-style': 'error', + curly: ['error', 'multi-line', 'consistent'], + 'dot-location': ['error', 'property'], + 'handle-callback-err': 'off', + indent: ['error', 'tab'], + 'keyword-spacing': 'error', + 'max-nested-callbacks': ['error', { max: 4 }], + 'max-statements-per-line': ['error', { max: 2 }], + 'no-console': 'off', + 'no-empty-function': 'error', + 'no-floating-decimal': 'error', + 'no-inline-comments': 'error', + 'no-lonely-if': 'error', + 'no-multi-spaces': 'error', + 'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1, maxBOF: 0 }], + 'no-shadow': ['error', { allow: ['err', 'resolve', 'reject'] }], + 'no-trailing-spaces': ['error'], + 'no-var': 'error', + 'no-undef': 'off', + 'object-curly-spacing': ['error', 'always'], + 'prefer-const': 'error', + quotes: ['error', 'single'], + semi: ['error', 'always'], + 'space-before-blocks': 'error', + 'space-before-function-paren': ['error', { + anonymous: 'never', + named: 'never', + asyncArrow: 'always', + }], + 'space-in-parens': 'error', + 'space-infix-ops': 'error', + 'space-unary-ops': 'error', + 'spaced-comment': 'error', + yoda: 'error', + }, + }, +]; \ No newline at end of file diff --git a/events/interacionAiPost.js b/events/interacionAiPost.js new file mode 100644 index 0000000..3124df2 --- /dev/null +++ b/events/interacionAiPost.js @@ -0,0 +1,59 @@ +const { Events } = require('discord.js'); +const { generateCaption, generateImage, generatePostImage } = require('../utils/newsProcessor'); + +module.exports = { + name: Events.InteractionCreate, + async execute(interaction) { + if (!interaction.isModalSubmit()) return; + if (interaction.customId !== 'aipostModal') return; + + try { + const newsLink = interaction.fields.getTextInputValue('newsLinkInput'); + await interaction.deferReply(); + + // Process the article with status updates + // await interaction.editReply('📰 Reading article...'); + // const articleData = await scrapeArticle(newsLink); + + await interaction.editReply('✍️ Generating image copy...'); + const imageText = await generateImage(newsLink); + + await interaction.editReply('🎨 Creating AI image...'); + const imageUrl = await generatePostImage(imageText); + + await interaction.editReply('📝 Generating Instagram post...'); + const postText = await generateCaption(newsLink); + + // First message with the AI-generated image and its copy + await interaction.editReply({ + content: `📸 **Generated Image Copy:**\n${imageText}`, + files: [imageUrl], + }); + + // Second message with the Instagram post copy + await interaction.followUp({ + content: `📱 **Generated Instagram Post:**\n${postText}`, + }); + + } + catch (error) { + console.error('Error in modal submission:', error); + const errorMessage = error.message.includes('Failed to') + ? error.message + : 'An unexpected error occurred while generating the post.'; + + if (interaction.deferred) { + await interaction.editReply({ + content: `❌ ${errorMessage}\nPlease try again or use a different article.`, + ephemeral: true, + }); + } + else { + await interaction.reply({ + content: `❌ ${errorMessage}\nPlease try again or use a different article.`, + ephemeral: true, + }); + } + } + }, +}; \ No newline at end of file diff --git a/events/interactionCreate.js b/events/interactionCreate.js new file mode 100644 index 0000000..0323dec --- /dev/null +++ b/events/interactionCreate.js @@ -0,0 +1,33 @@ +const { Events } = require('discord.js'); + +module.exports = { + name: Events.InteractionCreate, + async execute(interaction) { + if (!interaction.isChatInputCommand()) return; + + const command = interaction.client.commands.get(interaction.commandName); + if (!command) { + console.error(`No command matching ${interaction.commandName} was found.`); + return; + } + + try { + await command.execute(interaction); + } + catch (error) { + console.error(error); + if (interaction.replied || interaction.deferred) { + await interaction.followUp({ + content: 'There was an error while executing this command', + ephemeral: true, + }); + } + else { + await interaction.reply({ + content: 'There was an error while executing this command!', + ephemeral: true, + }); + } + } + }, +}; \ No newline at end of file diff --git a/events/ready.js b/events/ready.js new file mode 100644 index 0000000..0f49430 --- /dev/null +++ b/events/ready.js @@ -0,0 +1,9 @@ +const { Events } = require('discord.js'); + +module.exports = { + name: Events.ClientReady, + once: true, + execute(client) { + console.log(`Ready! Logged in as ${client.user.tag}`); + }, +}; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..0a8a45e --- /dev/null +++ b/index.js @@ -0,0 +1,47 @@ +const { Client, GatewayIntentBits, Collection } = require('discord.js'); +const fs = require('fs'); +const path = require('path'); +require('dotenv').config(); + +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMembers, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + ], +}); + +client.commands = new Collection(); +const foldersPath = path.join(__dirname, 'commands'); +const commandFolders = fs.readdirSync(foldersPath); + +for (const folder of commandFolders) { + const commandsPath = path.join(foldersPath, folder); + const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); + for (const file of commandFiles) { + const filePath = path.join(commandsPath, file); + const command = require(filePath); + if ('data' in command && 'execute' in command) { + client.commands.set(command.data.name, command); + } else { + console.log(`[WARNING] The command at ${filePath} is missing a required "data" or + "execute" property.`); + } + } +} + +const eventsPath = path.join(__dirname, 'events'); +const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js')); + +for (const file of eventFiles) { + const filePath = path.join(eventsPath, file); + const event = require(filePath); + if (event.once) { + client.once(event.name, (...args) => event.execute(...args)); + } else { + client.on(event.name, (...args) => event.execute(...args)); + } +} + +client.login(process.env.DISCORD_TOKEN); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..72579e5 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "dscrdbt", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "start": "node src/index.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "discord.js": "^14.18.0", + "dotenv": "^16.4.7", + "openai": "^4.90.0" + }, + "devDependencies": { + "@eslint/js": "^9.23.0", + "eslint": "^9.23.0" + } +} diff --git a/utils/newsProcessor.js b/utils/newsProcessor.js new file mode 100644 index 0000000..ba618bd --- /dev/null +++ b/utils/newsProcessor.js @@ -0,0 +1,80 @@ +const OpenAI = require('openai'); + +// Initialize OpenAI client +const openai = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, +}); + +// Function to generate Instagram caption +async function generateCaption(articleData) { + try { + const prompt = `Act as a community manager for a technology and software community. Your job is to create content for the community's social media. + Give me the copy for the Instagram post. + Here is the information: + URL: ${articleData} + The copy for the Instagram post should be informative and educational, written in a friendly and approachable tone that invites conversation and interaction. + Try to relate the information to technology and software. + Make sure to include hashtags following Instagram and marketing best practices.`; + + const completion = await openai.chat.completions.create({ + model: 'gpt-3.5-turbo', + messages: [{ role: 'user', content: prompt }], + max_tokens: 500, + temperature: 0.7, + }); + + return completion.choices[0].message.content.trim(); + } + catch (error) { + throw new Error(`Failed to generate caption: ${error.message}`); + } +} + +// Function to generate image copy +async function generateImage(articleData) { + try { + const prompt = `Act as a community manager for a technology and software community. Your job is to create content for the community's social media. + Give me the copy for the image. + Here is the information: + URL: ${articleData} + The copy for the image should be engaging and attention-grabbing to encourage people to click and read the post.`; + + const completion = await openai.chat.completions.create({ + model: 'gpt-3.5-turbo', + messages: [{ role: 'user', content: prompt }], + max_tokens: 100, + temperature: 0.7, + }); + + return completion.choices[0].message.content.trim(); + } + catch (error) { + throw new Error(`Failed to generate image copy: ${error.message}`); + } +} + +// Function to generate image using DALL-E +async function generatePostImage(imageText) { + try { + const prompt = `Create a modern, professional social media image that represents: ${imageText}. The image should be suitable for a technology and software community, with a clean and engaging design.`; + + const response = await openai.images.generate({ + model: 'dall-e-3', + prompt: prompt, + n: 1, + quality: 'standard', + size: '1024x1024', + }); + + return response.data[0].url; + } + catch (error) { + throw new Error(`Failed to generate image with DALL-E: ${error.message}`); + } +} + +module.exports = { + generateCaption, + generateImage, + generatePostImage, +}; \ No newline at end of file