diff --git a/src/cli.ts b/src/cli.ts index dda8c75..5640835 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -96,6 +96,7 @@ async function main() { } case "get-folders": { const folders = feed.getFolders(FOLDERS); + report += folders.length + " Folders" + "\n"; folders.forEach((folder) => { report += folder.report() + "\n"; }); @@ -114,6 +115,7 @@ async function main() { platforms: PLATFORMS, status: STATUS, }); + report += allposts.length + " Posts" + "\n"; allposts.forEach((post) => { report += post.report(); }); diff --git a/src/models/Feed.ts b/src/models/Feed.ts index 51fa726..4f1d67e 100644 --- a/src/models/Feed.ts +++ b/src/models/Feed.ts @@ -189,7 +189,7 @@ export default class Feed { } /** - * Get one post + * Get one (prepared) post * @param path - path to a single folder * @param platformId - the platform for the post * @returns the given post, or undefined if not prepared @@ -200,7 +200,7 @@ export default class Feed { } /** - * Get multiple posts + * Get multiple (prepared) posts * @param filters - object to filter posts by * @param filters.folders - paths to folders to filter on * @param filters.platforms - slugs to platforms to filter on diff --git a/src/models/Platform.ts b/src/models/Platform.ts index 4ce4417..5ef7e8b 100644 --- a/src/models/Platform.ts +++ b/src/models/Platform.ts @@ -16,6 +16,7 @@ export default class Platform { active: boolean = false; id: PlatformId = PlatformId.UNKNOWN; defaultBody: string = "Fairpost feed"; + postFile: string = "post.json"; /** * Return a small report for this feed @@ -31,12 +32,22 @@ export default class Platform { } /** - * getPostFileName - * @returns the intended name for a post of this - * platform to be saved in this folder. + * getAssetsFolderName + * @returns the relative path to a folder used + * to store assets for a post of this platform */ - getPostFileName(): string { - return "_" + this.id + ".json"; + assetsFolder(): string { + return "_" + this.id; + } + + /** + * getPostFilePath + * @param folder the folder for the new or existing post + * @returns the full path to the post file used + * to store data for a post of this platform + */ + getPostFilePath(folder: Folder): string { + return folder.path + "/" + this.assetsFolder() + "/" + this.postFile; } /** @@ -48,10 +59,9 @@ export default class Platform { getPost(folder: Folder): Post | undefined { Logger.trace("Platform", "getPost"); - if (fs.existsSync(folder.path + "/" + this.getPostFileName())) { - const data = JSON.parse( - fs.readFileSync(folder.path + "/" + this.getPostFileName(), "utf8"), - ); + const postFilePath = this.getPostFilePath(folder); + if (fs.existsSync(postFilePath)) { + const data = JSON.parse(fs.readFileSync(postFilePath, "utf8")); if (data) { return new Post(folder, this, data); } diff --git a/src/models/Post.ts b/src/models/Post.ts index 4664fe7..cf3234b 100644 --- a/src/models/Post.ts +++ b/src/models/Post.ts @@ -41,6 +41,10 @@ export default class Post { this.scheduled = this.scheduled ? new Date(this.scheduled) : undefined; this.published = this.published ? new Date(this.published) : undefined; } + const assetsPath = this.getFullPath(platform.assetsFolder()); + if (!fs.existsSync(assetsPath)) { + fs.mkdirSync(assetsPath, { recursive: true }); + } } /** @@ -70,7 +74,7 @@ export default class Post { delete data.folder; delete data.platform; fs.writeFileSync( - this.folder.path + "/" + this.platform.getPostFileName(), + this.platform.getPostFilePath(this.folder), JSON.stringify(data, null, "\t"), ); } @@ -88,6 +92,29 @@ export default class Post { this.status = PostStatus.SCHEDULED; this.save(); } + + /** + * @param filename relative path in this post.folder + * @returns the full path to that file + */ + getFullPath(filename: string): string { + return this.folder.path + "/" + filename; + } + + /** + * Replace a file in the post with an alternative + * @param src relative path in this post.folder + * @param dst relative path in this post.folder + */ + useAlternativeFile(src: string, dst: string) { + for (const type in this.files) { + if (this.files[type].includes(src)) { + // be simple for now + this.files[type].push(dst); + this.files[type] = this.files[type].filter((file) => file !== src); + } + } + } } export interface PostResult { diff --git a/src/platforms/Ayrshare/AsFacebook.ts b/src/platforms/Ayrshare/AsFacebook.ts index 031c6ca..2b887f9 100644 --- a/src/platforms/Ayrshare/AsFacebook.ts +++ b/src/platforms/Ayrshare/AsFacebook.ts @@ -20,18 +20,17 @@ export default class AsFacebook extends Ayrshare { const post = await super.preparePost(folder); if (post) { // facebook : max 10mb images - for (const image of post.files.image) { - const size = - fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); + for (const src of post.files.image) { + const dst = this.assetsFolder() + "/facebook-" + src; + const size = fs.statSync(post.getFullPath(src)).size / (1024 * 1024); if (size >= 10) { - console.log("Resizing " + image + " for facebook .."); - await sharp(post.folder.path + "/" + image) + console.log("Resizing " + src + " for facebook .."); + await sharp(post.getFullPath(src)) .resize({ width: 1200, }) - .toFile(post.folder.path + "/_facebook-" + image); - post.files.image.push("_facebook-" + image); - post.files.image = post.files.image.filter((file) => file !== image); + .toFile(post.getFullPath(dst)); + post.useAlternativeFile(src, dst); } } post.save(); diff --git a/src/platforms/Ayrshare/AsInstagram.ts b/src/platforms/Ayrshare/AsInstagram.ts index d95d552..571138d 100644 --- a/src/platforms/Ayrshare/AsInstagram.ts +++ b/src/platforms/Ayrshare/AsInstagram.ts @@ -1,3 +1,4 @@ +import * as path from "path"; import * as sharp from "sharp"; import Ayrshare from "../Ayrshare"; @@ -18,30 +19,37 @@ export default class AsInstagram extends Ayrshare { async preparePost(folder: Folder): Promise { const post = await super.preparePost(folder); - if (post) { + if (post && post.files) { // instagram: 1 video for reel if (post.files.video.length) { - Logger.trace("Removing images for instagram reel.."); - post.files.image = []; - if (post.files.video.length > 1) { - Logger.trace("Using first video for instagram reel.."); - post.files.video = [post.files.video[0]]; + if (post.files.video.length > 10) { + Logger.trace("Removing > 10 videos for instagram caroussel.."); + post.files.video.length = 10; + } + const remaining = 10 - post.files.video.length; + if (post.files.image.length > remaining) { + Logger.trace("Removing some images for instagram caroussel.."); + post.files.image.length = remaining; } } - // instagram : scale images - for (const image of post.files.image) { - const metadata = await sharp(post.folder.path + "/" + image).metadata(); + + // instagram : scale images, jpeg only + for (const src of post.files.image) { + const metadata = await sharp(post.getFullPath(src)).metadata(); if (metadata.width > 1440) { - Logger.trace("Resizing " + image + " for instagram .."); - await sharp(post.folder.path + "/" + image) + Logger.trace("Resizing " + src + " for instagram .."); + const extension = src.split(".")?.pop(); + const basename = path.basename(src, extension ? "." + extension : ""); + const dst = this.assetsFolder() + "/instagram-" + basename + ".JPEG"; + await sharp(post.getFullPath(src)) .resize({ width: 1440, }) - .toFile(post.folder.path + "/_instagram-" + image); - post.files.image.push("_instagram-" + image); - post.files.image = post.files.image.filter((file) => file !== image); + .toFile(post.getFullPath(dst)); + post.useAlternativeFile(src, dst); } } + // instagram: require media if (post.files.image.length + post.files.video.length === 0) { post.valid = false; diff --git a/src/platforms/Ayrshare/AsLinkedIn.ts b/src/platforms/Ayrshare/AsLinkedIn.ts index 4c662b9..871943b 100644 --- a/src/platforms/Ayrshare/AsLinkedIn.ts +++ b/src/platforms/Ayrshare/AsLinkedIn.ts @@ -31,18 +31,17 @@ export default class AsLinkedIn extends Ayrshare { ); } // linkedin: max 5mb images - for (const image of post.files.image) { - const size = - fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); + for (const src of post.files.image) { + const dst = this.assetsFolder() + "/linkedin-" + src; + const size = fs.statSync(post.getFullPath(src)).size / (1024 * 1024); if (size >= 5) { - Logger.trace("Resizing " + image + " for linkedin .."); - await sharp(post.folder.path + "/" + image) + Logger.trace("Resizing " + src + " for linkedin .."); + await sharp(post.getFullPath(src)) .resize({ width: 1200, }) - .toFile(post.folder.path + "/_linkedin-" + image); - post.files.image.push("_linkedin-" + image); - post.files.image = post.files.image.filter((file) => file !== image); + .toFile(post.getFullPath(dst)); + post.useAlternativeFile(src, dst); } } post.save(); diff --git a/src/platforms/Ayrshare/AsTwitter.ts b/src/platforms/Ayrshare/AsTwitter.ts index ac307d2..c23096f 100644 --- a/src/platforms/Ayrshare/AsTwitter.ts +++ b/src/platforms/Ayrshare/AsTwitter.ts @@ -27,18 +27,17 @@ export default class AsTwitter extends Ayrshare { post.files.image.length = 4; } // twitter: max 5mb images - for (const image of post.files.image) { - const size = - fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); + for (const src of post.files.image) { + const dst = this.assetsFolder() + "/twitter-" + src; + const size = fs.statSync(post.getFullPath(src)).size / (1024 * 1024); if (size >= 5) { - Logger.trace("Resizing " + image + " for twitter .."); - await sharp(post.folder.path + "/" + image) + Logger.trace("Resizing " + src + " for twitter .."); + await sharp(post.getFullPath(src)) .resize({ width: 1200, }) - .toFile(post.folder.path + "/_twitter-" + image); - post.files.image.push("_twitter-" + image); - post.files.image = post.files.image.filter((file) => file !== image); + .toFile(post.getFullPath(dst)); + post.useAlternativeFile(src, dst); } } post.save(); diff --git a/src/platforms/Facebook.ts b/src/platforms/Facebook.ts index 27468be..adc2bf6 100644 --- a/src/platforms/Facebook.ts +++ b/src/platforms/Facebook.ts @@ -47,18 +47,17 @@ export default class Facebook extends Platform { post.files.image = []; } // facebook : max 4mb images - for (const image of post.files.image) { - const size = - fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); + for (const src of post.files.image) { + const dst = this.assetsFolder() + "/facebook-" + src; + const size = fs.statSync(post.getFullPath(src)).size / (1024 * 1024); if (size >= 4) { - Logger.trace("Resizing " + image + " for facebook .."); - await sharp(post.folder.path + "/" + image) + Logger.trace("Resizing " + src + " for facebook .."); + await sharp(post.getFullPath(src)) .resize({ width: 1200, }) - .toFile(post.folder.path + "/_facebook-" + image); - post.files.image.push("_facebook-" + image); - post.files.image = post.files.image.filter((file) => file !== image); + .toFile(post.getFullPath(dst)); + post.useAlternativeFile(src, dst); } } post.save(); diff --git a/src/platforms/Instagram.ts b/src/platforms/Instagram.ts index 214cf5d..6b92882 100644 --- a/src/platforms/Instagram.ts +++ b/src/platforms/Instagram.ts @@ -55,23 +55,19 @@ export default class Instagram extends Platform { } // instagram : scale images, jpeg only - for (const image of post.files.image) { - const metadata = await sharp(post.folder.path + "/" + image).metadata(); + for (const src of post.files.image) { + const metadata = await sharp(post.getFullPath(src)).metadata(); if (metadata.width > 1440) { - Logger.trace("Resizing " + image + " for instagram .."); - const extension = image.split(".")?.pop(); - const basename = path.basename( - image, - extension ? "." + extension : "", - ); - const outfile = "_instagram-" + basename + ".JPEG"; - await sharp(post.folder.path + "/" + image) + Logger.trace("Resizing " + src + " for instagram .."); + const extension = src.split(".")?.pop(); + const basename = path.basename(src, extension ? "." + extension : ""); + const dst = this.assetsFolder() + "/instagram-" + basename + ".JPEG"; + await sharp(post.getFullPath(src)) .resize({ width: 1440, }) - .toFile(post.folder.path + "/" + outfile); - post.files.image.push(outfile); - post.files.image = post.files.image.filter((file) => file !== image); + .toFile(post.getFullPath(dst)); + post.useAlternativeFile(src, dst); } } diff --git a/src/platforms/LinkedIn.ts b/src/platforms/LinkedIn.ts index d5536fc..3708cd7 100644 --- a/src/platforms/LinkedIn.ts +++ b/src/platforms/LinkedIn.ts @@ -53,18 +53,17 @@ export default class LinkedIn extends Platform { } // linkedin: max 5mb images - for (const image of post.files.image) { - const size = - fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); + for (const src of post.files.image) { + const dst = this.assetsFolder() + "/linkedin-" + src; + const size = fs.statSync(post.getFullPath(src)).size / (1024 * 1024); if (size >= 5) { - Logger.trace("Resizing " + image + " for linkedin .."); - await sharp(post.folder.path + "/" + image) + Logger.trace("Resizing " + src + " for linkedin .."); + await sharp(post.getFullPath(src)) .resize({ width: 1200, }) - .toFile(post.folder.path + "/_linkedin-" + image); - post.files.image.push("_linkedin-" + image); - post.files.image = post.files.image.filter((file) => file !== image); + .toFile(post.getFullPath(dst)); + post.useAlternativeFile(src, dst); } } post.save(); diff --git a/src/platforms/Reddit.ts b/src/platforms/Reddit.ts index 1a44600..984e6c8 100644 --- a/src/platforms/Reddit.ts +++ b/src/platforms/Reddit.ts @@ -48,27 +48,24 @@ export default class Reddit extends Platform { if (post) { // reddit: max 1 image or video // TODO: extract video thumbnail - if (false && post.files.video.length >= 1) { // eslint-disable-line + if (post.files.video.length >= 1) { // eslint-disable-line post.files.video.length = 1; post.files.image = []; } else if (post.files.image.length > 1) { // 20971520 - const image = post.files.image[0]; - const metadata = await sharp(post.folder.path + "/" + image).metadata(); + const src = post.files.image[0]; + const metadata = await sharp(post.getFullPath(src)).metadata(); if (metadata.width > 3000) { - Logger.trace("Resizing " + image + " for reddit .."); - const extension = image.split(".")?.pop(); - const basename = path.basename( - image, - extension ? "." + extension : "", - ); - const outfile = "_reddit-" + basename + ".jpg"; - await sharp(post.folder.path + "/" + image) + Logger.trace("Resizing " + src + " for reddit .."); + const extension = src.split(".")?.pop(); + const basename = path.basename(src, extension ? "." + extension : ""); + const dst = this.assetsFolder() + "/reddit-" + basename + ".jpg"; + await sharp(post.getFullPath(src)) .resize({ width: 3000, }) - .toFile(post.folder.path + "/" + outfile); - post.files.image = [outfile]; + .toFile(post.getFullPath(dst)); + post.files.image = [dst]; } } post.save(); diff --git a/src/platforms/Twitter.ts b/src/platforms/Twitter.ts index 96d0be8..72434e7 100644 --- a/src/platforms/Twitter.ts +++ b/src/platforms/Twitter.ts @@ -52,18 +52,17 @@ export default class Twitter extends Platform { post.files.image.length = 4; } // twitter: max 5mb images - for (const image of post.files.image) { - const size = - fs.statSync(post.folder.path + "/" + image).size / (1024 * 1024); + for (const src of post.files.image) { + const dst = this.assetsFolder() + "/twitter-" + src; + const size = fs.statSync(post.getFullPath(src)).size / (1024 * 1024); if (size >= 5) { - Logger.trace("Resizing " + image + " for twitter .."); - await sharp(post.folder.path + "/" + image) + Logger.trace("Resizing " + src + " for twitter .."); + await sharp(post.getFullPath(src)) .resize({ width: 1200, }) - .toFile(post.folder.path + "/_twitter-" + image); - post.files.image.push("_twitter-" + image); - post.files.image = post.files.image.filter((file) => file !== image); + .toFile(post.getFullPath(dst)); + post.useAlternativeFile(src, dst); } } post.save();