528 lines
16 KiB
JavaScript
528 lines
16 KiB
JavaScript
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'
|
|
import {load} from 'cheerio'
|
|
|
|
let local_node_env_conf = dotenv.config()
|
|
const myArgs = process.argv.slice(2)
|
|
export const reallySendPost = hasCliArgument('--force')
|
|
console.log('reallySendPost', reallySendPost)
|
|
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(/<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
|
|
}
|
|
|
|
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<unknown>}
|
|
*/
|
|
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<Object>}
|
|
* @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
|
|
}
|
|
|
|
export function convertHTMLtoMD(htmlContent) {
|
|
const $ = load(htmlContent)
|
|
|
|
translateNode($)
|
|
|
|
return $.html()
|
|
}
|
|
|
|
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)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
export async function sendGetRequest(options) {
|
|
return new Promise((resolve, reject) => {
|
|
const req = http.request(options, (res) => {
|
|
let body = Buffer.alloc(0)
|
|
|
|
res.on('data', (chunk) => {
|
|
body = Buffer.concat([body, chunk])
|
|
})
|
|
|
|
res.on('end', () => {
|
|
resolve(JSON.parse(body.toString()))
|
|
})
|
|
})
|
|
|
|
req.on('error', (err) => {
|
|
reject(err)
|
|
})
|
|
|
|
req.end()
|
|
})
|
|
}
|
|
|
|
const splitTextIntoChunks = (text, limit) => {
|
|
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
|
|
}
|
|
|
|
export function splitLongDescription(text, limit) {
|
|
let chunks = splitTextIntoChunks(text, limit)
|
|
if (chunks) {
|
|
if (chunks[0]) {
|
|
return chunks[0]
|
|
}
|
|
return ''
|
|
}
|
|
}
|
|
|