multi-account-post-schedule.../wip/recommandations_curator.mjs
2024-09-03 10:32:31 +02:00

244 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// selectionner un compte parmi les gens suivis dans une liste
// export des abonnements utilisateur https://mastodon.cipherbliss.com/settings/exports/follows.csv/home/cipherbliss/Nextcloud/inbox/following_accounts.csv
import axios from 'axios'
import Masto from 'mastodon'
/**
* picture generation
*/
import Jimp from 'jimp'
import { getRequestOptions, randomIntFromInterval, sendGetRequest } from '../helpers/libs/utils.mjs'
import fs from 'fs'
import path from 'path'
let reallySendPost = false
// reallySendPost = true
async function getFollowers (username, instance) {
const rand = randomIntFromInterval(1, 30) * 1000
console.log('random max id x 1000', rand)
const url = `${instance}/api/v1/accounts/${username}/following?max_id=${rand}000`
try {
const response = await axios.get(url)
return response.data
} catch (error) {
console.error(error)
}
}
// Function to look up the user account number using webfinger
async function getUserAccountNumberFromMastodonUsername (username) {
const parsedUsername = username.split('@')
if (parsedUsername.length !== 2) {
throw new Error('Invalid Mastodon username format.')
}
const localPart = parsedUsername[0]
const domain = parsedUsername[1]
console.log('localPart, domain', localPart, domain)
const acctPath = '/.well-known/webfinger?resource=' + encodeURIComponent('acct:' + localPart + '@' + domain)
const options = getRequestOptions(domain, 443, acctPath)
console.log('options', options)
try {
const fingerResult = await sendGetRequest(options)
const actorResource = fingerResult['subject'] || fingerResult.links?.find((item) => item.rel === 'self').href
if (!actorResource) {
throw new Error('Failed to obtain the resource identifier.')
}
const accountHost = actorResource.split('/')[2]
const accountPath = actorResource.split('/').pop()
const accountInfoReqOptions = getRequestOptions(accountHost, 443, '/api/v1/accounts/' + accountPath)
const accountInfo = await sendGetRequest(accountInfoReqOptions)
return accountInfo.id
} catch (error) {
throw error
}
}
async function main () {
const userIdOnInstance = '1'
const instance = 'https://mastodon.cipherbliss.com'
// TODO get the followers from a csv file if there is one in assets/data/follows.csv
const followers = await getFollowers(userIdOnInstance, instance)
console.log('followers.length', followers.length)
const randomFollowers = followers?.sort(() => Math.random() - 0.5)?.slice(0, 3)
// console.log('Random followers:', randomFollowers)
let message = '\n Les personnes que l\'on vous recommande de suivre aujourd\'hui:'
let avatars_urls = []
randomFollowers.forEach(account => {
message += '\n' + displayDataAboutFollower(account)
console.log('account', account.acct)
avatars_urls.push(account.avatar_static)
})
message += '\n #fedifollows #curatorRecommendations'
await generateAvatarComposite(avatars_urls)
return message
}
/**
* displays username, acct, avatar and url for one account
* @param follower
*/
function displayDataAboutFollower (follower) {
let text = ''
if (follower.note) {
text = follower.note.trim().substring(0, 150)
if (follower.note.trim() > 150) {
text += '...'
}
}
return ` * _${follower.display_name}_: [${follower.acct}](${follower.url})
${text.trim()}`
}
let folderUnpublished = ''
let compositeFileName = ''
// Function to maintain aspect ratio while scaling an image down.
function scaleDownPreserveAspectRatio (image, newWidth, newHeight) {
const originalWidth = image.bitmap.width
const originalHeight = image.bitmap.height
const scaleFactor = Math.min(newWidth / originalWidth, newHeight / originalHeight)
const newWidthScaled = Math.round(originalWidth * scaleFactor)
const newHeightScaled = Math.round(originalHeight * scaleFactor)
return image.scaleToFit(newWidthScaled, newHeightScaled)
}
/**
* Prend trois URL d'image d'avatar et génère une image composite JPG avec celles-ci,
* enregistrée sous le format «YYYY-MM-DDTHH-MM-SS.jpg».
*/
async function generateAvatarComposite (avatarUrls) {
if (!Array.isArray(avatarUrls) || avatarUrls.length !== 3) {
throw new Error('Veuillez fournir exactement trois URL davatar.')
}
// Télécharger chaque avatar
const avatarImagesPromises = avatarUrls.map((url) => downloadImage(url))
const avatarImages = await Promise.all(avatarImagesPromises)
// Combiner les images horizontalement
// Resize avatars to a consistent dimension of 400x400 pixels while maintaining aspect ratio
const resizedAvatarImages = avatarImages.map((image) => scaleDownPreserveAspectRatio(image, 400, 400))
// Combine the images horizontally
const combinedWidth = 3 * resizedAvatarImages[0].bitmap.width
const combinedHeight = Math.max(...resizedAvatarImages.map((image) => image.bitmap.height))
const composite = new Jimp(combinedWidth, combinedHeight)
for (let i = 0; i < resizedAvatarImages.length; i++) {
const xOffset = i * resizedAvatarImages[0].bitmap.width
composite.composite(resizedAvatarImages[i], xOffset, 0)
}
// Enregistrer l'image composite
compositeFileName = `avatars_reccommendations_${Date.now().toString()}.jpg`
await composite.writeAsync(compositeFileName)
console.log(`L'image composite a été enregistrée sous le nom "${compositeFileName}".`)
return compositeFileName
}
/**
* Télécharge une image depuis l'URL spécifiée et renvoie une instance Jimp.
*/
async function downloadImage (url) {
return await Jimp.read(url)
}
function publishOnMastodon (folderUnpublished, configPost) {
let accessToken = process.env['TOKEN_' + configPost.author.toUpperCase()]
console.log('accessToken', accessToken)
const masto = new Masto({
access_token: accessToken,
api_url: process.env.INSTANCE_MASTODON + '/api/v1/',
})
let imagePath = `${path.resolve()}/${compositeFileName}`
console.log('------- imagePath', imagePath)
if (configPost.reallySendPost) {
/**
* poster le média, puis faire un toot avec le média lié
*/
masto.post('media', { file: fs.createReadStream(imagePath) })
.then(resp => {
let id = resp.data.id
configPost.media_ids = [id]
console.log('\n\n id du média pour le post:', id, configPost)
masto.post('statuses', configPost).then(rep => {
// console.log('rep', rep)
console.log(`\n\n posté avec une nouvelle image, ${configPost.image} WOOT`)
}, err => {
console.error(err)
console.log('\n\n erreur T_T')
})
}, err => {
console.error(err)
})
} else {
console.log('envoi désactivé avec configPost.reallySendPost')
}
}
/**
* run all
*/
main().then(message => {
// let md_message = convertHTMLtoMD(message)
let md_message = message.trim() + '\n'
console.log('message', message)
console.log('md_message', md_message)
// upload de fileName et création du post par le Curator
let configPost = {
author: 'curator',
website: 'cipherbliss',
visibility: 'public',
language: 'fr',
sensitive: false,
postObject: {},
folder_image: '.',
scheduled_at: '',
scheduled_at_bool: false,
content_type: 'text/markdown',
// image: compositeFileName,
message: md_message,
reallySendPost
}
// publishOnMastodon(folderUnpublished, configPost)
}, err => {
console.error(err)
})