import Masto from "mastodon"; import dotenv from "dotenv"; import sharp from 'sharp'; import fs from 'fs'; import https from 'https'; import moment from "moment"; import Parser from 'rss-parser'; 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()]; } /** * create a masto fetcher instance * @param userNickName * @returns {Mastodon} */ export function createMastoFetcherWithAuthorLogin(userNickName) { let accessToken = process.env['TOKEN_' + userNickName.toUpperCase()] const masto = new Masto({ access_token: accessToken, api_url: process.env.INSTANCE_MASTODON + '/api/v1/', }); return masto; } /** * 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.message) } 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.image) // upload new media return masto.post('media', {file: fs.createReadStream(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 < l; i++) { str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i)); } // Remove invalid chars str = str.replace(/[^a-z0-9 -]/g, '') // Collapse whitespace and replace by - .replace(/\s+/g, '-') // Collapse dashes .replace(/-+/g, '-'); return str; } /** * @name listFilesOfFolder * lister les noms de fichier que l'on peut publier dans un dossier. * retourne un tableau */ export function listFilesOfFolder(folderPath) { let filesNames = [] fs.readdirSync(folderPath).map(fileName => { 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 } let parser = new Parser(); export function diffDaysBetweenTwoDates(date1, date2) { const a = moment(date1); const b = moment(date2); return a.diff(b, 'days'); } 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) }) return selection; }