import Masto from "mastodon"; import dotenv from "dotenv"; import sharp from 'sharp'; import fs from 'fs'; import https from 'https'; let local_node_env_conf = dotenv.config() const myArgs = process.argv.slice(2); export const reallySendPost = hasCliArgument('--force'); export const folderBlogPostsPreview = process.cwd() + '/assets/blog_posts_medias/' export function randomIntFromInterval(min, max) { // min and max included return Math.floor(Math.random() * (max - min + 1) + min) | 1 } export function getRandomElementOfArray(listItems) { return listItems[Math.floor(Math.random() * listItems.length)] } let nowDate = new Date() export let defaultConfigMasto = { author: 'curator', visibility: 'public', language: 'fr', sensitive: false, disable_slugify:false, reallySendPost, image: '', folder_image: process.cwd() + '/assets/blog_posts_medias/', message: "Hey coucou! on est le" + nowDate, scheduled_at: "", scheduled_at_bool: false, content_type: "text/markdown", website: 'qzine', slug: 'default_post_title', postObject : {}, } export function tokenForAuthorIsPresentInDotEnv(author) { return process.env['TOKEN_' + author.toUpperCase()]; } /** * send post to mastodon with config * @param config * @returns {*} */ export default function sendPostMastodon(config) { // console.log('send post', config.postObject.post_guid , config.postObject.guid ) // override defaults with input argument config = { ...defaultConfigMasto, ...config, } // console.log("sendPostMastodon config", config) if (!config.reallySendPost) { console.log("\n\n =========== le message ne sera PAS réellement posté sur le compte @" + config.author + "@" + process.env.INSTANCE_MASTODON + " =========== \n") console.log('configPost.folder_image', config.folder_image) console.log('config', config) } else { console.log(" ") if (process.env.INSTANCE_MASTODON && tokenForAuthorIsPresentInDotEnv(config.author)) { let visibility = 'public'; let language = 'fr'; let sensitive = false; let accessToken = process.env['TOKEN_' + config.author.toUpperCase()] const masto = new Masto({ access_token: accessToken, api_url: process.env.INSTANCE_MASTODON + '/api/v1/', }); let params = { status: config.message, visibility, language, sensitive } if (config.cw) { params['spoiler_text'] = config.cw } if (config.scheduled_at && config.scheduled_at_bool) { let dateschedule = new Date(config.scheduled_at) params['scheduled_at'] = dateschedule.toISOString() } /** * envoi sans fichier joint */ if (!config.image) { console.log('pas d image dans la config') if (config.reallySendPost) { masto.post('statuses', params).then(rep => { console.log("posté, yay!") }, err => { console.error(err) }) } } /** * envoi avec fichier, * on doit d'abord faire un upload du fichier, * puis relier son id de média au nouveau post. */ else if (config.image) { var id; console.log("envoi du média", config.folder_image +config.image) // upload new media return masto.post('media', {file: fs.createReadStream(config.folder_image +config.image)}) .then(resp => { id = resp.data.id; params.media_ids = [id] console.log("\n ✅ image, id", id) masto.post('statuses', params).then(rep => { // console.log('rep', rep) console.log("\n ✅ posté avec une nouvelle image, WOOT") }, err => { console.error(err) console.log("erreur T_T") }) }) } // } } else { console.error(`pas de token pour l'auteur "${config.author}" ou pas d'instance mastodon définie`) } } } // Slugify a string export function slugify(str) { str = str.replace(/^\s+|\s+$/g, ''); // Make the string lowercase str = str.toLowerCase(); // Remove accents, swap ñ for n, etc var from = "ÁÄÂÀÃÅČÇĆĎÉĚËÈÊẼĔȆÍÌÎÏŇÑÓÖÒÔÕØŘŔŠŤÚŮÜÙÛÝŸŽáäâàãåčçćďéěëèêẽĕȇíìîïňñóöòôõøðřŕšťúůüùûýÿžþÞĐđßÆa·/_,:;"; var to = "AAAAAACCCDEEEEEEEEIIIINNOOOOOORRSTUUUUUYYZaaaaaacccdeeeeeeeeiiiinnooooooorrstuuuuuyyzbBDdBAa------"; for (var i=0, l=from.length ; i { return filesNames.push(fileName); }); return filesNames; } /** * @name initializeFolderForPictures * crée un dossier d'assets, avec ses sous dossiers not_published et published si ils manquent. * une fois que l'on prendra une image dans le dossier non publié, on la déplacera dans le dossier des images publées. */ export function initializeFolderForPictures(folderName) { try { if (!fs.existsSync(folderName)) { fs.mkdirSync(folderName); } } catch (err) { console.error(err); } } /** * find first image in blog post and return the src value of the img tag * @param htmlContent * @returns {string} */ export function findFirstImageInContent(htmlContent='') { let result = '' let foundPictures = htmlContent.match(/]*?src\s*=\s*['\"]([^'\"]*?)['\"][^>]*?>/); let first = ''; if(foundPictures && foundPictures[0]){ first = foundPictures[0] }else{ console.log('pas d image trouvée dans le contenu ', htmlContent) } if (first) { result = first.match(/src\=\"(.*)\"/i) if(result.length && result[0]){ result = result[0].split('"') result = result[1] } } result = clearLink(result) console.log('clearLink', result) return result; } function clearLink(linkString){ linkString = linkString.replace('http:', 'https:') linkString = linkString.replace('https://www.ailesse.info/~tykayn/bazar/kotlife', 'https://www.tykayn.fr/wp-content/uploads/i/kotlife') linkString = linkString.replace('https://blog.artlemoine.com/public/i', 'https://www.tykayn.fr/wp-content/uploads/i') linkString = linkString.replace('https://www.ailesse.com/%7Etykayn/bazar', 'https://www.tykayn.fr/wp-content/uploads/i/bazar') return linkString } /** * usage: * downloadImage('https://upload.wikimedia.org/wikipedia/en/thumb/7/7d/Lenna_%28test_image%29.png/440px-Lenna_%28test_image%29.png', 'lena.png') * .then(console.log) * .catch(console.error); * @param url * @param filepath * @returns {Promise} */ export function downloadImage(url, filepath) { return new Promise((resolve, reject) => { const options = { headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52" } }; https.get( url, options, (res) => { if (res.statusCode === 200) { res.pipe(fs.createWriteStream(filepath)) .on('error', reject) .once('close', () => resolve(filepath)); } else { // Consume response data to free up memory res.resume(); reject(new Error(`Request Failed With a Status Code: ${res.statusCode}; \n ${res.statusMessage} `)); } }); }); } /** * faire un * @param pictureName * @param width * @param height * @returns {Promise} * @constructor */ export function CropPicture(pictureName, width = 500, height = 300) { return sharp(pictureName) .extract({left: 0, top: 0, width, height}) .toFile('thumb_' + pictureName, function (err) { if (err) console.log(err); }); } /** * prendre un post parmi tous ceux du blog, dans ceux qui ont été publiés * @returns {*} */ export function getRandomLinkGeneral(tkpostsjson) { let filteredLinks = [] if (tkpostsjson[0].post_status) { filteredLinks = tkpostsjson.filter(elem => elem.post_status === 'publish') } else if (tkpostsjson[0].status) { filteredLinks = tkpostsjson.filter(elem => elem.status === 'publish') } return getRandomElementOfArray(filteredLinks) } /** * trouver l'image du contenu si il y en a * @param postContent * @param configPost */ export function findPictureAndSendPost(postContent, configPost){ let firstPictureSource = findFirstImageInContent(postContent); let filePathForDownloadedImage = `${configPost.folder_image}_${configPost.website}_media_post_${slugify(configPost.slug)}.jpg` if (firstPictureSource) { console.log("firstPictureSource found", firstPictureSource) // check if picture already exist console.log('on envoie le média et l image : ' , filePathForDownloadedImage) downloadImage(firstPictureSource, filePathForDownloadedImage) .then((res) => { // suite du poste avec upload d'image console.log('média téléchargé, on envoie le post') configPost.image = filePathForDownloadedImage; sendPostMastodon(configPost) }, (err) => { console.log('pas dimage trouvée pour l URL ', firstPictureSource, err) sendPostMastodon(configPost) } ) .catch((err) => { console.log('erreur avec cette URL ', firstPictureSource, err) sendPostMastodon(configPost) }) } else { // no image provided console.log("pas d'image dans le corps du texte", configPost.image) // on envoie avec l'image par défaut sendPostMastodon(configPost) } } /** * find cli argument * @param argument * @returns {boolean} */ export function hasCliArgument(argument){ return myArgs.indexOf(argument) !== -1 } import Parser from 'rss-parser'; import moment from "moment"; let parser = new Parser(); /** * fetch and return a parsed rss feed * @param url * @returns {Promise<*>} */ export async function parseRssFeed(url){ let parsedFeed = await parser.parseURL(url); console.log(parsedFeed.title); parsedFeed.items.forEach((item) => { console.log(item.title); }); return parsedFeed } /** * get a subset of rss parsed items * @param parsedRssFeed * @param days * @returns {T[]} */ export function getArticlesFromDaysInRssFeed(parsedRssFeed , days=7) { // return parsedRssFeed.items.splice(0,days); return parsedRssFeed.items.splice(0,6); } export function makeTitleContentFromRss(parsedFeed) { let content = '' let counter = 0; if (!parsedFeed.length) { return ''; } parsedFeed.forEach((item) => { // if (!counter) { // console.log('first item', item) // } let cleanedTitle= item.title.replace('[','').trim() cleanedTitle= cleanedTitle.replace(']','').trim() content += `\n* [${cleanedTitle}](${item.link}) `; content += `\n ${item.isoDate.substring(0,10)} : ${item.contentSnippet.split('\n')[0]} \n`; counter++; }); return content; } export function getArticlesFromDaysInJson(json_content,nbOfDays){ let today = new Date() let selectedEvents = []; console.log('getArticlesFromDaysInJson: filtre nbOfDays', nbOfDays) json_content.forEach((item) => { let differenceDays = diffDaysBetweenTwoDates(new Date(item.start_time) ,today); console.log('differenceDays', differenceDays) if( differenceDays <= nbOfDays || differenceDays == 0 || differenceDays == 1){ console.log('on garde', item.title) selectedEvents.push(item) }else{ console.log('on ne garde pas', differenceDays) } }) return selectedEvents } export function diffDaysBetweenTwoDates(date1,date2) { const diffTime = Math.abs(date2 - date1); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); return diffDays; } export function filterRegionAgendaDuLibreEvents(events_list, filter_critera) { let selection = [] events_list.forEach(item=> { if( item.region_id == filter_critera){ selection.push(item) } }) return selection; } moment.locale('fr'); export function groupEventsByDay(events_list){ let selection = {} events_list.forEach(item=> { let formattedDay = moment(item.start_time).format('dddd DD') if( ! selection[formattedDay]){ selection[formattedDay] = [] selection[formattedDay].push(item) } }) console.log('selection', selection) return selection; }