2024-02-07 17:06:53 +01:00
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
2024-02-07 17:06:53 +01:00
import Parser from 'rss-parser'
2025-01-12 12:28:59 +01:00
import { load } from 'cheerio'
2023-03-04 22:53:12 +01:00
2022-12-07 23:15:07 +01:00
let local _node _env _conf = dotenv . config ( )
2024-02-07 17:06:53 +01:00
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
}
2023-03-04 22:53:12 +01:00
2022-12-14 16:38:47 +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
}
2022-12-14 16:38:47 +01: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-12-14 16:38:47 +01:00
}
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-11-22 10:55:11 +01:00
}
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' )
2022-12-14 16:38:47 +01:00
2025-01-12 12:28:59 +01:00
return linkString
2022-12-14 16:38:47 +01:00
}
2023-03-04 22:53:12 +01:00
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 )
} )
2022-12-14 16:38:47 +01:00
}
2023-03-04 22:53:12 +01:00
2022-12-14 16:38:47 +01:00
/ * *
* 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 )
2022-12-14 16:38:47 +01:00
}
/ * *
* 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 )
2022-12-14 16:38:47 +01:00
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
2022-12-14 16:38:47 +01:00
2025-01-12 12:28:59 +01:00
sendPostMastodon ( configPost )
2022-12-14 16:38:47 +01:00
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 {
2022-12-14 16:38:47 +01:00
2025-01-12 12:28:59 +01:00
// no image provided
2022-12-14 16:38:47 +01:00
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
}
2024-02-07 17:06:53 +01:00
let parser = new Parser ( )
2025-01-12 12:28:59 +01:00
export function diffDaysBetweenTwoDates ( date1 , date2 ) {
2024-02-07 17:06:53 +01:00
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
2024-02-07 17:06:53 +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
2024-02-07 17:06:53 +01:00
}
2023-03-03 15:19:09 +01:00
2024-02-07 17:06:53 +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 ( $ )
2024-02-07 17:06:53 +01:00
2025-01-12 12:28:59 +01:00
return $ . html ( )
2024-02-07 17:06:53 +01:00
}
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
2024-02-07 17:06:53 +01:00
}
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 )
2024-02-07 17:06:53 +01:00
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 )
} )
2024-02-07 17:06:53 +01:00
2025-01-12 12:28:59 +01:00
req . end ( )
} )
2024-02-07 17:06:53 +01:00
}
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
2024-02-07 17:06:53 +01:00
}
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 ''
}
2024-02-07 17:06:53 +01:00
}
2023-03-04 22:53:12 +01:00