const { EmbedBuilder } = require('discord.js'); const { DebugBuilder } = require("./utilities/debugBuilder"); const log = new DebugBuilder("server", "libUtils"); const { NodeHtmlMarkdown } = require('node-html-markdown'); const { parse } = require("node-html-parser"); const crypto = require("crypto"); const imageRegex = /(http(s?):)([/|.|\w|\s|-])*((\.(?:jpg|gif|png|webm))|(\/gallery\/(?:[/|.|\w|\s|-])*))/g; const youtubeVideoRegex = /((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube(-nocookie)?\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)/g exports.EmmeliaEmbedBuilder = class PostEmbedBuilder extends EmbedBuilder { constructor() { super() this.setTimestamp(); this.setFooter({ text: 'Brought to you by Emmelia.' }); } } /** * sleep - sleep/wait * @constructor */ exports.runAfter = async (toRun, timeout = 10000) => { log.DEBUG(`Running '${toRun}' after ${timeout / 1000} seconds`); setTimeout(toRun, timeout) } /** * Normalize a port into a number, string, or false. * * @param {*} val Value to be normalized * @returns Normalized value */ exports.normalizePort = (val) => { var port = parseInt(val, 10); if (isNaN(port)) { // named pipe return val; } if (port >= 0) { // port number return port; } return false; } /** * Event listener for HTTP server "error" event. */ exports.onError = (error) => { if (error.syscall !== 'listen') { throw error; } var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': log.ERROR(bind + ' requires elevated privileges'); process.exit(1); break; case 'EADDRINUSE': log.ERROR(bind + ' is already in use'); process.exit(1); break; default: throw error; } } exports.sendPost = (post, source, channel, callback) => { log.DEBUG("Sending post from source: ", post, source); const postTitle = String(post.title).substring(0, 150); const postLink = post.link; let postContent; if (post.content) { // Reset the content parameter with the encoded parameter post.content = parse(post['content:encoded'] ?? post.content); // Get the post content and trim it to length or add a placeholder if necessary var postText = String(post.content.text); if (postText.length >= 300) postText = `${postText.slice(0, 300).substring(0, Math.min(String(post.content.text).length, String(post.content.text).lastIndexOf(" ")))}...`; else if (postText.length === 0) postText = `*This post has no content* [Direct Link](${post.link})`; postContent = postText; } // Check for embedded youtube videos and add the first four as links const ytVideos = String(post.content).match(youtubeVideoRegex); if (ytVideos) { for (var ytVideo of ytVideos.slice(0,4)){ // If the video is an embed, replace the embed to make it watchable if (ytVideo.includes("embed")) ytVideo = ytVideo.replace("embed/", "watch?v="); postContent += `\nEmbeded Video from Post: [YouTube](${ytVideo})` } } log.DEBUG("Post content: ", postContent); const postId = post.postId; if (!post.pubDate) post.pubDate = Date.now(); const postPubDate = new Date(post.pubDate).toISOString(); var postSourceLink = source.title; var postImage = post.image ?? undefined; if (!postImage){ if (post.content){ const linksInPost = post.content.querySelectorAll("a"); if (linksInPost) { log.DEBUG("Found links in post:", linksInPost); for (const link of linksInPost) { // Check to see if this link is a youtube video that was already found, if so skip it if (ytVideos?.includes(link)) continue; const images = String(link.getAttribute("href")).match(imageRegex); log.DEBUG("Images found in post:", images); if (images) { postImage = images[0]; } } } } } log.DEBUG("Sending an RSS post to discord", postTitle, postId, postContent) try{ const rssMessage = new this.EmmeliaEmbedBuilder() .setColor(0x0099FF) .setTitle(postTitle) .setURL(postLink) .addFields({ name: 'Source', value: postSourceLink, inline: true }) .addFields({ name: 'Published', value: postPubDate, inline: true }); // TODO - If there is more than one image, create a canvas and post the created canvas if (postImage) { log.DEBUG("Image from post:", postImage); rssMessage.setImage(postImage); } //Add the main content if it's present if (postContent) rssMessage.addFields({ name: "Post Content", value: postContent, inline: false }) channel.send({ embeds: [rssMessage] }); //throw new Error("YOU SHALL NOT PASS"); return callback(undefined, true); } catch (err){ log.ERROR("Error sending message: ", postTitle, postId, postContent, postPubDate, err); return callback(err, undefined); } } exports.returnHash = (...stringsIncluded) => { return crypto.createHash('sha1').update(`${stringsIncluded.join("-<>-")}`).digest("base64"); } /** * Check if a key exists in an array of objects * @param {*} key The key to search for * @param {*} array The object to search for the key * @returns {boolean} If the key exists in the object */ exports.checkForKeyInArrayOfObjects = (key, array) => { return array.filter(function (o) { return o.hasOwnProperty(key); }).length > 0; }