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); let reallySendPost = hasCliArgument('--force'); 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, reallySendPost, image: '', folder_image: process.cwd() + '/assets/blog_posts_medias/', message: "Hey coucou! on est le" + nowDate, scheduled_at: "", 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 =========== le message ne sera PAS réellement posté sur le compte @" + config.author + "@" + process.env.INSTANCE_MASTODON + " =========== \n") } 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) { 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 { 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 CropPictue(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 filePathImage = `${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 : ' , filePathImage) downloadImage(firstPictureSource, filePathImage) .then((res) => { // suite du poste avec upload d'image console.log('média téléchargé, on envoie le post') configPost.image = filePathImage; 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){ console.log('myArgs: ', myArgs); return myArgs.indexOf(argument) !== -1 }