diff --git a/commands/admin/add.js b/commands/admin/add.js new file mode 100644 index 000000000..25284a135 --- /dev/null +++ b/commands/admin/add.js @@ -0,0 +1,94 @@ +module.exports = { + name: 'add', + aliases: ['adduser'], + desc: 'Add a member to the group', + category: 'Admin', + + groupOnly: true, + adminOnly: true, + botAdminNeeded: true, + + reactions: { + start: '➕', + success: '✅' + }, + + execute: async (sock, msg, args, { + from, + reply, + react + }) => { + try { + await react('➕'); + + let jid; + + // Reply target + if (msg.quoted?.sender) { + jid = msg.quoted.sender; + } + + // Mention target + if (!jid && msg.mentionedJid?.length) { + jid = msg.mentionedJid[0]; + } + + // Number from args + if (!jid && args.length) { + let number = args[0].replace(/\D/g, ''); + + if (number.startsWith('0')) { + number = '27' + number.slice(1); + } + + if (number.length < 8) { + return reply('❌ Invalid phone number.'); + } + + jid = `${number}@s.whatsapp.net`; + } + + if (!jid) { + return reply( + 'Usage:\n' + + '• .add @user\n' + + '• .add 27712345678\n' + + '• Reply to someone with .add' + ); + } + + const result = await sock.groupParticipantsUpdate( + from, + [jid], + 'add' + ); + + const status = Number(result?.[0]?.status); + + switch (status) { + case 200: + await react('✅'); + return reply('✅ Member added successfully.'); + + case 403: + return reply("❌ Can't add this user because of their privacy settings."); + + case 404: + return reply('❌ User is not registered on WhatsApp.'); + + case 408: + return reply('❌ Invite expired or timed out.'); + + case 409: + return reply('ℹ️ User is already in the group.'); + + default: + return reply(`❌ Failed to add user.\nStatus: ${status || 'Unknown'}`); + } + + } catch (err) { + console.error(err); + reply(`❌ ${err.message}`); + } + } +}; diff --git a/commands/admin/poll.js b/commands/admin/poll.js new file mode 100644 index 000000000..88c82be00 --- /dev/null +++ b/commands/admin/poll.js @@ -0,0 +1,100 @@ +/** + * Poll Command - Create a poll in a group chat + */ + +const config = require('../../config'); + +// =================================================== +// UTILITIES +// =================================================== + +/** + * Creates a decorative header for the command response. + * @param {string} title - The title of the section. + * @returns {string} - The formatted header string. + */ +const createHeader = (title) => { + return `*[ ${title.toUpperCase()} ]*`; +}; + +/** + * Creates a separator line. + * @returns {string} + */ +const createSeparator = () => "──────────────"; + +// =================================================== +// MAIN COMMAND DEFINITION +// =================================================== + +module.exports = { + name: 'poll', + aliases: ['createpoll', 'vote'], + category: 'admin', + adminOnly: true, + description: 'Create a poll with a question and options', + usage: '.poll Question | Option 1 | Option 2 | Option 3', + + async execute(sock, msg, args, extra) { + try { + // Check if it's a group chat (polls are typically used in groups) + if (!extra.from.endsWith('@g.us')) { + await sock.sendMessage( + extra.from, + { text: '❌ This command can only be used inside groups!' }, + { quoted: msg } + ); + return; + } + + // Combine all arguments and split by the pipe symbol `|` + const input = args.join(' '); + const parts = input.split('|').map(part => part.trim()); + + const question = parts[0]; + const options = parts.slice(1); + + // Validate question and options (at least 2 options required for a valid poll) + if (!question || options.length < 2) { + let text = ''; + text += createHeader('Poll Usage Error') + '\n\n'; + text += `❌ Please provide a question and at least 2 options separated by pipes (\`|\`).\n\n`; + text += `*Example:*\n`; + text += `${config.prefix}poll What should we play? | PUBG | COD Mobile | Free Fire\n\n`; + text += createSeparator() + '\n'; + text += `💡 Make sure to use \`|\` between options!`; + + await sock.sendMessage( + extra.from, + { text, mentions: [extra.sender] }, + { quoted: msg } + ); + return; + } + + // Baileys poll creation payload + // selectableCount: 1 allows single voting, set to higher if multi-choice is needed + await sock.sendMessage( + extra.from, + { + poll: { + name: question, + values: options, + selectableCount: 1 + } + }, + { quoted: msg } + ); + + } catch (err) { + console.error(err); + await sock.sendMessage( + extra.from, + { + text: `❌ ${err.message}` + }, + { quoted: msg } + ); + } + } +}; diff --git a/commands/admin/revoke.js b/commands/admin/revoke.js new file mode 100644 index 000000000..65889c791 --- /dev/null +++ b/commands/admin/revoke.js @@ -0,0 +1,33 @@ +module.exports = { + name: 'revokelink', + aliases: ['resetlink', 'revokeinvite', 'rvk'], + category: 'admin', + description: 'Revoke the current group invite link', + usage: 'revokelink', + groupOnly: true, + adminOnly: true, + reactions: { + start: '🔄', + success: '✅', + error: '❌' + }, + + execute: async (sock, msg, args, { reply }) => { + try { + const groupId = msg.key.remoteJid; + + await sock.groupRevokeInvite(groupId); + + const newCode = await sock.groupInviteCode(groupId); + const newLink = `https://chat.whatsapp.com/${newCode}`; + + return reply( + `✅ *Group invite link has been revoked!*\n\n` + + `🔗 *New Invite Link:*\n${newLink}` + ); + } catch (err) { + console.error(err); + return reply('❌ Failed to revoke the group invite link.'); + } + } +}; diff --git a/commands/admin/setgcname.js b/commands/admin/setgcname.js new file mode 100644 index 000000000..1ae5eb742 --- /dev/null +++ b/commands/admin/setgcname.js @@ -0,0 +1,37 @@ +module.exports = { + name: 'setgroupname', + aliases: ['setname', 'groupname', 'subject'], + category: 'admin', + description: 'Change the group name', + usage: 'setgroupname ', + groupOnly: true, + adminOnly: true, + reactions: { + start: '✏️', + success: '✅', + error: '❌' + }, + + execute: async (sock, msg, args, { reply }) => { + try { + if (!args.length) { + return reply( + `❌ Please provide a new group name.\n\nExample:\n.setgroupname My Awesome Group` + ); + } + + const newName = args.join(' ').trim(); + + if (newName.length > 100) { + return reply('❌ Group name cannot be longer than 100 characters.'); + } + + await sock.groupUpdateSubject(msg.key.remoteJid, newName); + + return reply(`✅ Group name has been changed to:\n*${newName}*`); + } catch (err) { + console.error(err); + return reply('❌ Failed to change the group name.'); + } + } +}; diff --git a/commands/admin/setgcpp.js b/commands/admin/setgcpp.js new file mode 100644 index 000000000..be8e1ffff --- /dev/null +++ b/commands/admin/setgcpp.js @@ -0,0 +1,114 @@ +/** + * Set Group Icon / Profile Command - Change the group's profile picture + */ + +const fs = require('fs'); +const path = require('path'); +const config = require('../../config'); + +// =================================================== +// UTILITIES +// =================================================== + +/** + * Creates a decorative header for the command response. + * @param {string} title - The title of the section. + * @returns {string} - The formatted header string. + */ +const createHeader = (title) => { + return `*[ ${title.toUpperCase()} ]*`; +}; + +/** + * Creates a separator line. + * @returns {string} + */ +const createSeparator = () => "──────────────"; + +// =================================================== +// MAIN COMMAND DEFINITION +// =================================================== + +module.exports = { + name: 'setgpp', + aliases: ['gp', 'gcpp'], + category: 'admin', + description: 'Change the group profile picture', + usage: '.setgpp (reply to an image or attach one)', + + async execute(sock, msg, args, extra) { + try { + // Check if it's a group chat + if (!extra.from.endsWith('@g.us')) { + await sock.sendMessage( + extra.from, + { text: '❌ This command can only be used inside groups!' }, + { quoted: msg } + ); + return; + } + + // Check if sender is admin or bot has admin rights (optional check depending on your bot framework, but good practice) + // Here we look for quoted media or media attached directly to the message + const quotedMsg = msg.message?.extendedTextMessage?.contextInfo?.quotedMessage; + const isQuotedImage = quotedMsg?.imageMessage; + const isDirectImage = msg.message?.imageMessage; + + if (!isDirectImage && !isQuotedImage) { + let text = ''; + text += createHeader('Group Profile Error') + '\n\n'; + text += `❌ Please send or reply to an image with the command ${config.prefix}setgpp.\n\n`; + text += createSeparator() + '\n'; + text += `💡 Attach an image or reply to one to update the group icon.`; + + await sock.sendMessage( + extra.from, + { text, mentions: [extra.sender] }, + { quoted: msg } + ); + return; + } + + // Inform user that processing has started + await sock.sendMessage( + extra.from, + { text: '🔄 Updating group profile picture...' }, + { quoted: msg } + ); + + // Download media (handles both direct media or quoted media) + // Note: Depending on your baileys wrapper helper, you can use downloadMediaMessage or downloadContentFromMessage. + // Using standard Baileys download approach: + const mediaMessage = isDirectImage ? msg.message : quotedMsg; + + // If your framework has a dedicated media downloader utility, use it here. + // Below is the standard Baileys method block for updating group icon: + const { downloadMediaMessage } = require('@whiskeysockets/baileys'); + const buffer = await downloadMediaMessage( + { message: mediaMessage }, + 'buffer', + {}, + { logger: console } + ); + + // Update group profile picture using Baileys socket method + await sock.updateProfilePicture(extra.from, buffer); + + await sock.sendMessage( + extra.from, + { text: '✅ Successfully updated the group profile picture!' }, + { quoted: msg } + ); + + } catch (err) { + console.error(err); + await sock.sendMessage( + extra.from, + { + text: `❌ Failed to update group picture: ${err.message}` + }, + { quoted: msg } + ); + } + } +}; diff --git a/commands/admin/tagadmins.js b/commands/admin/tagadmins.js new file mode 100644 index 000000000..f60b45cd4 --- /dev/null +++ b/commands/admin/tagadmins.js @@ -0,0 +1,51 @@ +module.exports = { + name: 'admins', + aliases: ['admin', 'tagadmin', 'tagadmins', 'staff'], + category: 'admin', + description: 'Mention all group admins', + usage: 'admins [optional message]', + groupOnly: true, + reactions: { + start: '📢', + success: '✅', + error: '❌' + }, + + execute: async (sock, msg, args, { reply }) => { + try { + const groupId = msg.key.remoteJid; + const metadata = await sock.groupMetadata(groupId); + + const admins = metadata.participants.filter( + p => p.admin === 'admin' || p.admin === 'superadmin' + ); + + if (!admins.length) { + return reply('❌ No admins found.'); + } + + const mentions = admins.map(a => a.id); + + let text = `👑 *Group Admins (${admins.length})*\n\n`; + + admins.forEach((admin, i) => { + text += `${i + 1}. @${admin.id.split('@')[0]}\n`; + }); + + if (args.length) { + text += `\n📩 *Message:*\n${args.join(' ')}`; + } + + await sock.sendMessage(groupId, { + text, + mentions + }, { + quoted: msg + }); + + } catch (err) { + console.error(err); + reply('❌ Failed to mention group admins.'); + } + } +}; diff --git a/commands/ai/ai.js b/commands/ai/ai.js deleted file mode 100644 index 3c8e12739..000000000 --- a/commands/ai/ai.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * AI Chat Command - ChatGPT-style responses - */ - -const APIs = require('../../utils/api'); - -module.exports = { - name: 'ai', - aliases: ['gpt', 'chatgpt', 'ask'], - category: 'ai', - description: 'Chat with AI (ChatGPT-style)', - usage: '.ai ', - - async execute(sock, msg, args, extra) { - try { - if (args.length === 0) { - return extra.reply('❌ Usage: .ai \n\nExample: .ai What is the capital of France?'); - } - - const question = args.join(' '); - - const response = await APIs.chatAI(question); - - // Send only the answer without labels - const answer = response.response || response.msg || response.data?.msg || response; - await extra.reply(answer); - - } catch (error) { - await extra.reply(`❌ AI Error: ${error.message}`); - } - } -}; diff --git a/commands/anime/hneko.js b/commands/anime/hneko.js deleted file mode 100644 index 4b3947686..000000000 --- a/commands/anime/hneko.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Hneko Command - Get random hneko anime images - */ - -const axios = require('axios'); -const fs = require('fs'); -const path = require('path'); -const { getTempDir, deleteTempFile } = require('../../utils/tempManager'); - -const BASE = 'https://api.princetechn.com/api/anime/hneko'; -const API_KEY = 'prince'; - -module.exports = { - name: 'hneko', - aliases: ['hnekonsfw'], - category: 'anime', - desc: 'Get random hneko NSFW anime images', - usage: 'hneko', - execute: async (sock, msg, args, extra) => { - try { - const url = `${BASE}?apikey=${API_KEY}`; - const response = await axios.get(url, { - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Accept': 'application/json' - }, - timeout: 30000 - }); - - if (!response.data || !response.data.result) { - throw new Error('Invalid API response: missing image URL'); - } - - const imageUrl = response.data.result; - - if (!imageUrl || typeof imageUrl !== 'string') { - throw new Error('Invalid image URL in API response'); - } - - const imageResponse = await axios.get(imageUrl, { - responseType: 'arraybuffer', - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Accept': 'image/*' - }, - timeout: 30000 - }); - - const imageBuffer = Buffer.from(imageResponse.data); - - if (!imageBuffer || imageBuffer.length === 0) { - throw new Error('Empty image response'); - } - - const maxImageSize = 5 * 1024 * 1024; - if (imageBuffer.length > maxImageSize) { - throw new Error(`Image too large: ${(imageBuffer.length / 1024 / 1024).toFixed(2)}MB (max 5MB)`); - } - - const contentType = imageResponse.headers['content-type'] || ''; - let extension = 'jpg'; - if (contentType.includes('png')) { - extension = 'png'; - } else if (contentType.includes('jpeg')) { - extension = 'jpg'; - } else if (imageUrl.match(/\.(png|jpg|jpeg)$/i)) { - const match = imageUrl.match(/\.(png|jpg|jpeg)$/i); - extension = match[1].toLowerCase(); - } - - const tempDir = getTempDir(); - const timestamp = Date.now(); - const tempImagePath = path.join(tempDir, `hneko_${timestamp}.${extension}`); - - let finalBuffer = null; - - try { - fs.writeFileSync(tempImagePath, imageBuffer); - finalBuffer = fs.readFileSync(tempImagePath); - - if (!finalBuffer || finalBuffer.length === 0) { - throw new Error('Failed to read image from temp file'); - } - - await sock.sendMessage(extra.from, { - image: finalBuffer - }, { quoted: msg }); - - } finally { - try { - deleteTempFile(tempImagePath); - } catch (cleanupError) { - } - } - - } catch (error) { - console.error('Error in hneko command:', error); - - if (error.response?.status === 404) { - await extra.reply('❌ Image not found. Please try again.'); - } else if (error.response?.status === 429) { - await extra.reply('❌ Rate limit exceeded. Please try again later.'); - } else if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) { - await extra.reply('❌ Request timed out. Please try again.'); - } else { - await extra.reply(`❌ Failed to fetch hneko image: ${error.message}`); - } - } - } -}; - diff --git a/commands/anime/hwaifu.js b/commands/anime/hwaifu.js deleted file mode 100644 index fee89125a..000000000 --- a/commands/anime/hwaifu.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Hwaifu Command - Get random hwaifu anime images - */ - -const axios = require('axios'); -const fs = require('fs'); -const path = require('path'); -const { getTempDir, deleteTempFile } = require('../../utils/tempManager'); - -const BASE = 'https://api.princetechn.com/api/anime/hwaifu'; -const API_KEY = 'prince'; - -module.exports = { - name: 'hwaifu', - aliases: ['hwaifunsfw'], - category: 'anime', - desc: 'Get random hwaifu NSFW anime images', - usage: 'hwaifu', - execute: async (sock, msg, args, extra) => { - try { - const url = `${BASE}?apikey=${API_KEY}`; - const response = await axios.get(url, { - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Accept': 'application/json' - }, - timeout: 30000 - }); - - if (!response.data || !response.data.result) { - throw new Error('Invalid API response: missing image URL'); - } - - const imageUrl = response.data.result; - - if (!imageUrl || typeof imageUrl !== 'string') { - throw new Error('Invalid image URL in API response'); - } - - const imageResponse = await axios.get(imageUrl, { - responseType: 'arraybuffer', - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Accept': 'image/*' - }, - timeout: 30000 - }); - - const imageBuffer = Buffer.from(imageResponse.data); - - if (!imageBuffer || imageBuffer.length === 0) { - throw new Error('Empty image response'); - } - - const maxImageSize = 5 * 1024 * 1024; - if (imageBuffer.length > maxImageSize) { - throw new Error(`Image too large: ${(imageBuffer.length / 1024 / 1024).toFixed(2)}MB (max 5MB)`); - } - - const contentType = imageResponse.headers['content-type'] || ''; - let extension = 'jpg'; - if (contentType.includes('png')) { - extension = 'png'; - } else if (contentType.includes('jpeg')) { - extension = 'jpg'; - } else if (imageUrl.match(/\.(png|jpg|jpeg)$/i)) { - const match = imageUrl.match(/\.(png|jpg|jpeg)$/i); - extension = match[1].toLowerCase(); - } - - const tempDir = getTempDir(); - const timestamp = Date.now(); - const tempImagePath = path.join(tempDir, `hwaifu_${timestamp}.${extension}`); - - let finalBuffer = null; - - try { - fs.writeFileSync(tempImagePath, imageBuffer); - finalBuffer = fs.readFileSync(tempImagePath); - - if (!finalBuffer || finalBuffer.length === 0) { - throw new Error('Failed to read image from temp file'); - } - - await sock.sendMessage(extra.from, { - image: finalBuffer - }, { quoted: msg }); - - } finally { - try { - deleteTempFile(tempImagePath); - } catch (cleanupError) { - } - } - - } catch (error) { - console.error('Error in hwaifu command:', error); - - if (error.response?.status === 404) { - await extra.reply('❌ Image not found. Please try again.'); - } else if (error.response?.status === 429) { - await extra.reply('❌ Rate limit exceeded. Please try again later.'); - } else if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) { - await extra.reply('❌ Request timed out. Please try again.'); - } else { - await extra.reply(`❌ Failed to fetch hwaifu image: ${error.message}`); - } - } - } -}; - diff --git a/commands/anime/konachan.js b/commands/anime/konachan.js deleted file mode 100644 index 230ec89d8..000000000 --- a/commands/anime/konachan.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Konachan Command - Get random konachan anime images - */ - -const axios = require('axios'); -const fs = require('fs'); -const path = require('path'); -const { getTempDir, deleteTempFile } = require('../../utils/tempManager'); - -const BASE = 'https://api.princetechn.com/api/anime/konachan'; -const API_KEY = 'prince'; - -module.exports = { - name: 'konachan', - aliases: ['konachansfw'], - category: 'anime', - desc: 'Get random konachan SFW anime images', - usage: 'konachan', - execute: async (sock, msg, args, extra) => { - try { - // Fetch JSON from API to get image URL - const url = `${BASE}?apikey=${API_KEY}`; - const response = await axios.get(url, { - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Accept': 'application/json' - }, - timeout: 30000 - }); - - // Extract image URL from response - if (!response.data || !response.data.result) { - throw new Error('Invalid API response: missing image URL'); - } - - const imageUrl = response.data.result; - - if (!imageUrl || typeof imageUrl !== 'string') { - throw new Error('Invalid image URL in API response'); - } - - // Download image from the URL - const imageResponse = await axios.get(imageUrl, { - responseType: 'arraybuffer', - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Accept': 'image/*' - }, - timeout: 30000 - }); - - const imageBuffer = Buffer.from(imageResponse.data); - - // Verify buffer is valid - if (!imageBuffer || imageBuffer.length === 0) { - throw new Error('Empty image response'); - } - - // Check file size (WhatsApp image limit is 5MB) - const maxImageSize = 5 * 1024 * 1024; // 5MB - if (imageBuffer.length > maxImageSize) { - throw new Error(`Image too large: ${(imageBuffer.length / 1024 / 1024).toFixed(2)}MB (max 5MB)`); - } - - // Determine file extension from URL or content type - const contentType = imageResponse.headers['content-type'] || ''; - let extension = 'jpg'; - if (contentType.includes('png')) { - extension = 'png'; - } else if (contentType.includes('jpeg')) { - extension = 'jpg'; - } else if (imageUrl.match(/\.(png|jpg|jpeg)$/i)) { - const match = imageUrl.match(/\.(png|jpg|jpeg)$/i); - extension = match[1].toLowerCase(); - } - - // Write to temp file first, then read back to ensure buffer is valid - const tempDir = getTempDir(); - const timestamp = Date.now(); - const tempImagePath = path.join(tempDir, `konachan_${timestamp}.${extension}`); - - let finalBuffer = null; - - try { - // Write buffer to temp file - fs.writeFileSync(tempImagePath, imageBuffer); - - // Read back from file to ensure buffer is properly formed - finalBuffer = fs.readFileSync(tempImagePath); - - if (!finalBuffer || finalBuffer.length === 0) { - throw new Error('Failed to read image from temp file'); - } - - // Send the image - await sock.sendMessage(extra.from, { - image: finalBuffer - }, { quoted: msg }); - - } finally { - // Cleanup temp file - try { - deleteTempFile(tempImagePath); - } catch (cleanupError) { - // Ignore cleanup errors - } - } - - } catch (error) { - console.error('Error in konachan command:', error); - - // Handle specific error cases - if (error.response?.status === 404) { - await extra.reply('❌ Image not found. Please try again.'); - } else if (error.response?.status === 429) { - await extra.reply('❌ Rate limit exceeded. Please try again later.'); - } else if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) { - await extra.reply('❌ Request timed out. Please try again.'); - } else { - await extra.reply(`❌ Failed to fetch konachan image: ${error.message}`); - } - } - } -}; - diff --git a/commands/anime/loli.js b/commands/anime/loli.js index fedaca770..91445d54c 100644 --- a/commands/anime/loli.js +++ b/commands/anime/loli.js @@ -13,7 +13,7 @@ const API_KEY = 'prince'; module.exports = { name: 'loli', aliases: ['lolinsfw'], - category: 'anime', + category: 'nsfw', desc: 'Get random loli NSFW anime images', usage: 'loli', execute: async (sock, msg, args, extra) => { diff --git a/commands/anime/megumin.js b/commands/anime/megumin.js deleted file mode 100644 index f73609f08..000000000 --- a/commands/anime/megumin.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Megumin Command - Get random megumin anime images - */ - -const axios = require('axios'); -const fs = require('fs'); -const path = require('path'); -const { getTempDir, deleteTempFile } = require('../../utils/tempManager'); - -const BASE = 'https://api.princetechn.com/api/anime/megumin'; -const API_KEY = 'prince'; - -module.exports = { - name: 'megumin', - aliases: ['meguminnsfw'], - category: 'anime', - desc: 'Get random megumin NSFW anime images', - usage: 'megumin', - execute: async (sock, msg, args, extra) => { - try { - const url = `${BASE}?apikey=${API_KEY}`; - const response = await axios.get(url, { - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Accept': 'application/json' - }, - timeout: 30000 - }); - - if (!response.data || !response.data.result) { - throw new Error('Invalid API response: missing image URL'); - } - - const imageUrl = response.data.result; - - if (!imageUrl || typeof imageUrl !== 'string') { - throw new Error('Invalid image URL in API response'); - } - - const imageResponse = await axios.get(imageUrl, { - responseType: 'arraybuffer', - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Accept': 'image/*' - }, - timeout: 30000 - }); - - const imageBuffer = Buffer.from(imageResponse.data); - - if (!imageBuffer || imageBuffer.length === 0) { - throw new Error('Empty image response'); - } - - const maxImageSize = 5 * 1024 * 1024; - if (imageBuffer.length > maxImageSize) { - throw new Error(`Image too large: ${(imageBuffer.length / 1024 / 1024).toFixed(2)}MB (max 5MB)`); - } - - const contentType = imageResponse.headers['content-type'] || ''; - let extension = 'jpg'; - if (contentType.includes('png')) { - extension = 'png'; - } else if (contentType.includes('jpeg')) { - extension = 'jpg'; - } else if (imageUrl.match(/\.(png|jpg|jpeg)$/i)) { - const match = imageUrl.match(/\.(png|jpg|jpeg)$/i); - extension = match[1].toLowerCase(); - } - - const tempDir = getTempDir(); - const timestamp = Date.now(); - const tempImagePath = path.join(tempDir, `megumin_${timestamp}.${extension}`); - - let finalBuffer = null; - - try { - fs.writeFileSync(tempImagePath, imageBuffer); - finalBuffer = fs.readFileSync(tempImagePath); - - if (!finalBuffer || finalBuffer.length === 0) { - throw new Error('Failed to read image from temp file'); - } - - await sock.sendMessage(extra.from, { - image: finalBuffer - }, { quoted: msg }); - - } finally { - try { - deleteTempFile(tempImagePath); - } catch (cleanupError) { - } - } - - } catch (error) { - console.error('Error in megumin command:', error); - - if (error.response?.status === 404) { - await extra.reply('❌ Image not found. Please try again.'); - } else if (error.response?.status === 429) { - await extra.reply('❌ Rate limit exceeded. Please try again later.'); - } else if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) { - await extra.reply('❌ Request timed out. Please try again.'); - } else { - await extra.reply(`❌ Failed to fetch megumin image: ${error.message}`); - } - } - } -}; - diff --git a/commands/anime/milf.js b/commands/anime/milf.js deleted file mode 100644 index d2652aad1..000000000 --- a/commands/anime/milf.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * Milf Command - Get random milf anime images - */ - -const axios = require('axios'); -const fs = require('fs'); -const path = require('path'); -const { getTempDir, deleteTempFile } = require('../../utils/tempManager'); - -const BASE = 'https://api.princetechn.com/api/anime/milf'; -const API_KEY = 'prince'; - -module.exports = { - name: 'milf', - aliases: ['milfnsfw'], - category: 'anime', - desc: 'Get random milf NSFW anime images', - usage: 'milf', - execute: async (sock, msg, args, extra) => { - try { - const url = `${BASE}?apikey=${API_KEY}`; - const response = await axios.get(url, { - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Accept': 'application/json' - }, - timeout: 30000 - }); - - if (!response.data || !response.data.result) { - throw new Error('Invalid API response: missing image URL'); - } - - const imageUrl = response.data.result; - - if (!imageUrl || typeof imageUrl !== 'string') { - throw new Error('Invalid image URL in API response'); - } - - const imageResponse = await axios.get(imageUrl, { - responseType: 'arraybuffer', - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Accept': 'image/*' - }, - timeout: 30000 - }); - - const imageBuffer = Buffer.from(imageResponse.data); - - if (!imageBuffer || imageBuffer.length === 0) { - throw new Error('Empty image response'); - } - - const maxImageSize = 5 * 1024 * 1024; - if (imageBuffer.length > maxImageSize) { - throw new Error(`Image too large: ${(imageBuffer.length / 1024 / 1024).toFixed(2)}MB (max 5MB)`); - } - - const contentType = imageResponse.headers['content-type'] || ''; - let extension = 'jpg'; - if (contentType.includes('png')) { - extension = 'png'; - } else if (contentType.includes('jpeg')) { - extension = 'jpg'; - } else if (imageUrl.match(/\.(png|jpg|jpeg)$/i)) { - const match = imageUrl.match(/\.(png|jpg|jpeg)$/i); - extension = match[1].toLowerCase(); - } - - const tempDir = getTempDir(); - const timestamp = Date.now(); - const tempImagePath = path.join(tempDir, `milf_${timestamp}.${extension}`); - - let finalBuffer = null; - - try { - fs.writeFileSync(tempImagePath, imageBuffer); - finalBuffer = fs.readFileSync(tempImagePath); - - if (!finalBuffer || finalBuffer.length === 0) { - throw new Error('Failed to read image from temp file'); - } - - await sock.sendMessage(extra.from, { - image: finalBuffer - }, { quoted: msg }); - - } finally { - try { - deleteTempFile(tempImagePath); - } catch (cleanupError) { - } - } - - } catch (error) { - console.error('Error in milf command:', error); - - if (error.response?.status === 404) { - await extra.reply('❌ Image not found. Please try again.'); - } else if (error.response?.status === 429) { - await extra.reply('❌ Rate limit exceeded. Please try again later.'); - } else if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) { - await extra.reply('❌ Request timed out. Please try again.'); - } else { - await extra.reply(`❌ Failed to fetch milf image: ${error.message}`); - } - } - } -}; - diff --git a/commands/anime/neko.js b/commands/anime/neko.js index ba9a3bebc..cae1e841d 100644 --- a/commands/anime/neko.js +++ b/commands/anime/neko.js @@ -1,45 +1,38 @@ -/** - * Neko Command - Get random neko anime images - */ - const axios = require('axios'); const fs = require('fs'); const path = require('path'); const { getTempDir, deleteTempFile } = require('../../utils/tempManager'); -const BASE = 'https://api.princetechn.com/api/anime/neko'; -const API_KEY = 'prince'; +const BASE = 'https://api.nekosapi.com/v4/images/random'; module.exports = { name: 'neko', - aliases: ['nekosfw'], - category: 'anime', - desc: 'Get random neko SFW anime images', + aliases: ['catgirl', 'waifu'], + category: 'nsfw', + desc: 'Get a random neko image', usage: 'neko', + execute: async (sock, msg, args, extra) => { try { - // Fetch JSON from API to get image URL - const url = `${BASE}?apikey=${API_KEY}`; - const response = await axios.get(url, { + const { data } = await axios.get(BASE, { headers: { 'User-Agent': 'Mozilla/5.0', 'Accept': 'application/json' }, timeout: 30000 }); - - // Extract image URL from response - if (!response.data || !response.data.result) { - throw new Error('Invalid API response: missing image URL'); + + if (!Array.isArray(data) || data.length === 0) { + throw new Error('No images returned from the API'); } - - const imageUrl = response.data.result; - - if (!imageUrl || typeof imageUrl !== 'string') { - throw new Error('Invalid image URL in API response'); + + const image = data[0]; + const imageUrl = image.url; + + if (!imageUrl) { + throw new Error('Invalid image URL'); } - - // Download image from the URL + const imageResponse = await axios.get(imageUrl, { responseType: 'arraybuffer', headers: { @@ -48,78 +41,49 @@ module.exports = { }, timeout: 30000 }); - + const imageBuffer = Buffer.from(imageResponse.data); - - // Verify buffer is valid - if (!imageBuffer || imageBuffer.length === 0) { + + if (!imageBuffer.length) { throw new Error('Empty image response'); } - - // Check file size (WhatsApp image limit is 5MB) - const maxImageSize = 5 * 1024 * 1024; // 5MB - if (imageBuffer.length > maxImageSize) { - throw new Error(`Image too large: ${(imageBuffer.length / 1024 / 1024).toFixed(2)}MB (max 5MB)`); - } - - // Determine file extension from URL or content type + const contentType = imageResponse.headers['content-type'] || ''; let extension = 'jpg'; - if (contentType.includes('png')) { - extension = 'png'; - } else if (contentType.includes('jpeg')) { - extension = 'jpg'; - } else if (imageUrl.match(/\.(png|jpg|jpeg)$/i)) { - const match = imageUrl.match(/\.(png|jpg|jpeg)$/i); - extension = match[1].toLowerCase(); - } - - // Write to temp file first, then read back to ensure buffer is valid + + if (contentType.includes('png')) extension = 'png'; + else if (contentType.includes('webp')) extension = 'webp'; + else if (contentType.includes('jpeg')) extension = 'jpg'; + const tempDir = getTempDir(); - const timestamp = Date.now(); - const tempImagePath = path.join(tempDir, `neko_${timestamp}.${extension}`); - - let finalBuffer = null; - + const tempPath = path.join(tempDir, `neko_${Date.now()}.${extension}`); + try { - // Write buffer to temp file - fs.writeFileSync(tempImagePath, imageBuffer); - - // Read back from file to ensure buffer is properly formed - finalBuffer = fs.readFileSync(tempImagePath); - - if (!finalBuffer || finalBuffer.length === 0) { - throw new Error('Failed to read image from temp file'); - } - - // Send the image - await sock.sendMessage(extra.from, { - image: finalBuffer - }, { quoted: msg }); - + fs.writeFileSync(tempPath, imageBuffer); + + await sock.sendMessage( + extra.from, + { + image: fs.readFileSync(tempPath) + }, + { quoted: msg } + ); } finally { - // Cleanup temp file - try { - deleteTempFile(tempImagePath); - } catch (cleanupError) { - // Ignore cleanup errors - } + deleteTempFile(tempPath); } - + } catch (error) { console.error('Error in neko command:', error); - - // Handle specific error cases - if (error.response?.status === 404) { - await extra.reply('❌ Image not found. Please try again.'); - } else if (error.response?.status === 429) { - await extra.reply('❌ Rate limit exceeded. Please try again later.'); - } else if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) { - await extra.reply('❌ Request timed out. Please try again.'); - } else { - await extra.reply(`❌ Failed to fetch neko image: ${error.message}`); + + if (error.response?.status === 429) { + return extra.reply('❌ Rate limit exceeded. Please try again later.'); + } + + if (error.code === 'ECONNABORTED') { + return extra.reply('❌ Request timed out. Please try again.'); } + + return extra.reply(`❌ Failed to fetch image: ${error.message}`); } } }; - diff --git a/commands/anime/random.js b/commands/anime/random.js deleted file mode 100644 index c9214c5c6..000000000 --- a/commands/anime/random.js +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Random Command - Get random anime data - */ - -const axios = require('axios'); -const fs = require('fs'); -const path = require('path'); -const { getTempDir, deleteTempFile } = require('../../utils/tempManager'); - -const BASE = 'https://api.princetechn.com/api/anime/random'; -const API_KEY = 'prince'; - -module.exports = { - name: 'random', - aliases: ['animerandom', 'randomanime'], - category: 'anime', - desc: 'Get random anime data', - usage: 'random', - execute: async (sock, msg, args, extra) => { - try { - const url = `${BASE}?apikey=${API_KEY}`; - const response = await axios.get(url, { - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Accept': 'application/json' - }, - timeout: 30000 - }); - - if (!response.data || !response.data.result) { - throw new Error('Invalid API response: missing anime data'); - } - - const animeData = response.data.result; - - // Download thumbnail image - let imageBuffer = null; - if (animeData.thumbnail) { - try { - const imageResponse = await axios.get(animeData.thumbnail, { - responseType: 'arraybuffer', - headers: { - 'User-Agent': 'Mozilla/5.0', - 'Accept': 'image/*' - }, - timeout: 30000 - }); - - imageBuffer = Buffer.from(imageResponse.data); - - if (imageBuffer && imageBuffer.length > 0) { - const maxImageSize = 5 * 1024 * 1024; - if (imageBuffer.length > maxImageSize) { - imageBuffer = null; // Skip image if too large - } - } - } catch (imgError) { - console.error('Error downloading thumbnail:', imgError); - imageBuffer = null; - } - } - - // Build caption with anime info - let caption = `*${animeData.title || 'Unknown'}*\n\n`; - - if (animeData.episodes) { - caption += `📺 Episodes: ${animeData.episodes}\n`; - } - - if (animeData.status) { - caption += `📊 Status: ${animeData.status}\n`; - } - - if (animeData.synopsis) { - caption += `\n📝 ${animeData.synopsis}\n`; - } - - if (animeData.link) { - caption += `\n🔗 ${animeData.link}`; - } - - // Send with image if available - if (imageBuffer) { - const contentType = 'image/jpeg'; - let extension = 'jpg'; - if (animeData.thumbnail.match(/\.(png|jpg|jpeg)$/i)) { - const match = animeData.thumbnail.match(/\.(png|jpg|jpeg)$/i); - extension = match[1].toLowerCase(); - } - - const tempDir = getTempDir(); - const timestamp = Date.now(); - const tempImagePath = path.join(tempDir, `anime_${timestamp}.${extension}`); - - try { - fs.writeFileSync(tempImagePath, imageBuffer); - const finalBuffer = fs.readFileSync(tempImagePath); - - await sock.sendMessage(extra.from, { - image: finalBuffer, - caption: caption - }, { quoted: msg }); - - } finally { - try { - deleteTempFile(tempImagePath); - } catch (cleanupError) { - } - } - } else { - // Send text only if no image - await sock.sendMessage(extra.from, { - text: caption - }, { quoted: msg }); - } - - } catch (error) { - console.error('Error in random command:', error); - - if (error.response?.status === 404) { - await extra.reply('❌ Anime data not found. Please try again.'); - } else if (error.response?.status === 429) { - await extra.reply('❌ Rate limit exceeded. Please try again later.'); - } else if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) { - await extra.reply('❌ Request timed out. Please try again.'); - } else { - await extra.reply(`❌ Failed to fetch anime data: ${error.message}`); - } - } - } -}; - diff --git a/commands/anime/waifu.js b/commands/anime/waifu.js index 86a8e860c..76724e8a7 100644 --- a/commands/anime/waifu.js +++ b/commands/anime/waifu.js @@ -1,45 +1,37 @@ -/** - * Waifu Command - Get random waifu anime images - */ - const axios = require('axios'); const fs = require('fs'); const path = require('path'); const { getTempDir, deleteTempFile } = require('../../utils/tempManager'); -const BASE = 'https://api.princetechn.com/api/anime/waifu'; -const API_KEY = 'prince'; - +const BASE = 'https://api.waifu.im/images?IsNsfw=True'; module.exports = { name: 'waifu', - aliases: ['waifusfw'], - category: 'anime', - desc: 'Get random waifu SFW anime images', + aliases: ['waifuim'], + category: 'nsfw', + desc: 'Get a random waifu image', usage: 'waifu', + execute: async (sock, msg, args, extra) => { try { - // Fetch JSON from API to get image URL - const url = `${BASE}?apikey=${API_KEY}`; - const response = await axios.get(url, { + const { data } = await axios.get(BASE, { headers: { 'User-Agent': 'Mozilla/5.0', 'Accept': 'application/json' }, timeout: 30000 }); - - // Extract image URL from response - if (!response.data || !response.data.result) { - throw new Error('Invalid API response: missing image URL'); + + if (!data?.items?.length) { + throw new Error('No images returned from the API'); } - - const imageUrl = response.data.result; - - if (!imageUrl || typeof imageUrl !== 'string') { - throw new Error('Invalid image URL in API response'); + + const image = data.items[0]; + const imageUrl = image.url; + + if (!imageUrl) { + throw new Error('Image URL not found'); } - - // Download image from the URL + const imageResponse = await axios.get(imageUrl, { responseType: 'arraybuffer', headers: { @@ -48,78 +40,55 @@ module.exports = { }, timeout: 30000 }); - + const imageBuffer = Buffer.from(imageResponse.data); - - // Verify buffer is valid - if (!imageBuffer || imageBuffer.length === 0) { + + if (!imageBuffer.length) { throw new Error('Empty image response'); } - - // Check file size (WhatsApp image limit is 5MB) - const maxImageSize = 5 * 1024 * 1024; // 5MB - if (imageBuffer.length > maxImageSize) { - throw new Error(`Image too large: ${(imageBuffer.length / 1024 / 1024).toFixed(2)}MB (max 5MB)`); - } - - // Determine file extension from URL or content type - const contentType = imageResponse.headers['content-type'] || ''; - let extension = 'jpg'; - if (contentType.includes('png')) { - extension = 'png'; - } else if (contentType.includes('jpeg')) { - extension = 'jpg'; - } else if (imageUrl.match(/\.(png|jpg|jpeg)$/i)) { - const match = imageUrl.match(/\.(png|jpg|jpeg)$/i); - extension = match[1].toLowerCase(); - } - - // Write to temp file first, then read back to ensure buffer is valid + + const extension = path.extname(imageUrl).replace('.', '') || 'png'; const tempDir = getTempDir(); - const timestamp = Date.now(); - const tempImagePath = path.join(tempDir, `waifu_${timestamp}.${extension}`); - - let finalBuffer = null; - + const tempPath = path.join(tempDir, `waifu_${Date.now()}.${extension}`); + try { - // Write buffer to temp file - fs.writeFileSync(tempImagePath, imageBuffer); - - // Read back from file to ensure buffer is properly formed - finalBuffer = fs.readFileSync(tempImagePath); - - if (!finalBuffer || finalBuffer.length === 0) { - throw new Error('Failed to read image from temp file'); - } - - // Send the image - await sock.sendMessage(extra.from, { - image: finalBuffer - }, { quoted: msg }); - + fs.writeFileSync(tempPath, imageBuffer); + + const artist = image.artists?.[0]?.name || 'Unknown'; + const tags = image.tags?.map(tag => tag.name).join(', ') || 'None'; + + await sock.sendMessage( + extra.from, + { + image: fs.readFileSync(tempPath), + caption: +`✨ Random Waifu + +🆔 ID: ${image.id} +🎨 Artist: ${artist} +🏷️ Tags: ${tags} +📏 Resolution: ${image.width}×${image.height} +❤️ Favorites: ${image.favorites} +🔞 NSFW: ${image.isNsfw ? 'Yes' : 'No'}` + }, + { quoted: msg } + ); } finally { - // Cleanup temp file - try { - deleteTempFile(tempImagePath); - } catch (cleanupError) { - // Ignore cleanup errors - } + deleteTempFile(tempPath); } - + } catch (error) { console.error('Error in waifu command:', error); - - // Handle specific error cases - if (error.response?.status === 404) { - await extra.reply('❌ Image not found. Please try again.'); - } else if (error.response?.status === 429) { - await extra.reply('❌ Rate limit exceeded. Please try again later.'); - } else if (error.code === 'ECONNABORTED' || error.message.includes('timeout')) { - await extra.reply('❌ Request timed out. Please try again.'); - } else { - await extra.reply(`❌ Failed to fetch waifu image: ${error.message}`); + + if (error.response?.status === 429) { + return extra.reply('❌ Rate limit exceeded. Please try again later.'); + } + + if (error.code === 'ECONNABORTED') { + return extra.reply('❌ Request timed out. Please try again.'); } + + return extra.reply(`❌ Failed to fetch image: ${error.message}`); } } }; - diff --git a/commands/fun/cat.js b/commands/fun/cat.js new file mode 100644 index 000000000..e84e4f082 --- /dev/null +++ b/commands/fun/cat.js @@ -0,0 +1,44 @@ +const axios = require("axios"); + +module.exports = { + name: "cat", + aliases: ["kitty", "meow"], + category: "fun", + description: "Get a random cat image.", + usage: "cat", + + async execute(sock, m) { + try { + const { data } = await axios.get( + "https://api.thecatapi.com/v1/images/search" + ); + + if (!data.length || !data[0].url) { + return await sock.sendMessage( + m.key.remoteJid, + { text: "❌ Failed to fetch a cat image." }, + { quoted: m } + ); + } + + await sock.sendMessage( + m.key.remoteJid, + { + image: { url: data[0].url }, + caption: "🐱 Meow!" + }, + { quoted: m } + ); + } catch (err) { + console.error(err); + + await sock.sendMessage( + m.key.remoteJid, + { + text: "❌ An error occurred while fetching a cat image." + }, + { quoted: m } + ); + } + }, +}; diff --git a/commands/general/list.js b/commands/general/list.js index e474689cf..a6cddf5ca 100644 --- a/commands/general/list.js +++ b/commands/general/list.js @@ -60,25 +60,12 @@ module.exports = { text: menu, footer: `> *Powered by ${config.botName}*`, buttons: [ + { name: 'cta_url', buttonParamsJson: JSON.stringify({ - display_text: 'Youtube', - url: config.social?.youtube || 'http://youtube.com/@mr_unique_hacker' - }) - }, - { - name: 'cta_url', - buttonParamsJson: JSON.stringify({ - display_text: 'Visit Bot Repo', - url: config.social?.github || 'https://github.com/mruniquehacker' - }) - }, - { - name: 'cta_url', - buttonParamsJson: JSON.stringify({ - display_text: 'Join Channel', - url: 'https://whatsapp.com/channel/0029Va90zAnIHphOuO8Msp3A' + display_text: 'Join Group', + url: 'https://chat.whatsapp.com/DXdVqaHPBM2Eed2DRJkd7S?s=cl&p=i&mlu=4&amv=0' }) } ] diff --git a/commands/general/menu.js b/commands/general/menu.js index 35c737ac6..52166091b 100644 --- a/commands/general/menu.js +++ b/commands/general/menu.js @@ -2,201 +2,203 @@ * Menu Command - Display all available commands */ +/** + * Dynamic Menu Command + */ + +/** + * Menu Command - Display all available commands + */ + +const fs = require('fs'); +const path = require('path'); const config = require('../../config'); const { loadCommands } = require('../../utils/commandLoader'); +// =================================================== +// UTILITIES +// =================================================== + +/** + * Creates a decorative header for menu sections. + * Matches the styling from user screenshots. + * @param {string} title - The title of the section. + * @returns {string} - The formatted header string. + */ +const createHeader = (title) => { + const padding = "━".repeat(3); + return `*[ ${title.toUpperCase()} ]*`; +}; + +/** + * Creates a separator line. + * @returns {string} + */ +const createSeparator = () => "──────────────"; + +// =================================================== +// MAIN COMMAND DEFINITION +// =================================================== + module.exports = { - name: 'menu', - aliases: ['help', 'commands'], - category: 'general', - description: 'Show all available commands', - usage: '.menu', - - async execute(sock, msg, args, extra) { - try { - const commands = loadCommands(); - const categories = {}; - - // Group commands by category - commands.forEach((cmd, name) => { - if (cmd.name === name) { // Only count main command names, not aliases - if (!categories[cmd.category]) { - categories[cmd.category] = []; - } - categories[cmd.category].push(cmd); - } - }); - - const ownerNames = Array.isArray(config.ownerName) ? config.ownerName : [config.ownerName]; - const displayOwner = ownerNames[0] || config.ownerName || 'Bot Owner'; - - let menuText = `╭━━『 *${config.botName}* 』━━╮\n\n`; - menuText += `👋 Hello @${extra.sender.split('@')[0]}!\n\n`; - menuText += `⚡ Prefix: ${config.prefix}\n`; - menuText += `📦 Total Commands: ${commands.size}\n`; - menuText += `👑 Owner: ${displayOwner}\n\n`; - - // General Commands - if (categories.general) { - menuText += `┏━━━━━━━━━━━━━━━━━\n`; - menuText += `┃ 🧭 GENERAL COMMAND\n`; - menuText += `┗━━━━━━━━━━━━━━━━━\n`; - categories.general.forEach(cmd => { - menuText += `│ ➜ ${config.prefix}${cmd.name}\n`; - }); - menuText += `\n`; - } - - // AI Commands - if (categories.ai) { - menuText += `┏━━━━━━━━━━━━━━━━━\n`; - menuText += `┃ 🤖 AI COMMAND\n`; - menuText += `┗━━━━━━━━━━━━━━━━━\n`; - categories.ai.forEach(cmd => { - menuText += `│ ➜ ${config.prefix}${cmd.name}\n`; - }); - menuText += `\n`; - } - - // Group Commands - if (categories.group) { - menuText += `┏━━━━━━━━━━━━━━━━━\n`; - menuText += `┃ 🔵 GROUP COMMAND\n`; - menuText += `┗━━━━━━━━━━━━━━━━━\n`; - categories.group.forEach(cmd => { - menuText += `│ ➜ ${config.prefix}${cmd.name}\n`; - }); - menuText += `\n`; - } - - // Admin Commands - if (categories.admin) { - menuText += `┏━━━━━━━━━━━━━━━━━\n`; - menuText += `┃ 🛡️ ADMIN COMMAND\n`; - menuText += `┗━━━━━━━━━━━━━━━━━\n`; - categories.admin.forEach(cmd => { - menuText += `│ ➜ ${config.prefix}${cmd.name}\n`; - }); - menuText += `\n`; - } - - // Owner Commands - if (categories.owner) { - menuText += `┏━━━━━━━━━━━━━━━━━\n`; - menuText += `┃ 👑 OWNER COMMAND\n`; - menuText += `┗━━━━━━━━━━━━━━━━━\n`; - categories.owner.forEach(cmd => { - menuText += `│ ➜ ${config.prefix}${cmd.name}\n`; - }); - menuText += `\n`; - } - - // Media Commands - if (categories.media) { - menuText += `┏━━━━━━━━━━━━━━━━━\n`; - menuText += `┃ 🎞️ MEDIA COMMAND\n`; - menuText += `┗━━━━━━━━━━━━━━━━━\n`; - categories.media.forEach(cmd => { - menuText += `│ ➜ ${config.prefix}${cmd.name}\n`; - }); - menuText += `\n`; - } - - // Fun Commands - if (categories.fun) { - menuText += `┏━━━━━━━━━━━━━━━━━\n`; - menuText += `┃ 🎭 FUN COMMAND\n`; - menuText += `┗━━━━━━━━━━━━━━━━━\n`; - categories.fun.forEach(cmd => { - menuText += `│ ➜ ${config.prefix}${cmd.name}\n`; - }); - menuText += `\n`; - } - - // Economy Commands - if (categories.economy) { - const economyCmds = categories.economy.filter( - (cmd) => !cmd.ownerOnly || extra.isOwner - ); - if (economyCmds.length) { - menuText += `┏━━━━━━━━━━━━━━━━━\n`; - menuText += `┃ 💰 ECONOMY COMMAND\n`; - menuText += `┗━━━━━━━━━━━━━━━━━\n`; - economyCmds.forEach(cmd => { - menuText += `│ ➜ ${config.prefix}${cmd.name}\n`; - }); - menuText += `\n`; - } - } - - // Utility Commands - if (categories.utility) { - menuText += `┏━━━━━━━━━━━━━━━━━\n`; - menuText += `┃ 🔧 UTILITY COMMAND\n`; - menuText += `┗━━━━━━━━━━━━━━━━━\n`; - categories.utility.forEach(cmd => { - menuText += `│ ➜ ${config.prefix}${cmd.name}\n`; - }); - menuText += `\n`; - } - - // Anime Commands - if (categories.anime) { - menuText += `┏━━━━━━━━━━━━━━━━━\n`; - menuText += `┃ 👾 ANIME COMMAND\n`; - menuText += `┗━━━━━━━━━━━━━━━━━\n`; - categories.anime.forEach(cmd => { - menuText += `│ ➜ ${config.prefix}${cmd.name}\n`; - }); - menuText += `\n`; - } - - // Textmaker Commands - if (categories.utility) { - menuText += `┏━━━━━━━━━━━━━━━━━\n`; - menuText += `┃ 🖋️ TEXTMAKER COMMAND\n`; - menuText += `┗━━━━━━━━━━━━━━━━━\n`; - categories.textmaker.forEach(cmd => { - menuText += `│ ➜ ${config.prefix}${cmd.name}\n`; - }); - menuText += `\n`; - } - - menuText += `╰━━━━━━━━━━━━━━━━━\n\n`; - menuText += `💡 Type ${config.prefix}help for more info\n`; - menuText += `🌟 Bot Version: 1.0.3\n`; - - // Send menu with image - const fs = require('fs'); - const path = require('path'); - const imagePath = path.join(__dirname, '../../utils/bot_image.jpg'); - - if (fs.existsSync(imagePath)) { - // Send image with newsletter forwarding context - const imageBuffer = fs.readFileSync(imagePath); - await sock.sendMessage(extra.from, { - image: imageBuffer, - caption: menuText, - mentions: [extra.sender], - contextInfo: { - forwardingScore: 1, - isForwarded: true, - forwardedNewsletterMessageInfo: { - newsletterJid: config.newsletterJid || '120363161513685998@newsletter', - newsletterName: config.botName, - serverMessageId: -1 + name: 'menu', + aliases: ['commands'], + category: 'general', + description: 'Display the bot menu', + usage: '.menu [category]', + + async execute(sock, msg, args, extra) { + try { + const commands = loadCommands(); + const categories = {}; + const unique = []; + + // Process commands: remove aliases, organize by category + for (const [name, cmd] of commands.entries()) { + if (cmd.name !== name) continue; + unique.push(cmd); + const cat = (cmd.category || 'other').toLowerCase(); + if (!categories[cat]) categories[cat] = []; + categories[cat].push(cmd); + } + + // Define icons for categories + const categoryIcons = { + general: '⚖️', ai: '🤖', group: '👥', admin: '🛡️', media: '🎵', + download: '📥', downloader: '📥', utility: '⚙️', tools: '🧰', + owner: '👑', fun: '🎲', anime: '🌸', economy: '💰', games: '🎮', + search: '🔍', convert: '🔄', textmaker: '🎨', nsfw: '🍁', + misc: '📦', other: '📁' + }; + + const owner = Array.isArray(config.ownerName) ? config.ownerName[0] : config.ownerName; + + // Define display order for main menu + const order = [ + 'general', 'ai', 'group', 'admin', 'media', 'download', + 'downloader', 'utility', 'tools', 'fun', 'anime', 'economy', + 'games', 'search', 'convert', 'textmaker', 'owner', 'misc', 'other' + ]; + + const sortedCategories = [ + ...order.filter(c => categories[c]), + ...Object.keys(categories).filter(c => !order.includes(c)).sort() + ]; + + const requested = (args[0] || '').toLowerCase(); + let text = ''; + + // =================================================== + // SECTION: CATEGORY MENU + // =================================================== + + if (requested) { + if (!categories[requested]) { + // Error State: Category not found + text = `❌ Category *${requested}* was not found.\n\nAvailable categories:\n\n${sortedCategories.join(', ')}\n\nExample:\n${config.prefix}menu ai`; + } else { + // Success State: Display commands in category with its dedicated emoji + const cmds = categories[requested].sort((a, b) => a.name.localeCompare(b.name)); + const catEmoji = categoryIcons[requested] || '📁'; + + text += createHeader(requested) + '\n\n'; + + for (const cmd of cmds) { + text += `${catEmoji} *${config.prefix}${cmd.name}*\n`; + } + + text += `\n${createSeparator()}\n`; + text += `💡 ${config.prefix}help `; + } + } + + // =================================================== + // SECTION: MAIN MENU + // =================================================== + + else { + const uptime = process.uptime(); + const hours = Math.floor(uptime / 3600); + const minutes = Math.floor((uptime % 3600) / 60); + const seconds = Math.floor(uptime % 60); + + const ram = (process.memoryUsage().rss / 1024 / 1024).toFixed(0); + + // Bot Info Header + text += createHeader(config.botName) + '\n\n'; + text += `👤 User: @${extra.sender.split('@')[0]}\n`; + text += `👑 Owner: ${owner}\n`; + text += `⚡ Prefix: ${config.prefix}\n`; + text += `📦 Commands: ${unique.length}\n`; + text += `📂 Categories: ${sortedCategories.length}\n`; + text += `💾 RAM: ${ram} MB\n`; + text += `⏱️ Uptime: ${hours}h ${minutes}m ${seconds}s\n`; + text += `🖥 Platform: ${process.platform}`; + + // Categories List + text += `\n\n${createSeparator()}\n\n`; + text += createHeader('Categories') + '\n\n'; + + for (const cat of sortedCategories) { + text += `${categoryIcons[cat] || '📁'} ${cat.charAt(0).toUpperCase() + cat.slice(1)} (${categories[cat].length})\n`; + } + + // Footer Instructions + text += `\n${createSeparator()}\n\n`; + text += `💡 ${config.prefix}menu \n`; + text += `💡 ${config.prefix}help \n\n`; + text += `Examples\n`; + text += `${config.prefix}menu ai\n`; + text += `${config.prefix}menu download`; } - } - }, { quoted: msg }); - } else { - await sock.sendMessage(extra.from, { - text: menuText, - mentions: [extra.sender] - }, { quoted: msg }); - } - - } catch (error) { - await extra.reply(`❌ Error: ${error.message}`); + + // =================================================== + // SEND MESSAGE + // =================================================== + + const imagePath = path.join(__dirname, '../../utils/bot_image.jpg'); + + if (fs.existsSync(imagePath)) { + // Send as image with caption (preferred) + await sock.sendMessage( + extra.from, + { + image: fs.readFileSync(imagePath), + caption: text, + mentions: [extra.sender], + contextInfo: { + forwardingScore: 1, + isForwarded: true, + forwardedNewsletterMessageInfo: { + newsletterJid: config.newsletterJid || '120363161513685996@newsletter', + newsletterName: config.botName, + serverMessageId: -1 + } + } + }, + { quoted: msg } + ); + } else { + // Fallback to text message + await sock.sendMessage( + extra.from, + { + text, + mentions: [extra.sender] + }, + { quoted: msg } + ); + } + + } catch (err) { + console.error(err); + await sock.sendMessage( + extra.from, + { + text: `❌ ${err.message}` + }, + { quoted: msg } + ); + } } - } }; diff --git a/commands/media/facebook.js b/commands/media/facebook.js index 1c057a09b..77ab1027d 100644 --- a/commands/media/facebook.js +++ b/commands/media/facebook.js @@ -2,172 +2,58 @@ * Facebook Downloader - Download Facebook videos */ -const { facebookdl } = require('@bochilteam/scraper-facebook'); -const axios = require('axios'); -const config = require('../../config'); - -// Store processed message IDs to prevent duplicates -const processedMessages = new Set(); +const axios = require("axios"); module.exports = { - name: 'facebook', - aliases: ['fb', 'fbdl', 'facebookdl'], - category: 'media', - description: 'Download Facebook videos', - usage: '.facebook ', - - async execute(sock, msg, args, extra) { + name: "facebook", + aliases: ["fb", "fbdl", "facebookdl"], + category: "media", + description: "Download Facebook videos.", + usage: "facebook ", + + async execute(sock, msg, args) { try { - // Check if message has already been processed - if (processedMessages.has(msg.key.id)) { - return; - } - - // Add message ID to processed set - processedMessages.add(msg.key.id); - - // Clean up old message IDs after 5 minutes - setTimeout(() => { - processedMessages.delete(msg.key.id); - }, 5 * 60 * 1000); - - const text = msg.message?.conversation || - msg.message?.extendedTextMessage?.text || - args.join(' '); - - if (!text) { - return await extra.reply('Please provide a Facebook link for the video.'); + if (!args.length) { + return await sock.sendMessage(msg.key.remoteJid, { + text: "❌ Please provide a Facebook video URL." + }, { quoted: msg }); } - - // Extract URL from command - const url = text.split(' ').slice(1).join(' ').trim(); - - if (!url) { - return await extra.reply('Please provide a Facebook link for the video.'); - } - - // Check for various Facebook URL formats - const facebookPatterns = [ - /https?:\/\/(?:www\.|m\.)?facebook\.com\//, - /https?:\/\/(?:www\.|m\.)?fb\.com\//, - /https?:\/\/fb\.watch\//, - /https?:\/\/(?:www\.)?facebook\.com\/watch/, - /https?:\/\/(?:www\.)?facebook\.com\/.*\/videos\// - ]; - - const isValidUrl = facebookPatterns.some(pattern => pattern.test(url)); - - if (!isValidUrl) { - return await extra.reply('That is not a valid Facebook link. Please provide a valid Facebook video link.'); + + const url = args.join(" "); + const api = `https://api.princetechn.com/api/download/facebook?apikey=prince&url=${encodeURIComponent(url)}`; + + const { data } = await axios.get(api); + + if (!data.success || !data.result) { + return await sock.sendMessage(msg.key.remoteJid, { + text: "❌ Failed to fetch the Facebook video." + }, { quoted: msg }); } - - await sock.sendMessage(extra.from, { - react: { text: '🔄', key: msg.key } - }); - - try { - // Use @bochilteam/scraper-facebook - const data = await facebookdl(url); - - if (!data || !data.video || !Array.isArray(data.video) || data.video.length === 0) { - throw new Error('No video data found'); - } - - // Get the highest quality video (first in array is usually highest) - const videoOption = data.video[0]; - if (!videoOption || !videoOption.download) { - throw new Error('No video download function found'); - } - - // Call the download function to get the video URL or buffer - const videoData = await videoOption.download(); - - let videoUrl = null; - let videoBuffer = null; - - // Check if it's a URL or buffer - if (typeof videoData === 'string') { - videoUrl = videoData; - } else if (Buffer.isBuffer(videoData)) { - videoBuffer = videoData; - } else if (videoData && videoData.url) { - videoUrl = videoData.url; - } else if (videoData && videoData.data) { - videoBuffer = Buffer.from(videoData.data); - } else { - throw new Error('Invalid video data format'); - } - - // Build caption with video info - const botName = config.botName.toUpperCase(); - let caption = `*DOWNLOADED BY ${botName}*`; - - const parts = []; - - if (data.duration) { - parts.push(`⏱️ Duration: ${data.duration}`); - } - - if (videoOption.quality) { - parts.push(`📹 Quality: ${videoOption.quality}`); - } - - if (parts.length > 0) { - caption += '\n\n' + parts.join('\n'); - } - - // Send video - if (videoBuffer) { - // Send as buffer - await sock.sendMessage(extra.from, { - video: videoBuffer, - mimetype: 'video/mp4', - caption: caption - }, { quoted: msg }); - } else if (videoUrl) { - // Try URL first - try { - await sock.sendMessage(extra.from, { - video: { url: videoUrl }, - mimetype: 'video/mp4', - caption: caption - }, { quoted: msg }); - } catch (urlError) { - // If URL fails, download and send as buffer - console.error('URL send failed, trying buffer method:', urlError.message); - try { - const videoResponse = await axios.get(videoUrl, { - responseType: 'arraybuffer', - timeout: 60000, - maxContentLength: 100 * 1024 * 1024, - headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', - 'Referer': 'https://www.facebook.com/' - } - }); - - const buffer = Buffer.from(videoResponse.data); - await sock.sendMessage(extra.from, { - video: buffer, - mimetype: 'video/mp4', - caption: caption - }, { quoted: msg }); - } catch (bufferError) { - console.error('Buffer method also failed:', bufferError.message); - throw new Error('Failed to send video'); - } - } - } else { - throw new Error('No video URL or buffer found'); - } - - } catch (error) { - console.error('Error in Facebook download:', error); - await extra.reply(`❌ Failed to download Facebook video.\n\nError: ${error.message}\n\nPlease try again with a different link.`); + + const videoUrl = data.result.hd_video || data.result.sd_video; + + if (!videoUrl) { + return await sock.sendMessage(msg.key.remoteJid, { + text: "❌ No downloadable video was found." + }, { quoted: msg }); } - } catch (error) { - console.error('Error in Facebook command:', error); - await extra.reply('An error occurred while processing the request. Please try again later.'); + + await sock.sendMessage( + msg.key.remoteJid, + { + video: { url: videoUrl }, + mimetype: "video/mp4", + caption: `📹 *${data.result.title || "Facebook Video"}*\n\n⏱ Duration: ${data.result.duration || "Unknown"}` + }, + { quoted: msg } + ); + + } catch (err) { + console.error(err); + + await sock.sendMessage(msg.key.remoteJid, { + text: "❌ An error occurred while downloading the Facebook video." + }, { quoted: msg }); } } }; diff --git a/commands/media/spotify.js b/commands/media/spotify.js new file mode 100644 index 000000000..51c32f532 --- /dev/null +++ b/commands/media/spotify.js @@ -0,0 +1,57 @@ +const axios = require('axios'); + +module.exports = { + name: 'spotify', + aliases: ['sp', 'spotifymp3', 'spotifydl'], + category: 'media', + description: 'Download a Spotify track as MP3.', + usage: '', + + async execute(sock, msg, args) { + try { + if (!args.length) { + return sock.sendMessage( + msg.key.remoteJid, + { + text: `❌ Please provide a Spotify track URL.\n\nExample:\n.spotify https://open.spotify.com/track/...` + }, + { quoted: msg } + ); + } + + const url = args[0]; + + const { data } = await axios.get( + `https://api-olive-five-53.vercel.app/spotify?url=${encodeURIComponent(url)}` + ); + + if (!data?.data?.download) { + return sock.sendMessage( + msg.key.remoteJid, + { text: '❌ Failed to fetch the download link.' }, + { quoted: msg } + ); + } + + await sock.sendMessage( + msg.key.remoteJid, + { + document: { url: data.data.download }, + mimetype: 'audio/mpeg', + fileName: 'spotify.mp3' + }, + { quoted: msg } + ); + } catch (err) { + console.error(err); + + await sock.sendMessage( + msg.key.remoteJid, + { + text: `❌ An error occurred while downloading the Spotify track.\n\n${err.response?.data?.message || err.message}` + }, + { quoted: msg } + ); + } + } +}; diff --git a/commands/media/tiktok.js b/commands/media/tiktok.js index b151560fb..f7ff8a24c 100644 --- a/commands/media/tiktok.js +++ b/commands/media/tiktok.js @@ -2,189 +2,74 @@ * TikTok Downloader - Download TikTok videos */ -const { ttdl } = require('ruhend-scraper'); -const axios = require('axios'); -const APIs = require('../../utils/api'); -const config = require('../../config'); - -// Store processed message IDs to prevent duplicates -const processedMessages = new Set(); +const axios = require("axios"); module.exports = { - name: 'tiktok', - aliases: ['tt', 'ttdl', 'tiktokdl'], - category: 'media', - description: 'Download TikTok videos', - usage: '.tiktok ', - + name: "tiktok", + aliases: ["tt", "ttdl", "tik", "tikdl"], + category: "media", + description: "Download TikTok videos without watermark.", + usage: "tiktok ", + async execute(sock, msg, args) { try { - // Check if message has already been processed - if (processedMessages.has(msg.key.id)) { - return; - } - - // Add message ID to processed set - processedMessages.add(msg.key.id); - - // Clean up old message IDs after 5 minutes - setTimeout(() => { - processedMessages.delete(msg.key.id); - }, 5 * 60 * 1000); - - const text = msg.message?.conversation || - msg.message?.extendedTextMessage?.text || - args.join(' '); - - if (!text) { - return await sock.sendMessage(msg.key.remoteJid, { - text: 'Please provide a TikTok link for the video.' - }, { quoted: msg }); + if (!args.length) { + return await sock.sendMessage( + msg.key.remoteJid, + { + text: "❌ Please provide a TikTok video URL." + }, + { quoted: msg } + ); } - - // Extract URL from command - const url = text.split(' ').slice(1).join(' ').trim(); - - if (!url) { - return await sock.sendMessage(msg.key.remoteJid, { - text: 'Please provide a TikTok link for the video.' - }, { quoted: msg }); - } - - // Check for various TikTok URL formats - const tiktokPatterns = [ - /https?:\/\/(?:www\.)?tiktok\.com\//, - /https?:\/\/(?:vm\.)?tiktok\.com\//, - /https?:\/\/(?:vt\.)?tiktok\.com\//, - /https?:\/\/(?:www\.)?tiktok\.com\/@/, - /https?:\/\/(?:www\.)?tiktok\.com\/t\// - ]; - - const isValidUrl = tiktokPatterns.some(pattern => pattern.test(url)); - - if (!isValidUrl) { - return await sock.sendMessage(msg.key.remoteJid, { - text: 'That is not a valid TikTok link. Please provide a valid TikTok video link.' - }, { quoted: msg }); + + const url = args.join(" "); + const api = `https://api.princetechn.com/api/download/tiktok?apikey=prince&url=${encodeURIComponent(url)}`; + + const { data } = await axios.get(api); + + if (!data.success || !data.result) { + return await sock.sendMessage( + msg.key.remoteJid, + { + text: "❌ Failed to fetch the TikTok video." + }, + { quoted: msg } + ); } - - await sock.sendMessage(msg.key.remoteJid, { - react: { text: '🔄', key: msg.key } - }); - - try { - let videoUrl = null; - let title = null; - - // Try Siputzx API first - try { - const result = await APIs.getTikTokDownload(url); - videoUrl = result.videoUrl; - title = result.title; - } catch (apiError) { - console.error(`Siputzx API failed: ${apiError.message}`); - } - - // If Siputzx API didn't work, try ttdl method - if (!videoUrl) { - try { - let downloadData = await ttdl(url); - if (downloadData && downloadData.data && downloadData.data.length > 0) { - const mediaData = downloadData.data; - for (let i = 0; i < Math.min(20, mediaData.length); i++) { - const media = mediaData[i]; - const mediaUrl = media.url; - const isVideo = /\.(mp4|mov|avi|mkv|webm)$/i.test(mediaUrl) || media.type === 'video'; - - if (isVideo) { - await sock.sendMessage(msg.key.remoteJid, { - video: { url: mediaUrl }, - mimetype: 'video/mp4', - caption: `*DOWNLOADED BY ${config.botName.toUpperCase()}*` - }, { quoted: msg }); - } else { - await sock.sendMessage(msg.key.remoteJid, { - image: { url: mediaUrl }, - caption: `*DOWNLOADED BY ${config.botName.toUpperCase()}*` - }, { quoted: msg }); - } - } - return; - } - } catch (ttdlError) { - console.error('ttdl fallback also failed:', ttdlError.message); - } - } - - // Send the video if we got a URL - if (videoUrl) { - try { - // Download video as buffer - const videoResponse = await axios.get(videoUrl, { - responseType: 'arraybuffer', - timeout: 60000, - maxContentLength: 100 * 1024 * 1024, // 100MB limit - headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - 'Accept': 'video/mp4,video/*,*/*;q=0.9', - 'Accept-Language': 'en-US,en;q=0.9', - 'Accept-Encoding': 'gzip, deflate, br', - 'Connection': 'keep-alive', - 'Referer': 'https://www.tiktok.com/' - } - }); - - const videoBuffer = Buffer.from(videoResponse.data); - - if (videoBuffer.length === 0) { - throw new Error('Video buffer is empty'); - } - - const botName = config.botName.toUpperCase(); - const caption = title ? `*DOWNLOADED BY ${botName}*\n\n📝 Title: ${title}` : `*DOWNLOADED BY ${botName}*`; - - await sock.sendMessage(msg.key.remoteJid, { - video: videoBuffer, - mimetype: 'video/mp4', - caption: caption - }, { quoted: msg }); - - return; - } catch (downloadError) { - console.error(`Failed to download video: ${downloadError.message}`); - // Fallback to URL method - try { - const botName = config.botName.toUpperCase(); - const caption = title ? `*DOWNLOADED BY ${botName}*\n\n📝 Title: ${title}` : `*DOWNLOADED BY ${botName}*`; - - await sock.sendMessage(msg.key.remoteJid, { - video: { url: videoUrl }, - mimetype: 'video/mp4', - caption: caption - }, { quoted: msg }); - return; - } catch (urlError) { - console.error(`URL method also failed: ${urlError.message}`); - } - } - } - - // If we reach here, no method worked - return await sock.sendMessage(msg.key.remoteJid, { - text: '❌ Failed to download TikTok video. All download methods failed. Please try again with a different link.' - }, { quoted: msg }); - - } catch (error) { - console.error('Error in TikTok download:', error); - await sock.sendMessage(msg.key.remoteJid, { - text: 'Failed to download the TikTok video. Please try again with a different link.' - }, { quoted: msg }); + + const { title, duration, video, author } = data.result; + + if (!video) { + return await sock.sendMessage( + msg.key.remoteJid, + { + text: "❌ No downloadable video was found." + }, + { quoted: msg } + ); } - } catch (error) { - console.error('Error in TikTok command:', error); - await sock.sendMessage(msg.key.remoteJid, { - text: 'An error occurred while processing the request. Please try again later.' - }, { quoted: msg }); + + await sock.sendMessage( + msg.key.remoteJid, + { + video: { url: video }, + mimetype: "video/mp4", + caption: `🎵 *${title || "TikTok Video"}*\n\n👤 Author: ${author?.name || "Unknown"}\n⏱ Duration: ${duration || 0}s` + }, + { quoted: msg } + ); + + } catch (err) { + console.error(err); + + await sock.sendMessage( + msg.key.remoteJid, + { + text: "❌ An error occurred while downloading the TikTok video." + }, + { quoted: msg } + ); } } -}; \ No newline at end of file +}; diff --git a/commands/media/video.js b/commands/media/video.js index c7ee97f0b..29b7122ff 100644 --- a/commands/media/video.js +++ b/commands/media/video.js @@ -7,17 +7,18 @@ const APIs = require('../../utils/api'); const config = require('../../config'); module.exports = { - name: 'ytvideo', - aliases: ['ytv', 'ytmp4', 'ytvid', 'video'], + name: 'ytdoc', + aliases: ['yt', 'ytvd'], category: 'media', - description: 'Download video from YouTube', + description: 'Download video from YouTube document format', usage: '.video