Skip to content

Instantly share code, notes, and snippets.

@weskerty
Last active April 19, 2025 22:35
Show Gist options
  • Save weskerty/6aa65e1bd6f88fd77c2adce5aa081c7c to your computer and use it in GitHub Desktop.
Save weskerty/6aa65e1bd6f88fd77c2adce5aa081c7c to your computer and use it in GitHub Desktop.
Get Media Public Server
const fs = require('fs').promises;
const path = require('path');
const { bot } = require('../lib');
const config = {
media: {
directory: process.env.AMULEDOWNLOADS || path.join(process.cwd(), 'tmp'),
maxFilesToSend: 20,
maxFileSize: Number(process.env.MAX_UPLOAD || 1500) * 1048576 // 1.5 GB
},
timeout: {
selection: 60000, // 1 minuto
},
messages: {
fileTooLarge: "El archivo es demasiado grande para enviar. Max ({maxSize} MB).",
usageGuide: "Uso del comando:\n> gm <nombre> para buscar un archivo.\n> gm dd l1,l2,l3 para descargar archivos específicos de búsqueda local.",
noResultsFound: "No se encontraron resultados para: *{query}*.",
sendingFiles: "Enviando: \n{files}",
oversizedFiles: "Los siguientes archivos exceden el límite de tamaño ({size}) y no se enviaron:\n- {files}",
queueMessage: "⏳ Estás en cola. Posición: #{position}",
waitingForSelection: "ℹ️ Usa `gm dd` L1,l#,etc... para seleccionar. Tienes {timeout} segundos.",
searchQueued: "🔍 Tu búsqueda ha sido encolada. Posición: #{position}"
}
};
// Tipos de archivo permitidos
const FILE_TYPES = {
video: { extensions: ['mp4', 'mkv', 'avi'], mimetype: 'video/mp4' },
image: { extensions: ['jpg', 'jpeg', 'png', 'gif'], mimetype: 'image/jpeg' },
document: {
extensions: ['pdf', 'epub', 'docx', 'txt', 'apk', 'apks'],
mimetypes: {
'pdf': 'application/pdf',
'epub': 'application/epub+zip',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'txt': 'text/plain',
'apk': 'application/vnd.android.package-archive',
'default': 'application/octet-stream'
}
},
audio: { extensions: ['mp3', 'wav', 'ogg', 'flac'], mimetype: 'audio/mpeg' }
};
const globalQueue = {
activeSearches: 0,
searchQueue: [],
searchResults: new Map(),
searchTimeout: null,
addSearch(searchTask, message) {
if (this.activeSearches === 0) {
this.activeSearches = 1;
this.processSearch(searchTask, message);
return 0;
} else {
this.searchQueue.push({ searchTask, message });
return this.searchQueue.length;
}
},
async processSearch(searchTask, message) {
try {
await searchTask();
} catch (error) {
console.error("Error procesando búsqueda:", error);
try {
await message.send(`⚠️ Error en la búsqueda: ${error.message}`, { quoted: message.data });
} catch (replyError) {
console.error("Error al enviar mensaje de error:", replyError);
}
}
this.searchTimeout = setTimeout(() => {
this.completeSearch();
}, config.timeout.selection);
},
completeSearch() {
this.activeSearches = 0;
this.searchResults.clear();
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
this.searchTimeout = null;
}
if (this.searchQueue.length > 0) {
const { searchTask, message } = this.searchQueue.shift();
this.activeSearches = 1;
this.processSearch(searchTask, message);
}
},
setResults(results) {
this.searchResults.clear();
results.forEach(file => {
this.searchResults.set(file.index, file);
});
},
getSelectedFiles(indices) {
return indices
.filter(item => item.type === 'l')
.map(item => this.searchResults.get(item.index))
.filter(file => file !== undefined);
},
hasActiveSearch() {
return this.activeSearches > 0;
},
clearResults() {
this.searchResults.clear();
}
};
const utils = {
formatMessage(message, replacements = {}) {
return Object.entries(replacements).reduce((formattedMsg, [placeholder, value]) =>
formattedMsg.replace(new RegExp(`{${placeholder}}`, 'g'), value), message);
},
async delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
},
parseIndices(input) {
if (!input) return [];
return input
.toLowerCase()
.split(/[,\s]+/)
.map(item => {
const match = item.match(/^([l])(\d+)$/i);
if (!match) return null;
return {
type: match[1].toLowerCase(),
index: parseInt(match[2])
};
})
.filter(item => item !== null);
},
formatTimeInSeconds(milliseconds) {
const seconds = Math.floor(milliseconds / 1000);
return seconds;
}
};
class FileUtils {
static async getStats(filePath) {
try {
return await fs.stat(filePath);
} catch (error) {
console.error(`Error al verificar estadísticas del archivo ${filePath}:`, error);
return null;
}
}
static async getSize(filePath) {
const stats = await this.getStats(filePath);
return stats ? stats.size : null;
}
static async isSizeValid(filePath, maxSize) {
const size = await this.getSize(filePath);
return size !== null && size <= maxSize;
}
static formatSize(sizeInBytes) {
if (!sizeInBytes || isNaN(sizeInBytes)) return "Desconocido";
const kb = sizeInBytes / 1024;
if (kb < 1024) return `${kb.toFixed(2)} KB`;
const mb = kb / 1024;
if (mb < 1024) return `${mb.toFixed(2)} MB`;
return `${(mb / 1024).toFixed(2)} GB`;
}
static getDetails(filePath) {
const ext = path.extname(filePath).slice(1).toLowerCase();
for (const [category, typeInfo] of Object.entries(FILE_TYPES)) {
if (typeInfo.extensions.includes(ext)) {
return {
category,
mimetype: category === 'document'
? (typeInfo.mimetypes[ext] || typeInfo.mimetypes.default)
: typeInfo.mimetype
};
}
}
return {
category: 'document',
mimetype: FILE_TYPES.document.mimetypes.default
};
}
static async search(directory, keyword, results = []) {
try {
const entries = await fs.readdir(directory, { withFileTypes: true });
await Promise.all(entries.map(async entry => {
const fullPath = path.join(directory, entry.name);
if (entry.isDirectory()) {
await this.search(fullPath, keyword, results);
} else if (entry.name.toLowerCase().includes(keyword.toLowerCase())) {
results.push(fullPath);
}
}));
} catch (error) {
console.error(`Error: Buscando en ${directory}: ${error.message}`);
}
return results;
}
}
bot(
{
pattern: 'gm ?(.*)',
fromMe: true,
desc: 'Buscar y enviar archivos en el directorio de medios',
type: 'download',
},
async (message, match) => {
const query = (match || '').trim();
const reply = async (text, isError = false) => {
try {
await message.send(`${isError ? '⚠️' : '✅'} ${text}`, { quoted: message.data });
} catch (replyError) {
console.error('Error: Error en Respuesta', replyError);
}
};
try {
if (!query) {
return await reply(config.messages.usageGuide, true);
}
if (query.startsWith('dd')) {
if (!globalQueue.hasActiveSearch()) {
return await reply("No hay una búsqueda activa para seleccionar archivos.", true);
}
return await handleSelection(query.slice(2).trim(), message, reply);
}
const queuePosition = globalQueue.addSearch(
async () => {
await handleSearch(query, message, reply);
},
message
);
if (queuePosition > 0) {
await reply(utils.formatMessage(config.messages.searchQueued, { position: queuePosition }));
}
} catch (error) {
console.error('Error GM Plugin:', error);
await message.send(`⚠️ ${error.message}`, { quoted: message.data });
}
}
);
async function handleSearch(query, message, reply) {
try {
const foundFiles = await FileUtils.search(config.media.directory, query);
if (foundFiles.length === 0) {
globalQueue.completeSearch();
return await reply(utils.formatMessage(config.messages.noResultsFound, { query }), true);
}
const fileResults = await Promise.all(foundFiles.map(async (filePath, index) => {
const size = await FileUtils.getSize(filePath);
const formattedSize = size ? FileUtils.formatSize(size) : "Desconocido";
return {
index: index + 1,
path: filePath,
name: path.basename(filePath),
size,
formattedSize
};
}));
globalQueue.setResults(fileResults);
const timeoutSeconds = utils.formatTimeInSeconds(config.timeout.selection);
const selectionMessage = utils.formatMessage(config.messages.waitingForSelection, {
timeout: timeoutSeconds
});
let resultMessage = `${selectionMessage}\n✅ Resultados Local (${fileResults.length}):\n`;
fileResults.forEach(file => {
resultMessage += `\`L${file.index}.\` ${file.name} \n> Tamaño ${file.formattedSize}\n`;
});
await reply(resultMessage);
} catch (error) {
console.error('Error en búsqueda:', error);
await reply(`Error en búsqueda: ${error.message}`, true);
globalQueue.completeSearch();
}
}
async function handleSelection(selectionStr, message, reply) {
try {
const selectedIndices = utils.parseIndices(selectionStr);
if (selectedIndices.length === 0) {
return await reply("Formato de selección incorrecto. Ejemplo: l1,l2,l3", true);
}
const selectedFiles = globalQueue.getSelectedFiles(selectedIndices);
if (selectedFiles.length === 0) {
return await reply("No se encontraron archivos válidos en la selección.", true);
}
await reply(utils.formatMessage(config.messages.sendingFiles, {
files: selectedFiles.map(file => `${file.name} (${file.formattedSize})`).join('\n')
}));
const oversizedFiles = [];
for (const file of selectedFiles) {
try {
const isValid = await FileUtils.isSizeValid(file.path, config.media.maxFileSize);
if (!isValid) {
oversizedFiles.push(file.name);
continue;
}
const fileBuffer = await fs.readFile(file.path);
const { mimetype, category } = FileUtils.getDetails(file.path);
const fileStats = await FileUtils.getStats(file.path);
const creationDate = fileStats ? new Date(fileStats.birthtime).toLocaleString() : "Fecha desconocida";
await message.send(
fileBuffer,
{
fileName: file.name,
mimetype,
caption: `Fecha de creación: ${creationDate}`,
quoted: message.data
},
category
);
} catch (fileError) {
console.error(`Error: Enviando Archivo ${file.path}:`, fileError);
await reply(`Error al enviar archivo ${file.name}: ${fileError.message}`, true);
}
}
if (oversizedFiles.length > 0) {
await reply(utils.formatMessage(config.messages.oversizedFiles, {
size: FileUtils.formatSize(config.media.maxFileSize),
files: oversizedFiles.join('\n- ')
}), true);
}
globalQueue.completeSearch();
} catch (error) {
console.error("Error procesando selección:", error);
await reply(`Error en selección: ${error.message}`, true);
globalQueue.completeSearch();
}
}
module.exports = {};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment