/* eslint-disable max-len */
/* Requires */
const ffmpegloc = require('@ffmpeg-installer/ffmpeg');
const ffmpeg = require('fluent-ffmpeg');
const fs = require('fs');
const path = require('path');
const Search = require('yt-search');
const YouTube = require('youtube-dl-exec');
const Indexer = require('../../index');
/* JSON's | Utilidades */
const envInfo = JSON.parse(fs.readFileSync(`${__dirname}/utils.json`));
const canAdvise = global.config?.waitMessage?.value;
const params = envInfo.parameters.systemSets.value;
params.audio.ffmpegLocation = ffmpegloc.path;
ffmpeg.setFfmpegPath(ffmpegloc.path);
/**
* Retorna todos os detalhes do ambiente (`envInfo`).
*
* @returns {Object} O objeto `envInfo`, que contém os detalhes do ambiente da execução.
*/
function ambientDetails() {
/* Retorna a envData */
return envInfo;
}
/* Função que converte o arquivo em AAC para funcionar com IOS */
async function fixForIOS(inputPath, outputPath) {
/* Retorna uma Promise, é o meio mais simples de esperar essa conversão terminar */
return new Promise((resolve) => {
/* Executa o ffmpeg com os argumentos */
(ffmpeg()
.input(inputPath)
.audioChannels(1)
.audioCodec('opus')
.toFormat('ogg')
.addOutputOptions('-avoid_negative_ts make_zero')
.save(outputPath)
/* Quando terminar */
.on('end', () => {
/* Deleta o arquivo antigo */
Indexer('clear').destroy(inputPath);
/* Define o retorno como o arquivo novo */
resolve(outputPath);
})
/* Se houver erro */
.on('error', () => {
/* Retorna para parar */
resolve('dontDownload');
})
);
});
}
/* Função que faz a busca dos vídeos */
async function searchYouTube(
seaTerms = envInfo.functions.search.arguments.seaTerms.value,
kill = envInfo.functions.search.arguments.kill.value,
chatId = envInfo.functions.search.arguments.chatId.value,
reply = envInfo.functions.search.arguments.quoteThis.value,
) {
/* Define os resultados padrões */
const envResults = {
success: false,
type: 'Boolean / Array',
value: false,
};
/* Try-Catch para casos de erro */
try {
/* Caso o texto seja invalido */
if (typeof seaTerms === 'string') {
/* Executa a busca */
const responseBase = await Search(seaTerms);
/* Define um valor alterável */
const searchResults = responseBase;
/* Mescla valores */
searchResults.all = searchResults.all.concat(searchResults.video).flat(5);
/* Corrige valores nulos */
searchResults.all = searchResults.all.filter((res) => res != null).flat(5);
/* Só roda se tiver 1 ou + resultados */
if (searchResults.all.length > 0) {
/* Filtra somente os vídeos */
searchResults.all = searchResults.all.filter((res) => !res.url.includes('playlist') && res.type === 'video');
/* Organiza pelo resultado mais provável */
searchResults.all = searchResults.all.filter((res) => {
/* Informações dos vídeos */
let videoData = [
(res.videoId || ''),
(res.url || ''),
(res.title || ''),
(res.description || ''),
(res.author?.name || ''),
(res.author?.url || ''),
];
/* Transforma as informações em minusculas */
videoData = videoData.map((ponse) => ponse.toLowerCase());
/* Retorna o valor do filter */
return videoData.includes(seaTerms);
}).flat(5);
/* Adiciona os outros resultados de antes, caso o filter acima falhe */
envResults.value = searchResults.all.concat(responseBase.videos).concat(responseBase.all).flat(5);
}
}
/* Define o sucesso */
envResults.success = true;
/* Caso de algum erro */
} catch (error) {
/* Printa usando a Indexer */
Indexer('color').report(error, 'YouTube');
/* Avisa que deu erro enviando o comando e data atual pro sistema SER */
await kill.sendMessage(chatId, {
text: Indexer('sql').languages(region, 'S.E.R', error, true, true, {
command: 'YOUTUBE',
time: (new Date()).toLocaleString(),
}).value,
}, reply);
}
/* Retorna o que encontrou */
return envResults;
}
/* Cria uma função de obter o video/áudio/info */
async function createDownload(
linkURL = envInfo.functions.down.arguments.linkURL.value,
searchType = envInfo.functions.down.arguments.searchType.value,
kill = envInfo.functions.down.arguments.kill.value,
chatId = envInfo.functions.down.arguments.chatId.value,
reply = envInfo.functions.down.arguments.quoteThis.value,
haveURL = envInfo.functions.down.arguments.haveURL.value,
) {
/* Define os resultados padrões */
const envFinish = {
success: false,
type: 'Boolean / Array',
value: false,
};
/* Try-Catch para casos de erro */
try {
/* Só executa se for uma URL */
if (Indexer('regexp').urls(linkURL).value.isURL) {
/* Define os parametros de download */
const downOptions = params[searchType];
/* Se áudio */
if (searchType === 'audio') {
/* Define a pasta de download */
downOptions.o = path.normalize(`${__dirname}/Cache/${Indexer('strings').generate(10).value}.mp3`);
}
/* Se for URL externa */
if (haveURL.isURL) {
/* Remove o filtro que dará erros */
delete downOptions.matchFilters;
}
/* Faz o download */
let youTubeMedia = null;
await YouTube(linkURL, downOptions).then((result) => { youTubeMedia = result; }).catch(() => { youTubeMedia = 'dontDownload'; });
/* Define como youTubeMedia */
envFinish.value = youTubeMedia;
/* Se for audio, faz funcionar no Iphone via encoding do codec opus */
if (searchType === 'audio' && youTubeMedia != null && youTubeMedia !== 'dontDownload') {
/* Define o novo arquivo */
const newAudioFile = path.normalize(`${__dirname}/Cache/${Indexer('strings').generate(10).value}.ogg`);
/* Executa o ajuste para Iphones */
envFinish.value = await fixForIOS(downOptions.o, newAudioFile);
}
/* Se retornar vazio, como em casos de peso > 16MB */
if (youTubeMedia == null || youTubeMedia === 'dontDownload' || envFinish.value === 'dontDownload' || envFinish.value == null) {
/* Avisa a pessoa */
await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Play', 'Failed', true, true, envInfo).value }, reply);
/* Se certifica se não baixar */
envFinish.value = 'dontDownload';
}
}
/* Define como sucesso */
envFinish.success = true;
/* Caso de algum erro */
} catch (error) {
/* Se for áudio... */
if (searchType === 'audio') {
/* Limpa ele */
Indexer('clear').destroy(path.normalize(params[searchType].o));
}
/* Printa usando a Indexer */
Indexer('color').report(error, 'YouTube');
/* Avisa que deu erro enviando o comando e data atual pro sistema SER */
await kill.sendMessage(chatId, {
text: Indexer('sql').languages(region, 'S.E.R', error, true, true, {
command: 'YOUTUBE',
time: (new Date()).toLocaleString(),
}).value,
}, reply);
}
/* Retorna o que encontrou */
return envFinish;
}
/* Cria a função de comando */
async function youTubeDownloader(
kill = envInfo.functions.exec.arguments.kill.value,
env = envInfo.functions.exec.arguments.env.value,
) {
/* Define um resultado padrão */
envInfo.results.value = false;
/* Define o sucesso */
envInfo.results.success = false;
/* Try-Catch para casos de erro */
try {
/* Verifica se recebeu parâmetros corretos */
if (typeof kill === 'object' && typeof env === 'object') {
/* Importa os parâmetros que precisa */
const {
chatId,
reply,
arks,
isOwner,
command,
} = env.value;
/* Define o alias na envInfo */
envInfo.alias = env.value.alias;
/* Ajusta a body */
const body = env.value.body.replace(/(https:\/\/www\.youtube\.com|https:\/\/youtube\.com)\/shorts\/|\?si=.*$|\?feature.*$|-audio|-video|-link|^ /g, '');
/* Define o menu de ajuda para devs */
if (arks.includes('--help-dev') && isOwner === true) {
/* Manda a mensagem de ajuda de dev */
envInfo.results.value = await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Helper', 'Developer', true, true, envInfo).value }, reply);
/* Se não tiver nada */
} else if (arks.includes('--help') || arks.length === 0) {
/* Envia menu de ajuda sem informações sigilosas */
envInfo.results.value = await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Helper', 'User', true, true, envInfo).value }, reply);
/* Se for uso normal */
} else {
/* Se permitido os avisos e não for só uma search */
if (canAdvise === true && command !== 'ytsearch' && command !== 'videosearch') {
/* Avisa para esperar, pois vai pesquisar, filtrar, escolher a qualidade, baixar e enviar, demora */
if (config.waitMessage.value) await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Extras', 'Wait', true, true, envInfo).value }, reply);
}
/* Define qual vai ser o formato */
let downFormat = (arks.includes('-audio') || command === 'play' || command === 'downaudio' || command === 'youtube') ? 'audio' : 'video';
/* Verifica se a pessoa quer um formato e usou comando errado e se ela só queria pesquisar */
downFormat = arks.includes('-video') ? 'video' : downFormat;
downFormat = command.includes('search') && !arks.includes('-video') && !arks.includes('-audio') && !arks.includes('-link') ? 'audio' : downFormat;
downFormat = arks.includes('-link') ? 'video' : downFormat;
/* Define se tem URL e o formato de resultados de busca do YouTube */
const haveURL = Indexer('regexp').urls(body).value;
let youTubeData = {
value: [],
success: false,
};
/* Se for link de YouTube */
if (haveURL.matchedURL.includes('youtube.com') || haveURL.matchedURL.includes('youtu.be')) {
/* Redefine como não sendo URL */
haveURL.isURL = false;
haveURL.matchedURL = 'none';
}
/* Define se é para YouTube ou além */
if (haveURL.isURL === false) {
/* Pesquisa pelo video */
youTubeData = await searchYouTube(body, kill, chatId, reply);
}
/* Se não encontrar ou tiver erros */
if ((youTubeData.value.length !== 0 && youTubeData.success === true && Array.isArray(youTubeData.value)) || haveURL.isURL === true) {
/* Se não for uma busca de YouTube */
if (haveURL.isURL === false) {
/* Define o download */
[youTubeData.value] = [youTubeData.value[0]];
/* Se for, define a URL direto */
} else youTubeData.value.url = haveURL.matchedURL;
/* Envia o link se for YouTube */
if (downFormat !== 'video' && !arks.includes('-link') && haveURL.isURL === false) {
/* Se não for video, senão manda 2 mensagens quase iguais */
await kill.sendMessage(chatId, { image: { url: youTubeData.value.image }, caption: Indexer('sql').languages(region, 'Play', 'Details', true, true, youTubeData.value).value }, reply);
}
/* Se foi só uma busca, cancela */
if (command.includes('search') && !arks.includes('-video') && !arks.includes('-audio') && !arks.includes('-link')) return logging.postResults(envInfo);
/* Ajusta para reel em vez de reels, senão o instagram não baixará */
youTubeData.value.url = youTubeData.value.url.replace(/\/reels\//gi, '/reel/');
/* Inicia o download */
const baixarYouTube = await createDownload(youTubeData.value.url, downFormat, kill, chatId, reply, haveURL);
/* Se deu erro, cancela aqui */
if (baixarYouTube.success === false || baixarYouTube.value === 'dontDownload') return logging.postResults(envInfo);
/* Verifica se o download deu errado */
if (
(fs.existsSync(baixarYouTube.value))
|| (downFormat === 'video' && Indexer('regexp').urls(baixarYouTube.value).value.isURL)
) {
/* Se for só obter Link */
if (arks.includes('-link')) {
/* Envia a URL e armazena a ID */
await kill.sendMessage(chatId, { text: baixarYouTube.value }, reply);
/* Se for áudio */
} else if (downFormat === 'audio') {
/* Envia como PTT e armazena a ID/PTT */
await kill.sendMessage(chatId, { audio: { url: baixarYouTube.value }, mimetype: 'audio/mp4', ptt: true }, reply);
/* Se for video YouTube */
} else if (downFormat === 'video' && haveURL.isURL === false) {
/* Envia como arquivo URL e armazena a ID */
await kill.sendMessage(chatId, { video: { url: baixarYouTube.value }, mimetype: 'video/mp4', caption: Indexer('sql').languages(region, 'Play', 'Details', true, true, youTubeData.value).value }, reply);
/* Se for outro video */
} else {
/* Envia como arquivo URL e armazena a ID */
await kill.sendMessage(chatId, { video: { url: baixarYouTube.value }, mimetype: 'video/mp4' }, reply);
}
/* Se for áudio... */
if (downFormat === 'audio') {
/* Limpa ele */
Indexer('clear').destroy(baixarYouTube.value);
}
/* Retorna um valor */
return envInfo.results;
}
/* ...Se não tem resultados */
} else {
/* Envia a mensagem padrão */
envInfo.results.value = await kill.sendMessage(chatId, { text: Indexer('sql').languages(region, 'Play', 'Empty', true, true, envInfo).value }, reply);
}
}
}
/* Define o sucesso */
envInfo.results.success = true;
/* Caso de algum erro */
} catch (error) {
/* Insere tudo na envInfo */
logging.echoError(error, envInfo, __dirname);
/* Avisa que deu erro enviando o comando e data atual pro sistema SER */
await kill.sendMessage(env.value.chatId, {
text: Indexer('sql').languages(region, 'S.E.R', error, true, true, {
command: 'YOUTUBE',
time: (new Date()).toLocaleString(),
}).value,
}, env.value.reply);
}
/* Retorna a nova Array */
return logging.postResults(envInfo);
}
/**
* Restaura o ambiente e atualiza as exportações do módulo com a funcionalidade principal
* @param {Object} [changeKey={}] - Chaves personalizadas para atualizar o envInfo
* @param {Object} [envFile=envInfo] - Objeto com informações do ambiente
* @param {string} [dirname=__dirname] - Caminho do diretório atual
* @returns {Object} Exportações do módulo com todas as funções configuradas
*/
/* eslint-disable-next-line no-return-assign */
const resetLocal = (
changeKey = {},
envFile = envInfo,
dirname = __dirname,
) => module.exports = logging.resetAmbient({
functions: {
[envInfo.exports.env]: { value: ambientDetails },
[envInfo.exports.messedup]: { value: logging.echoError },
[envInfo.exports.poswork]: { value: logging.postResults },
[envInfo.exports.reset]: { value: resetLocal },
[envInfo.exports.search]: { value: searchYouTube },
[envInfo.exports.down]: { value: createDownload },
[envInfo.exports.exec]: { value: youTubeDownloader },
[envInfo.exports.ios]: { value: fixForIOS },
},
parameters: {
location: { value: __filename },
},
}, envFile, changeKey, dirname);
resetLocal();