Ok,
It may not be perfect as it’s my first lines in javascript, but it seems to work. Here is my code:
const express = require("express")
const GhostAdminAPI = require('@tryghost/admin-api');
const ProbeImageSize = require('probe-image-size');
const fs = require('fs')
const app = express()
const PORT = 3000
arguments = process.argv.slice(2)
const config = require(arguments[0])
const ghostDir = config.paths.contentPath.replace('/content','')
const ghostUrl = config.url.replace(/\/$/, "");
console.log('Site '+ ghostUrl + ' installed in ' + ghostDir)
function checkIP(req) {
const whitelistedIP = ['::1', '::ffff:127.0.0.1']
if (whitelistedIP.includes(req.ip)) {
console.log('Connection GRANTED from %s', req.ip)
return true
} else {
console.log('Connection DROPPED from %s', req.ip)
return false
}
}
// Update
const api = new GhostAdminAPI({
url: ghostUrl,
key: config.scripts.admin_key,
version: 'v3'
});
function get_images_from_card(card) {
let images = [];
switch (card[0]) {
case 'gallery':
card[1].images.forEach(
element => {
images.push(element.src)
}
);
break;
case 'image':
images.push(card[1].src)
break;
}
// console.log('images : %s', images)
return images
}
function isEmpty(str) {
return (!str || str.length === 0 );
}
function selectFeatureImage(images) {
let featureImage = ''
for(let i=0; i < images.length; i++) {
images[i] = images[i].replace(ghostUrl, ghostDir);
let data = fs.readFileSync(images[i]);
if (isEmpty(featureImage)) {
p_data = ProbeImageSize.sync(data)
if (p_data.width > p_data.height) {
featureImage = images[i]
}
// console.log(p_data);
}
}
// Fallback first image
if (isEmpty(featureImage)) {
featureImage = images[0]
}
return featureImage.replace(ghostDir, ghostUrl);
}
function updateImageCard(imageCard, imagesPrefix) {
const targetDir = ghostDir + '/content/images/' + imagesPrefix
const targetUrl = ghostUrl + '/content/images/' + imagesPrefix
originalImageUrl = imageCard.src
const originalImagePath = originalImageUrl.split('/')
const imageName = originalImagePath[originalImagePath.length - 1]
const originalImageDir = ghostDir + '/content/images/'
+ originalImagePath[originalImagePath.length - 3] + '/'
+ originalImagePath[originalImagePath.length - 2]
+ '/' + imageName
const targetImageDir = targetDir + '/'+ imageName
const targetImageUrl = targetUrl + '/'+ imageName
// console.log('%s -> %s', originalImageDir, targetImageDir)
if (targetImageUrl != originalImageUrl) {
// Do not overwrite existing image
if (fs.existsSync(targetImageDir) && fs.existsSync(originalImageDir)) {
console.error('Cannot move %s to %s', originalImageDir, targetImageDir)
console.error('Both images exists');
return null
} else if (!fs.existsSync(targetImageDir) && fs.existsSync(originalImageDir)) {
// console.log('Moved %s to %s', originalImageDir, targetImageDir)
fs.rename(originalImageDir, targetImageDir, function (err) {
if (err) throw err
console.log('Moved %s to %s', originalImageDir, targetImageDir)
imageCard.src = targetImageUrl
return imageCard
})
// imageCard.src = targetImageUrl
// return imageCard
} else if (fs.existsSync(targetImageDir) && ! fs.existsSync(originalImageDir)) {
console.log('Updated %s to %s', originalImageDir, targetImageDir)
imageCard.src = targetImageUrl
return imageCard
} else {
console.log('Nothing to do')
return null
}
}
}
function updateCard(card, imagesPrefix) {
switch (card[0]) {
case 'gallery':
images = card[1].images
for(let i=0; i < images.length; i++) {
newCard = updateImageCard(images[i], imagesPrefix)
if (newCard) {
images[i] = newCard
}
}
// console.log('new images', images)
const valImages = {}
valImages['images'] = images
return ['gallery', valImages]
case 'image':
// console.log('Image card : %s', card[1])
newCard = updateImageCard(card[1], imagesPrefix)
if (newCard) {
// console.log(newCard)
return ['image', newCard]
} else {
return card
}
default:
return card
}
}
function updateCards(cards, imagesPrefix) {
newCards = []
cards.forEach(
card => newCards.push(updateCard(card, imagesPrefix)));
return newCards
}
function createImageDir(postDateYear, postDateMonth) {
const yearDir = ghostDir + '/content/images/' + postDateYear
if (!fs.existsSync(yearDir)){
fs.mkdirSync(yearDir);
console.log('Directory %s created', yearDir)
}
const monthDir = yearDir + '/' + postDateMonth
if (!fs.existsSync(monthDir)){
fs.mkdirSync(monthDir);
console.log('Directory %s created', monthDir)
}
}
function updatePost(post){
let mdoc = JSON.parse(post.mobiledoc)
let cards = mdoc.cards
let images = []
const postDate = new Date(Date.parse(post.published_at))
// console.log(post_date)
const postDateMonth = ('0' + (postDate.getMonth()+1)).slice(-2)
const postDateYear = postDate.getFullYear();
const imagesPrefix = postDateYear + '/' + postDateMonth
console.log('%s/%s Updating post %s', postDateMonth, postDateYear, post.slug)
// Create image dir if needed
createImageDir(postDateYear, postDateMonth)
oldCardsStr = JSON.stringify(cards)
cards = updateCards(cards, imagesPrefix)
// Get images defined in card sections
cards.forEach(
element => images = images.concat(get_images_from_card(element)));
images = Array.from(new Set(images))
// console.log('images : %s', images)
featureImage = selectFeatureImage(images)
// console.log('featured : %s', featureImage)
mdoc.cards = cards
if (oldCardsStr != JSON.stringify(cards)) {
console.log('Updating post %s with images and feature image to %s', post.id, featureImage)
api.posts.edit({id: post.id,
updated_at: post.updated_at,
mobiledoc: JSON.stringify(mdoc),
feature_image: featureImage})
} else if (featureImage != post.feature_image) {
console.log('Updating post %s feature image to %s', post.id, featureImage)
api.posts.edit({id: post.id,
updated_at: post.updated_at,
feature_image: featureImage})
} else {
console.log('Post %s is up to date', post.id)
}
}
function updatePostReq(postSlug) {
api.posts.read({slug: postSlug})
.then(
post => updatePost(post)
)
.catch(error => {
console.error('Error or Post %s not found',postSlug)
console.error(error)
})
}
// API side
app.use(express.json());
app.get('/check', (req, res) => {
res.end('online');
console.log('server online')
res.status(200).end()
})
app.post("/update", (req, res) => {
if (!checkIP(req)) {
return
}
const slug = req.body.post.current.slug
console.log('Request to update %s', req.query.slug)
updatePostReq(slug)
res.status(200).end()
})
app.get('/update', (req, res) => {
if (!checkIP(req)) {
return
}
if (req.query.slug) {
res.end('update post '+ req.query.slug);
console.log('Request from %s to update %s', req.ip, req.query.slug)
updatePostReq(req.query.slug)
}
res.status(200).end()
})
app.listen(PORT, () => console.log(`🚀 Server running on port ${PORT}`))
I use pm2 to let this code running.
Create conf file:
{
"apps": {
"name": "ghost-tools-update",
"exec": "index.js",
"args": "/path_to/config.production.json",
"watch": true,
"ignore_watch": [
"node_modules",
"logs"
],
"error_file": "/home/ghost/.pm2/logs/ghost-tools-update-err.log",
"out_file": "/home/ghost/.pm2/logs/ghost-tools-update-out.log",
"log_date_format": "YYYY-MM-DD HH:mm:ss"
}
}
And launch:
$ sudo -u ghost pm2 start index.js pm2.conf
and I created two webhooks to https://localhost:3000/update triggered by ‘Post published’ and ‘Published post updated’
Hope it will help somebody else to do the same thing.
I will continue to improve this code, but if somebody, better in JS than me, want to help, I can share the updates on gitlab.