528 lines
16 KiB
JavaScript
Raw Normal View History

import Masto from 'mastodon'
import dotenv from 'dotenv'
import sharp from 'sharp'
import fs from 'fs'
import https from 'https'
import moment from 'moment'
2022-08-08 16:17:04 +02:00
import Parser from 'rss-parser'
2025-01-12 12:28:59 +01:00
import {load} from 'cheerio'
2022-12-07 23:15:07 +01:00
let local_node_env_conf = dotenv.config()
const myArgs = process.argv.slice(2)
export const reallySendPost = hasCliArgument('--force')
2025-01-12 12:28:59 +01:00
console.log('reallySendPost', reallySendPost)
2023-03-03 15:19:09 +01:00
export const folderBlogPostsPreview = process.cwd() + '/assets/blog_posts_medias/'
2022-08-04 23:07:59 +02:00
2025-01-12 12:28:59 +01:00
export function randomIntFromInterval(min, max) { // min and max included
return Math.floor(Math.random() * (max - min + 1) + min) | 1
2022-08-04 23:07:59 +02:00
}
2022-08-08 16:17:04 +02:00
2025-01-12 12:28:59 +01:00
export function getRandomElementOfArray(listItems) {
return listItems[Math.floor(Math.random() * listItems.length)]
2022-08-05 14:57:53 +02:00
}
let nowDate = new Date()
export let defaultConfigMasto = {
2025-01-12 12:28:59 +01:00
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: {},
2022-08-05 14:57:53 +02:00
}
2025-01-12 12:28:59 +01:00
export function tokenForAuthorIsPresentInDotEnv(author) {
return process.env['TOKEN_' + author.toUpperCase()]
2022-08-08 16:17:04 +02:00
}
2023-03-04 10:30:20 +01:00
/**
* create a masto fetcher instance
* @param userNickName
* @returns {Mastodon}
*/
2025-01-12 12:28:59 +01:00
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
2023-03-04 10:30:20 +01:00
}
/**
* send post to mastodon with config
* @param config
* @returns {*}
*/
2025-01-12 12:28:59 +01:00
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`)
}
}
2022-08-05 14:57:53 +02:00
}
// Slugify a string
2025-01-12 12:28:59 +01:00
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
}
2022-08-08 22:49:12 +02:00
/**
* @name listFilesOfFolder
* lister les noms de fichier que l'on peut publier dans un dossier.
* retourne un tableau
*/
2025-01-12 12:28:59 +01:00
export function listFilesOfFolder(folderPath) {
let filesNames = []
fs.readdirSync(folderPath).map(fileName => {
return filesNames.push(fileName)
})
2022-08-08 22:49:12 +02:00
2025-01-12 12:28:59 +01:00
return filesNames
2022-08-08 22:49:12 +02:00
}
2022-08-05 14:57:53 +02:00
2022-08-08 22:49:12 +02:00
/**
* @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.
*/
2025-01-12 12:28:59 +01:00
export function initializeFolderForPictures(folderName) {
try {
if (!fs.existsSync(folderName)) {
fs.mkdirSync(folderName)
}
} catch (err) {
console.error(err)
}
}
2022-12-07 23:15:07 +01:00
/**
* find first image in blog post and return the src value of the img tag
* @param htmlContent
* @returns {string}
*/
2025-01-12 12:28:59 +01:00
export function findFirstImageInContent(htmlContent = '') {
let result = ''
let foundPictures = htmlContent.match(/<img\s[^>]*?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
2022-12-07 23:15:07 +01:00
}
2025-01-12 12:28:59 +01:00
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')
2025-01-12 12:28:59 +01:00
return linkString
}
2022-12-07 23:15:07 +01:00
/**
* 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<unknown>}
*/
2025-01-12 12:28:59 +01:00
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} `))
}
})
})
2022-12-07 23:15:07 +01:00
}
/**
* faire un
* @param pictureName
* @param width
* @param height
* @returns {Promise<Object>}
* @constructor
*/
2025-01-12 12:28:59 +01:00
export function CropPicture(pictureName, width = 500, height = 300) {
2022-12-07 23:15:07 +01:00
2025-01-12 12:28:59 +01:00
return sharp(pictureName)
2022-12-07 23:15:07 +01:00
2025-01-12 12:28:59 +01:00
.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 {*}
*/
2025-01-12 12:28:59 +01:00
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
*/
2025-01-12 12:28:59 +01:00
export function findPictureAndSendPost(postContent, configPost) {
2023-03-03 15:19:09 +01:00
2025-01-12 12:28:59 +01:00
let firstPictureSource = findFirstImageInContent(postContent)
2023-03-03 15:19:09 +01:00
2025-01-12 12:28:59 +01:00
let filePathForDownloadedImage = `${configPost.folder_image}_${configPost.website}_media_post_${slugify(configPost.slug)}.jpg`
2023-03-03 15:19:09 +01:00
2025-01-12 12:28:59 +01:00
if (firstPictureSource) {
console.log('firstPictureSource found', firstPictureSource)
2025-01-12 12:28:59 +01:00
// 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
2022-12-14 17:31:03 +01:00
2025-01-12 12:28:59 +01:00
console.log('média téléchargé, on envoie le post')
configPost.image = filePathForDownloadedImage
2025-01-12 12:28:59 +01:00
sendPostMastodon(configPost)
2025-01-12 12:28:59 +01:00
},
(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 {
2025-01-12 12:28:59 +01:00
// no image provided
2025-01-12 12:28:59 +01:00
console.log('pas d\'image dans le corps du texte', configPost.image)
// on envoie avec l'image par défaut
sendPostMastodon(configPost)
}
2022-12-14 17:31:03 +01:00
}
/**
* find cli argument
* @param argument
* @returns {boolean}
*/
2025-01-12 12:28:59 +01:00
export function hasCliArgument(argument) {
return myArgs.indexOf(argument) !== -1
2022-12-14 17:31:03 +01:00
2023-03-03 15:19:09 +01:00
}
let parser = new Parser()
2025-01-12 12:28:59 +01:00
export function diffDaysBetweenTwoDates(date1, date2) {
2025-01-12 12:28:59 +01:00
const a = moment(date1)
const b = moment(date2)
return a.diff(b, 'days')
2023-03-03 15:19:09 +01:00
}
2025-01-12 12:28:59 +01:00
export function filterRegionAgendaDuLibreEvents(events_list, filter_critera) {
let selection = []
events_list.forEach(item => {
if (item.region_id == filter_critera) {
selection.push(item)
}
})
return selection
}
2023-03-03 15:19:09 +01:00
moment.locale('fr')
2023-03-03 15:19:09 +01:00
2025-01-12 12:28:59 +01:00
export function groupEventsByDay(events_list) {
let selection = {}
events_list.forEach(item => {
2023-03-03 17:28:41 +01:00
2025-01-12 12:28:59 +01:00
let formattedDay = moment(item.start_time).format('dddd DD')
2023-03-03 17:28:41 +01:00
2025-01-12 12:28:59 +01:00
if (!selection[formattedDay]) {
selection[formattedDay] = []
}
selection[formattedDay].push(item)
})
return selection
2023-03-03 18:54:50 +01:00
}
2025-01-12 12:28:59 +01:00
export function convertHTMLtoMD(htmlContent) {
const $ = load(htmlContent)
2023-03-03 18:54:50 +01:00
2025-01-12 12:28:59 +01:00
translateNode($)
2025-01-12 12:28:59 +01:00
return $.html()
}
2025-01-12 12:28:59 +01:00
export function translateNode($) {
const elementsToTranslate = [
{tag: 'h1', mdTag: '##'},
{tag: 'h2', mdTag: '###'},
{tag: 'h3', mdTag: '####'},
{tag: 'h4', mdTag: '#####'},
{tag: 'h5', mdTag: '######'},
{tag: 'p', mdTag: '\n\n'},
{tag: 'ul', preserveChildren: true, transformChild: ($child) => '\n- ' + $.text($child)},
{tag: 'ol', preserveChildren: true, transformChild: ($child, idx) => `\n${idx + 1}. ${$.text($child)}`},
{tag: 'a', parseAttr: ('href', link => `[${link}](${link})`)},
{tag: 'strong', mdFormat: '$&'},
{tag: 'em', mdFormat: '_$&_'},
{tag: 'code', mdFormat: '`$&`'},
]
elementsToTranslate.forEach(element => {
$(element.tag).each((_, el) => {
const $el = $(el)
if (element.parseAttr) {
const attrVal = $el.attr(element.parseAttr[0])
if (attrVal) {
$el.replaceWith(element.parseAttr[1](attrVal))
} else {
$el.remove()
}
} else if (element.transformChild) {
$el.contents().filter(() => !!this.parent()).each((_, childEl) => {
const transformedText = element.transformChild($(childEl), $el.index())
$el.before('\n' + transformedText)
})
$el.remove()
} else {
$el.replaceWith(element.mdTag + $el.text() + element.mdFormat)
}
})
})
2023-03-03 18:54:50 +01:00
}
2023-03-03 19:16:59 +01:00
2025-01-12 12:28:59 +01:00
export function getRequestOptions(host, port, path) {
const options = {
host: host,
port: port,
path: path,
method: 'GET',
headers: {
Accept: 'application/activity+json',
'User-Agent': 'MyApp/1.0.0 (contact@myemail.org)',
},
}
return options
}
2025-01-12 12:28:59 +01:00
export async function sendGetRequest(options) {
return new Promise((resolve, reject) => {
const req = http.request(options, (res) => {
let body = Buffer.alloc(0)
2025-01-12 12:28:59 +01:00
res.on('data', (chunk) => {
body = Buffer.concat([body, chunk])
})
2023-03-03 18:54:50 +01:00
2025-01-12 12:28:59 +01:00
res.on('end', () => {
resolve(JSON.parse(body.toString()))
})
})
2023-03-03 18:54:50 +01:00
2025-01-12 12:28:59 +01:00
req.on('error', (err) => {
reject(err)
})
2025-01-12 12:28:59 +01:00
req.end()
})
}
const splitTextIntoChunks = (text, limit) => {
2025-01-12 12:28:59 +01:00
const words = text.trim().split(/\s+/g)
const chunks = []
let currentChunk = []
let wordCount = 0
for (const word of words) {
if (wordCount + word.length > limit) {
chunks.push(currentChunk.join(' '))
currentChunk = [word]
wordCount = word.length
} else {
currentChunk.push(word)
wordCount += word.length + 1
}
}
if (currentChunk.length > 0) {
const joinedWords = currentChunk.join(' ')
chunks.push(joinedWords.length <= limit ? joinedWords : joinedWords.substr(0, limit) + '...')
}
return chunks
}
2025-01-12 12:28:59 +01:00
export function splitLongDescription(text, limit) {
let chunks = splitTextIntoChunks(text, limit)
if (chunks) {
if (chunks[0]) {
return chunks[0]
}
return ''
}
}