From 5e35a2fdbc105c312a3a1fc81da5107e9dbdf4b9 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 10 Aug 2024 09:35:09 +0200 Subject: [PATCH 1/6] feat: Rename Folder to Source --- src/models/Feed.ts | 2 +- src/models/Platform.ts | 2 +- src/models/Post.ts | 2 +- src/models/{Folder.ts => Source.ts} | 0 src/platforms/Facebook/Facebook.ts | 2 +- src/platforms/Instagram/Instagram.ts | 2 +- src/platforms/LinkedIn/LinkedIn.ts | 2 +- src/platforms/Reddit/Reddit.ts | 2 +- src/platforms/Twitter/Twitter.ts | 2 +- src/platforms/YouTube/YouTube.ts | 2 +- src/plugins/ImageSize.ts | 2 +- src/plugins/LimitFiles.ts | 2 +- 12 files changed, 11 insertions(+), 11 deletions(-) rename src/models/{Folder.ts => Source.ts} (100%) diff --git a/src/models/Feed.ts b/src/models/Feed.ts index 681fb1d..d02677b 100644 --- a/src/models/Feed.ts +++ b/src/models/Feed.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; -import Folder from "./Folder"; +import Folder from "./Source"; import Platform from "./Platform"; import { PlatformId } from "../platforms"; import Post from "./Post"; diff --git a/src/models/Platform.ts b/src/models/Platform.ts index 51ab3ef..b748bdb 100644 --- a/src/models/Platform.ts +++ b/src/models/Platform.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as pluginClasses from "../plugins"; -import Folder, { FileGroup } from "./Folder"; +import Folder, { FileGroup } from "./Source"; import { PlatformId } from "../platforms"; import Plugin from "./Plugin"; diff --git a/src/models/Post.ts b/src/models/Post.ts index 9404d62..85f34ce 100644 --- a/src/models/Post.ts +++ b/src/models/Post.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; -import Folder, { FileGroup, FileInfo } from "./Folder"; +import Folder, { FileGroup, FileInfo } from "./Source"; import Platform from "./Platform"; import { isSimilarArray } from "../utilities"; diff --git a/src/models/Folder.ts b/src/models/Source.ts similarity index 100% rename from src/models/Folder.ts rename to src/models/Source.ts diff --git a/src/platforms/Facebook/Facebook.ts b/src/platforms/Facebook/Facebook.ts index 7e507df..4172fcd 100644 --- a/src/platforms/Facebook/Facebook.ts +++ b/src/platforms/Facebook/Facebook.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as path from "path"; -import Folder, { FileGroup } from "../../models/Folder"; +import Folder, { FileGroup } from "../../models/Source"; import FacebookApi from "./FacebookApi"; import FacebookAuth from "./FacebookAuth"; diff --git a/src/platforms/Instagram/Instagram.ts b/src/platforms/Instagram/Instagram.ts index d65d1af..2a810d8 100644 --- a/src/platforms/Instagram/Instagram.ts +++ b/src/platforms/Instagram/Instagram.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as path from "path"; -import Folder, { FileGroup } from "../../models/Folder"; +import Folder, { FileGroup } from "../../models/Source"; import InstagramApi from "./InstagramApi"; import InstagramAuth from "./InstagramAuth"; diff --git a/src/platforms/LinkedIn/LinkedIn.ts b/src/platforms/LinkedIn/LinkedIn.ts index 0557cdb..c3488c9 100644 --- a/src/platforms/LinkedIn/LinkedIn.ts +++ b/src/platforms/LinkedIn/LinkedIn.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; -import Folder, { FileGroup } from "../../models/Folder"; +import Folder, { FileGroup } from "../../models/Source"; import { handleApiError, handleEmptyResponse } from "../../utilities"; import LinkedInApi from "./LinkedInApi"; diff --git a/src/platforms/Reddit/Reddit.ts b/src/platforms/Reddit/Reddit.ts index b92f6a8..4946b92 100644 --- a/src/platforms/Reddit/Reddit.ts +++ b/src/platforms/Reddit/Reddit.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as path from "path"; -import Folder, { FileGroup } from "../../models/Folder"; +import Folder, { FileGroup } from "../../models/Source"; import Platform from "../../models/Platform"; import { PlatformId } from ".."; diff --git a/src/platforms/Twitter/Twitter.ts b/src/platforms/Twitter/Twitter.ts index 608bc73..48ec2d0 100644 --- a/src/platforms/Twitter/Twitter.ts +++ b/src/platforms/Twitter/Twitter.ts @@ -1,4 +1,4 @@ -import Folder, { FileGroup } from "../../models/Folder"; +import Folder, { FileGroup } from "../../models/Source"; import Platform from "../../models/Platform"; import { PlatformId } from ".."; diff --git a/src/platforms/YouTube/YouTube.ts b/src/platforms/YouTube/YouTube.ts index e9caf0f..8dec5b1 100644 --- a/src/platforms/YouTube/YouTube.ts +++ b/src/platforms/YouTube/YouTube.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; -import Folder, { FileGroup } from "../../models/Folder"; +import Folder, { FileGroup } from "../../models/Source"; import Platform from "../../models/Platform"; import { PlatformId } from ".."; diff --git a/src/plugins/ImageSize.ts b/src/plugins/ImageSize.ts index 859bc8a..b9ce2bb 100644 --- a/src/plugins/ImageSize.ts +++ b/src/plugins/ImageSize.ts @@ -1,4 +1,4 @@ -import { FileGroup, FileInfo } from "../models/Folder"; +import { FileGroup, FileInfo } from "../models/Source"; import Plugin from "../models/Plugin"; import Post from "../models/Post"; diff --git a/src/plugins/LimitFiles.ts b/src/plugins/LimitFiles.ts index af4b588..21d8a53 100644 --- a/src/plugins/LimitFiles.ts +++ b/src/plugins/LimitFiles.ts @@ -1,4 +1,4 @@ -import { FileGroup } from "../models/Folder"; +import { FileGroup } from "../models/Source"; import Plugin from "../models/Plugin"; import Post from "../models/Post"; From 1fd2635f4cb8536271e00aff88822a1d65e7ed7a Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 10 Aug 2024 09:52:18 +0200 Subject: [PATCH 2/6] feat: Rename [Ff]older to [Ss]ource --- README.md | 20 ++-- docs/NewPlatform.md | 8 +- src/cli.ts | 8 +- src/models/Feed.ts | 158 +++++++++++++-------------- src/models/Platform.ts | 42 +++---- src/models/Post.ts | 28 ++--- src/models/Source.ts | 20 ++-- src/platforms/Facebook/Facebook.ts | 8 +- src/platforms/Instagram/Instagram.ts | 8 +- src/platforms/LinkedIn/LinkedIn.ts | 8 +- src/platforms/Reddit/Reddit.ts | 8 +- src/platforms/Twitter/Twitter.ts | 8 +- src/platforms/YouTube/YouTube.ts | 8 +- src/plugins/ImageSize.ts | 6 +- src/services/CommandHandler.ts | 108 +++++++++--------- src/services/Server.ts | 10 +- 16 files changed, 228 insertions(+), 228 deletions(-) diff --git a/README.md b/README.md index 5162c91..e489491 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ nano users/foobar/.env ``` fairpost.js prepare-posts ``` -Folders need to be `prepared` (iow turned into posts) +Sources need to be `prepared` (iow turned into posts) before they can be published to a platform. Each platform, as defined in src/platforms, will handle the folder contents by itself. It may @@ -139,21 +139,21 @@ fairpost: @userid refresh-platform --platform=xxx fairpost: @userid refresh-platforms [--platforms=xxx,xxx] fairpost: @userid get-platform --platform=xxx fairpost: @userid get-platforms [--platforms=xxx,xxx] -fairpost: @userid get-folder --folder=xxx -fairpost: @userid get-folders [--folders=xxx,xxx] +fairpost: @userid get-source --source=xxx +fairpost: @userid get-sources [--sources=xxx,xxx] fairpost: @userid get-post --post=xxx:xxx -fairpost: @userid get-posts [--status=xxx] [--folders=xxx,xxx] [--platforms=xxx,xxx] +fairpost: @userid get-posts [--status=xxx] [--sources=xxx,xxx] [--platforms=xxx,xxx] fairpost: @userid prepare-post --post=xxx:xxx fairpost: @userid schedule-post --post=xxx:xxx --date=xxxx-xx-xx -fairpost: @userid schedule-posts [--folders=xxx,xxx|--folder=xxx] [--platforms=xxx,xxx|--platform=xxx] --date=xxxx-xx-xx +fairpost: @userid schedule-posts [--sources=xxx,xxx|--source=xxx] [--platforms=xxx,xxx|--platform=xxx] --date=xxxx-xx-xx fairpost: @userid schedule-next-post [--date=xxxx-xx-xx] [--platforms=xxx,xxx|--platform=xxx] fairpost: @userid publish-post --post=xxx:xxx [--dry-run] -fairpost: @userid publish-posts [--folders=xxx,xxx|--folder=xxx] [--platforms=xxx,xxx|--platform=xxx] +fairpost: @userid publish-posts [--sources=xxx,xxx|--source=xxx] [--platforms=xxx,xxx|--platform=xxx] # feed planning: -fairpost: @userid prepare-posts [--folders=xxx,xxx|--folder=xxx] [--platforms=xxx,xxx|--platform=xxx] -fairpost: @userid schedule-next-posts [--date=xxxx-xx-xx] [--folders=xxx,xxx] [--platforms=xxx,xxx] -fairpost: @userid publish-due-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] [--dry-run] +fairpost: @userid prepare-posts [--sources=xxx,xxx|--source=xxx] [--platforms=xxx,xxx|--platform=xxx] +fairpost: @userid schedule-next-posts [--date=xxxx-xx-xx] [--sources=xxx,xxx] [--platforms=xxx,xxx] +fairpost: @userid publish-due-posts [--sources=xxx,xxx] [--platforms=xxx,xxx] [--dry-run] # admin only: fairpost: create-user --userid=xxx @@ -177,7 +177,7 @@ fairpost.js [command] [arguments] --verbose To add support for a new platform, add a class to `src/platforms` extending `src/classes/Platform`. You want to override at least the -method `preparePost(folder)` and `publishPost(post,dryrun)`. +method `preparePost(source)` and `publishPost(post,dryrun)`. Then import your class and add a `platformId` for your platform in `src/platforms/index.ts` and enable your platformId in your `.env`. diff --git a/docs/NewPlatform.md b/docs/NewPlatform.md index dc9b0f3..e1b68ca 100644 --- a/docs/NewPlatform.md +++ b/docs/NewPlatform.md @@ -7,7 +7,7 @@ you can write your own code to support it. To add support for a new platform, add a class to `src/platforms` extending `src/classes/Platform`. You want to override at least the -method `preparePost(folder)` and `publishPost(post,dryrun)`. +method `preparePost(source)` and `publishPost(post,dryrun)`. Make sure not to throw errors in or below publishPost; instead, just return false and let the `Post.processResult()` itself. @@ -30,8 +30,8 @@ export default class FooBar extends Platform { } /** @inheritdoc */ - async preparePost(folder: Folder): Promise { - const post = await super.preparePost(folder); + async preparePost(source: Source): Promise { + const post = await super.preparePost(source); if (post) { // prepare your post here post.save(); @@ -151,7 +151,7 @@ settings using `User.get(...)`. ## A more elaborate setup As your platform gets bigger, you may want to chunk it -up in several classes. Create a folder `src/platforms/FooBar`, +up in several classes. Create a source `src/platforms/FooBar`, move your class there, and update the imports. ### FooBarApi.ts diff --git a/src/cli.ts b/src/cli.ts index b3e1c95..7d4a708 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -23,12 +23,12 @@ const USERID = (getOption("userid") as string) ?? ""; const OUTPUT = (getOption("output") as string) ?? "text"; const PLATFORMS = ((getOption("platforms") as string)?.split(",") as PlatformId[]) ?? undefined; -const FOLDERS = (getOption("folders") as string)?.split(",") ?? undefined; +const FOLDERS = (getOption("sources") as string)?.split(",") ?? undefined; const DATE = (getOption("date") as string) ?? undefined; const STATUS = (getOption("status") as PostStatus) ?? undefined; let PLATFORM = (getOption("platform") as string as PlatformId) ?? undefined; -let FOLDER = (getOption("folder") as string) ?? undefined; +let FOLDER = (getOption("source") as string) ?? undefined; const POST = (getOption("post") as string) ?? undefined; if (POST) { [FOLDER, PLATFORM] = POST.split(":") as [string, PlatformId]; @@ -51,8 +51,8 @@ async function main() { userid: USERID, platforms: PLATFORMS, platform: PLATFORM, - folders: FOLDERS, - folder: FOLDER, + sources: FOLDERS, + source: FOLDER, date: DATE ? new Date(DATE) : undefined, status: STATUS, }); diff --git a/src/models/Feed.ts b/src/models/Feed.ts index d02677b..dd39234 100644 --- a/src/models/Feed.ts +++ b/src/models/Feed.ts @@ -1,10 +1,10 @@ import * as fs from "fs"; -import Folder from "./Source"; import Platform from "./Platform"; import { PlatformId } from "../platforms"; import Post from "./Post"; import { PostStatus } from "./Post"; +import Source from "./Source"; import User from "./User"; /** @@ -12,7 +12,7 @@ import User from "./User"; * * You start a feed with a config, by default .env, which * defines the feed folder and platform settings. From the feed, - * you are able to get platforms, folders and posts, and + * you are able to get platforms, sources and posts, and * manage and publish those. */ export default class Feed { @@ -22,7 +22,7 @@ export default class Feed { platforms: { [id in PlatformId]?: Platform; } = {}; - folders: Folder[] = []; + sources: Source[] = []; interval: number; /** @@ -59,8 +59,8 @@ export default class Feed { report += "\n - path: " + this.path; report += "\n - platforms: " + Object.keys(this.platforms).join(); report += - "\n - folders: " + - this.getFolders() + "\n - sources: " + + this.getSources() .map((f) => f.id) .join(); return report; @@ -180,13 +180,13 @@ export default class Feed { } /** - * Get all folders - * @returns all folder in the feed + * Get all sources + * @returns all source in the feed */ - getAllFolders(): Folder[] { - this.user.trace("Feed", "getAllFolders"); - if (this.folders.length) { - return this.folders; + getAllSources(): Source[] { + this.user.trace("Feed", "getAllSources"); + if (this.sources.length) { + return this.sources; } if (!fs.existsSync(this.path)) { fs.mkdirSync(this.path); @@ -199,65 +199,65 @@ export default class Feed { ); }); if (paths) { - this.folders = paths.map((path) => new Folder(this.path + "/" + path)); + this.sources = paths.map((path) => new Source(this.path + "/" + path)); } - return this.folders; + return this.sources; } /** - * Get one folder - * @param path - path to a single folder - * @returns the given folder object + * Get one source + * @param path - path to a single source + * @returns the given source object */ - getFolder(path: string): Folder | undefined { - this.user.trace("Feed", "getFolder", path); - return this.getFolders([path])[0]; + getSource(path: string): Source | undefined { + this.user.trace("Feed", "getSource", path); + return this.getSources([path])[0]; } /** - * Get multiple folders - * @param paths - paths to multiple folders - * @returns the given folder objects + * Get multiple sources + * @param paths - paths to multiple sources + * @returns the given source objects */ - getFolders(paths?: string[]): Folder[] { - this.user.trace("Feed", "getFolders", paths); + getSources(paths?: string[]): Source[] { + this.user.trace("Feed", "getSources", paths); return ( - paths?.map((path) => new Folder(this.path + "/" + path)) ?? - this.getAllFolders() + paths?.map((path) => new Source(this.path + "/" + path)) ?? + this.getAllSources() ); } /** * Get one (prepared) post - * @param path - path to a single folder + * @param path - path to a single source * @param platformId - the platform for the post * @returns the given post, or undefined if not prepared */ getPost(path: string, platformId: PlatformId): Post | undefined { this.user.trace("Feed", "getPost"); - return this.getPosts({ folders: [path], platforms: [platformId] })[0]; + return this.getPosts({ sources: [path], platforms: [platformId] })[0]; } /** * Get multiple (prepared) posts * @param filters - object to filter posts by - * @param filters.folders - paths to folders to filter on + * @param filters.sources - paths to sources to filter on * @param filters.platforms - slugs to platforms to filter on * @param filters.status - post status to filter on * @returns multiple posts */ getPosts(filters?: { - folders?: string[]; + sources?: string[]; platforms?: PlatformId[]; status?: PostStatus; }): Post[] { this.user.trace("Feed", "getPosts"); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.folders); - for (const folder of folders) { + const sources = this.getSources(filters?.sources); + for (const source of sources) { for (const platform of platforms) { - const post = platform.getPost(folder); + const post = platform.getPost(source); if ( post && (!filters?.status || filters.status.includes(post.status)) @@ -271,7 +271,7 @@ export default class Feed { /** * Prepare single post - * @param path - path to a single folder + * @param path - path to a single source * @param platformId - the platform for the post * @returns the given post, or undefined if failed */ @@ -281,7 +281,7 @@ export default class Feed { ): Promise { this.user.trace("Feed", "preparePost", path, platformId); return ( - await this.preparePosts({ folders: [path], platforms: [platformId] }) + await this.preparePosts({ sources: [path], platforms: [platformId] }) )[0]; } @@ -292,18 +292,18 @@ export default class Feed { */ async preparePosts(filters?: { - folders?: string[]; + sources?: string[]; platforms?: PlatformId[]; }): Promise { this.user.trace("Feed", "preparePosts", filters); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.folders); - for (const folder of folders) { + const sources = this.getSources(filters?.sources); + for (const source of sources) { for (const platform of platforms) { - const post = platform.getPost(folder); + const post = platform.getPost(source); if (post?.status !== PostStatus.PUBLISHED) { - const newPost = await platform.preparePost(folder); + const newPost = await platform.preparePost(source); if (newPost) { posts.push(newPost); } @@ -315,7 +315,7 @@ export default class Feed { /** * Schedule single post - * @param path - path to a single folder + * @param path - path to a single source * @param platformId - the platform for the post * @param date - Date to schedule post on * @returns the given post @@ -344,37 +344,37 @@ export default class Feed { * * Note - this is for consistence only, it is actually unused * @param filters - object to filter posts by - * @param filters.folders - paths to folders to filter on + * @param filters.sources - paths to sources to filter on * @param filters.platforms - slugs to platforms to filter on * @param date - date to schedule posts on * @returns multiple posts */ schedulePosts( filters: { - folders: string[]; + sources: string[]; platforms?: PlatformId[]; }, date: Date, ): Post[] { this.user.trace("Feed", "schedulePosts", filters, date); - if (!filters.folders) { + if (!filters.sources) { if (!filters.platforms) { throw this.user.error( - "Feed.schedulePosts needs to filter on either folders or platforms", + "Feed.schedulePosts needs to filter on either sources or platforms", ); } } - if (filters.folders && filters.folders.length > 1) { + if (filters.sources && filters.sources.length > 1) { throw this.user.error( - "Feed.schedulePosts will cowardly only operate on one folder", + "Feed.schedulePosts will cowardly only operate on one source", ); } const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.folders); + const sources = this.getSources(filters?.sources); for (const platform of platforms) { - for (const folder of folders) { - const post = platform.getPost(folder); + for (const source of sources) { + const post = platform.getPost(source); if (!post) { throw this.user.error("Post not found"); } @@ -398,7 +398,7 @@ export default class Feed { * Publish single post * * Will publish the post regardless of its status or skip - * @param path - path to a single folder + * @param path - path to a single source * @param platformId - the platform for the post * @param dryrun - wether or not to really publish * @returns the given post @@ -412,11 +412,11 @@ export default class Feed { this.user.trace("Feed", "publishPost", path, platformId, dryrun); const now = new Date(); const platform = this.getPlatform(platformId); - const folder = this.getFolder(path); - if (!folder) { - throw this.user.error("Folder not found", path); + const source = this.getSource(path); + if (!source) { + throw this.user.error("Source not found", path); } - const post = platform.getPost(folder); + const post = platform.getPost(source); if (!post) { throw this.user.error("Post not found", path, platformId); } @@ -435,38 +435,38 @@ export default class Feed { * * Note - this is for consistence only, it is actually unused * @param filters - object to filter posts by - * @param filters.folders - paths to folders to filter on + * @param filters.sources - paths to sources to filter on * @param filters.platforms - slugs to platforms to filter on * @param dryrun - wether to really publish * @returns multiple posts */ async publishPosts( filters: { - folders: string[]; + sources: string[]; platforms?: PlatformId[]; }, dryrun: boolean = false, ): Promise { this.user.trace("Feed", "publishPosts", filters, dryrun); - if (!filters.folders) { + if (!filters.sources) { if (!filters.platforms) { throw this.user.error( - "Feed.schedulePosts needs to filter on either folders or platforms", + "Feed.schedulePosts needs to filter on either sources or platforms", ); } } - if (filters.folders && filters.folders.length > 1) { + if (filters.sources && filters.sources.length > 1) { throw this.user.error( - "Feed.schedulePosts will cowardly only operate on one folder", + "Feed.schedulePosts will cowardly only operate on one source", ); } const now = new Date(); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.folders); + const sources = this.getSources(filters?.sources); for (const platform of platforms) { - for (const folder of folders) { - const post = platform.getPost(folder); + for (const source of sources) { + const post = platform.getPost(source); if (post) { if (post.valid) { if (!post.skip) { @@ -481,7 +481,7 @@ export default class Feed { this.user.warn("Skipping invalid post", post.id); } } else { - this.user.warn("Skipping post not found", folder.id, platform.id); + this.user.warn("Skipping post not found", source.id, platform.id); } } } @@ -542,26 +542,26 @@ export default class Feed { /** * Schedule the first unscheduled post for multiple platforms * - * for each platform, within given folders are all folders, + * for each platform, within given sources are all sources, * finds the next post date and the first unscheduled post, * and schedules that post on that date * @param date - use date instead of the next post date - * @param filters - limit the process to certain platforms or folders - * @param filters.folders - paths to folders to filter on + * @param filters - limit the process to certain platforms or sources + * @param filters.sources - paths to sources to filter on * @param filters.platforms - slugs of platforms to filter on * @returns the scheduled posts */ scheduleNextPosts( date?: Date, filters?: { - folders?: string[]; + sources?: string[]; platforms?: PlatformId[]; }, ): Post[] { this.user.trace("Feed", "scheduleNextPosts"); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.folders); + const sources = this.getSources(filters?.sources); for (const platform of platforms) { const scheduledPosts = this.getPosts({ platforms: [platform.id], @@ -577,8 +577,8 @@ export default class Feed { continue; } const nextDate = date ? date : this.getNextPostDate(platform.id); - for (const folder of folders) { - const post = platform.getPost(folder); + for (const source of sources) { + const post = platform.getPost(source); if ( post && post.valid && @@ -597,18 +597,18 @@ export default class Feed { /** * Publish scheduled posts, one for each platform * - * for each platform, within given folders or all folders, + * for each platform, within given sources or all sources, * find the first post that is scheduled in the past and * publish that. - * @param filters - limit the process to certain platforms or folders - * @param filters.folders - paths to folder to filter on + * @param filters - limit the process to certain platforms or sources + * @param filters.sources - paths to source to filter on * @param filters.platforms - slugs of platforms to filter on * @param dryrun - wether to really publish posts * @returns the published posts */ async publishDuePosts( filters?: { - folders?: string[]; + sources?: string[]; platforms?: PlatformId[]; }, dryrun: boolean = false, @@ -617,10 +617,10 @@ export default class Feed { const now = new Date(); const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); - const folders = this.getFolders(filters?.folders); + const sources = this.getSources(filters?.sources); for (const platform of platforms) { - for (const folder of folders) { - const post = platform.getPost(folder); + for (const source of sources) { + const post = platform.getPost(source); if (post && post.status === PostStatus.SCHEDULED) { if (!post.scheduled) { this.user.warn( diff --git a/src/models/Platform.ts b/src/models/Platform.ts index b748bdb..1335d3f 100644 --- a/src/models/Platform.ts +++ b/src/models/Platform.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as pluginClasses from "../plugins"; -import Folder, { FileGroup } from "./Source"; +import Source, { FileGroup } from "./Source"; import { PlatformId } from "../platforms"; import Plugin from "./Plugin"; @@ -83,28 +83,28 @@ export default class Platform { /** * getPostFilePath - * @param folder the folder for the new or existing post + * @param source the source 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.postFileName; + getPostFilePath(source: Source): string { + return source.path + "/" + this.assetsFolder + "/" + this.postFileName; } /** * getPost - * @param folder - the folder to get the post for this platform from - * @returns {Post} the post for this platform for the given folder, if it exists. + * @param source - the source to get the post for this platform from + * @returns {Post} the post for this platform for the given source, if it exists. */ - getPost(folder: Folder): Post | undefined { + getPost(source: Source): Post | undefined { this.user.trace("Platform", "getPost"); - const postFilePath = this.getPostFilePath(folder); + const postFilePath = this.getPostFilePath(source); if (fs.existsSync(postFilePath)) { const data = JSON.parse(fs.readFileSync(postFilePath, "utf8")); if (data) { - return new Post(folder, this, data); + return new Post(source, this, data); } } return; @@ -114,7 +114,7 @@ export default class Platform { * preparePost * * Prepare the post for this platform for the - * given folder, and save it. Optionally create + * given source, and save it. Optionally create * derivates of media and save those, too. * * If the post exists and is published, ignore. @@ -127,13 +127,13 @@ export default class Platform { * Presume the post may have already been prepared * before, and manually adapted later. For example, * post.skip may have manually been set to true. - * @param folder - the folder for which to prepare a post for this platform + * @param source - the source for which to prepare a post for this platform * @returns the prepared post */ - async preparePost(folder: Folder): Promise { + async preparePost(source: Source): Promise { this.user.trace("Platform", "preparePost"); - const post = this.getPost(folder) ?? new Post(folder, this); + const post = this.getPost(source) ?? new Post(source, this); if (post.status === PostStatus.PUBLISHED) { return post; } @@ -151,7 +151,7 @@ export default class Platform { // been changed manually post.purgeFiles(); - const files = await folder.getFiles(); + const files = await source.getFiles(); files.forEach((file) => { if (!post.ignoreFiles?.includes(file.name)) { post.putFile(file); @@ -165,32 +165,32 @@ export default class Platform { const textFiles = post.getFiles(FileGroup.TEXT); if (post.hasFile("body.txt")) { - post.body = fs.readFileSync(post.folder.path + "/body.txt", "utf8"); + post.body = fs.readFileSync(post.source.path + "/body.txt", "utf8"); } else if (textFiles.length === 1) { const bodyFile = textFiles[0].name; - post.body = fs.readFileSync(post.folder.path + "/" + bodyFile, "utf8"); + post.body = fs.readFileSync(post.source.path + "/" + bodyFile, "utf8"); } else { post.body = this.defaultBody; } if (post.hasFile("title.txt")) { - post.title = fs.readFileSync(post.folder.path + "/title.txt", "utf8"); + post.title = fs.readFileSync(post.source.path + "/title.txt", "utf8"); } else if (post.hasFile("subject.txt")) { - post.title = fs.readFileSync(post.folder.path + "/subject.txt", "utf8"); + post.title = fs.readFileSync(post.source.path + "/subject.txt", "utf8"); } if (post.hasFile("tags.txt")) { post.tags = fs - .readFileSync(post.folder.path + "/tags.txt", "utf8") + .readFileSync(post.source.path + "/tags.txt", "utf8") .split(/\s/); } if (post.hasFile("mentions.txt")) { post.mentions = fs - .readFileSync(post.folder.path + "/mentions.txt", "utf8") + .readFileSync(post.source.path + "/mentions.txt", "utf8") .split(/\s/); } if (post.hasFile("geo.txt")) { - post.geo = fs.readFileSync(post.folder.path + "/geo.txt", "utf8"); + post.geo = fs.readFileSync(post.source.path + "/geo.txt", "utf8"); } // decompile the body to see if there are diff --git a/src/models/Post.ts b/src/models/Post.ts index 85f34ce..c370654 100644 --- a/src/models/Post.ts +++ b/src/models/Post.ts @@ -1,21 +1,21 @@ import * as fs from "fs"; -import Folder, { FileGroup, FileInfo } from "./Source"; +import Source, { FileGroup, FileInfo } from "./Source"; import Platform from "./Platform"; import { isSimilarArray } from "../utilities"; /** - * Post - a post within a folder + * Post - a post within a source * - * A post belongs to one platform and one folder; + * A post belongs to one platform and one source; * it is *prepared* and later *published* by the platform. - * The post serializes to a json file in the folder, + * The post serializes to a json file in the source, * where it can be read later for further processing. */ export default class Post { id: string; - folder: Folder; + source: Source; platform: Platform; valid: boolean = false; skip: boolean = false; @@ -33,10 +33,10 @@ export default class Post { link?: string; remoteId?: string; - constructor(folder: Folder, platform: Platform, data?: object) { - this.folder = folder; + constructor(source: Source, platform: Platform, data?: object) { + this.source = source; this.platform = platform; - this.id = this.folder.id + ":" + this.platform.id; + this.id = this.source.id + ":" + this.platform.id; if (data) { Object.assign(this, data); this.scheduled = this.scheduled ? new Date(this.scheduled) : undefined; @@ -76,10 +76,10 @@ export default class Post { this.platform.user.trace("Post", "save"); // eslint-disable-next-line @typescript-eslint/no-explicit-any const data = { ...this } as { [key: string]: any }; - delete data.folder; + delete data.source; delete data.platform; fs.writeFileSync( - this.platform.getPostFilePath(this.folder), + this.platform.getPostFilePath(this.source), JSON.stringify(data, null, "\t"), ); } @@ -336,7 +336,7 @@ export default class Post { async addFile(name: string): Promise { const index = this.files?.findIndex((file) => file.name === name) ?? -1; if (index === -1) { - const newFile = await this.folder.getFileInfo( + const newFile = await this.source.getFileInfo( name, this.files?.length ?? 0, ); @@ -398,7 +398,7 @@ export default class Post { if (index > -1) { const oldFile = this.getFile(search); if (this.files && oldFile) { - const newFile = await this.folder.getFileInfo(replace, oldFile.order); + const newFile = await this.source.getFileInfo(replace, oldFile.order); newFile.original = oldFile.name; this.files[index] = newFile; return this.files[index]; @@ -409,11 +409,11 @@ export default class Post { } /** - * @param name relative path in this post.folder + * @param name relative path in this post.source * @returns the full path to that file */ getFilePath(name: string): string { - return this.folder.path + "/" + name; + return this.source.path + "/" + name; } /** diff --git a/src/models/Source.ts b/src/models/Source.ts index 5e7fa79..a0a1a94 100644 --- a/src/models/Source.ts +++ b/src/models/Source.ts @@ -4,20 +4,20 @@ import * as path from "path"; import sharp from "sharp"; /** - * Folder - a folder within a feed + * Source - a folder within a feed * - * A folder represents one post on all enabled + * A source represents one post on all enabled * and applicable platforms. It is also just * a folder on a filesystem. */ -export default class Folder { +export default class Source { id: string; path: string; files?: FileInfo[]; constructor(path: string) { if (!fs.statSync(path).isDirectory()) { - throw new Error("No such folder: " + path); + throw new Error("No such source: " + path); } this.id = path.replace(/^\//, "").split("/").slice(1).join("/"); this.path = path; @@ -30,17 +30,17 @@ export default class Folder { report(): string { let report = ""; - report += "\nFolder: " + this.id; + report += "\nSource: " + this.id; report += "\n - path: " + this.path; report += "\n - files: " + this.getFileNames(); return report; } /** - * Get the files in this folder + * Get the files in this source * * reads info from disk once, then caches that - * @returns array of fileinfo for all files in this folder + * @returns array of fileinfo for all files in this source */ public async getFiles(): Promise { @@ -57,7 +57,7 @@ export default class Folder { /** * Get info for a single file - * @param name - name of the file in this folder + * @param name - name of the file in this source * @param order - order to set on this file * @returns fileinfo object for the file */ @@ -87,8 +87,8 @@ export default class Folder { } /** - * Get the filenames in this folder - * @returns array of filenames relative to folder + * Get the filenames in this source + * @returns array of filenames relative to source */ private getFileNames(): string[] { diff --git a/src/platforms/Facebook/Facebook.ts b/src/platforms/Facebook/Facebook.ts index 4172fcd..5e3598e 100644 --- a/src/platforms/Facebook/Facebook.ts +++ b/src/platforms/Facebook/Facebook.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as path from "path"; -import Folder, { FileGroup } from "../../models/Source"; +import Source, { FileGroup } from "../../models/Source"; import FacebookApi from "./FacebookApi"; import FacebookAuth from "./FacebookAuth"; @@ -51,9 +51,9 @@ export default class Facebook extends Platform { } /** @inheritdoc */ - async preparePost(folder: Folder): Promise { - this.user.trace("Facebook.preparePost", folder.id); - const post = await super.preparePost(folder); + async preparePost(source: Source): Promise { + this.user.trace("Facebook.preparePost", source.id); + const post = await super.preparePost(source); if (post && post.files) { const userPluginSettings = JSON.parse( this.user.get("settings", "FACEBOOK_PLUGIN_SETTINGS", "{}"), diff --git a/src/platforms/Instagram/Instagram.ts b/src/platforms/Instagram/Instagram.ts index 2a810d8..4936742 100644 --- a/src/platforms/Instagram/Instagram.ts +++ b/src/platforms/Instagram/Instagram.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as path from "path"; -import Folder, { FileGroup } from "../../models/Source"; +import Source, { FileGroup } from "../../models/Source"; import InstagramApi from "./InstagramApi"; import InstagramAuth from "./InstagramAuth"; @@ -57,9 +57,9 @@ export default class Instagram extends Platform { } /** @inheritdoc */ - async preparePost(folder: Folder): Promise { - this.user.trace("Instagram.preparePost", folder.id); - const post = await super.preparePost(folder); + async preparePost(source: Source): Promise { + this.user.trace("Instagram.preparePost", source.id); + const post = await super.preparePost(source); if (post && post.files) { // instagram: require media if ( diff --git a/src/platforms/LinkedIn/LinkedIn.ts b/src/platforms/LinkedIn/LinkedIn.ts index c3488c9..ebef71c 100644 --- a/src/platforms/LinkedIn/LinkedIn.ts +++ b/src/platforms/LinkedIn/LinkedIn.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; -import Folder, { FileGroup } from "../../models/Source"; +import Source, { FileGroup } from "../../models/Source"; import { handleApiError, handleEmptyResponse } from "../../utilities"; import LinkedInApi from "./LinkedInApi"; @@ -62,9 +62,9 @@ export default class LinkedIn extends Platform { } /** @inheritdoc */ - async preparePost(folder: Folder): Promise { - this.user.trace("LinkedIn.preparePost", folder.id); - const post = await super.preparePost(folder); + async preparePost(source: Source): Promise { + this.user.trace("LinkedIn.preparePost", source.id); + const post = await super.preparePost(source); if (post) { const userPluginSettings = JSON.parse( this.user.get("settings", "LINKEDIN_PLUGIN_SETTINGS", "{}"), diff --git a/src/platforms/Reddit/Reddit.ts b/src/platforms/Reddit/Reddit.ts index 4946b92..90f3b43 100644 --- a/src/platforms/Reddit/Reddit.ts +++ b/src/platforms/Reddit/Reddit.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as path from "path"; -import Folder, { FileGroup } from "../../models/Source"; +import Source, { FileGroup } from "../../models/Source"; import Platform from "../../models/Platform"; import { PlatformId } from ".."; @@ -64,9 +64,9 @@ export default class Reddit extends Platform { } /** @inheritdoc */ - async preparePost(folder: Folder): Promise { - this.user.trace("Reddit.preparePost", folder.id); - const post = await super.preparePost(folder); + async preparePost(source: Source): Promise { + this.user.trace("Reddit.preparePost", source.id); + const post = await super.preparePost(source); if (post) { // TODO: extract video thumbnail if (post.hasFiles(FileGroup.VIDEO)) { diff --git a/src/platforms/Twitter/Twitter.ts b/src/platforms/Twitter/Twitter.ts index 48ec2d0..0dccf3d 100644 --- a/src/platforms/Twitter/Twitter.ts +++ b/src/platforms/Twitter/Twitter.ts @@ -1,4 +1,4 @@ -import Folder, { FileGroup } from "../../models/Source"; +import Source, { FileGroup } from "../../models/Source"; import Platform from "../../models/Platform"; import { PlatformId } from ".."; @@ -70,9 +70,9 @@ export default class Twitter extends Platform { } /** @inheritdoc */ - async preparePost(folder: Folder): Promise { - this.user.trace("Twitter.preparePost", folder.id); - const post = await super.preparePost(folder); + async preparePost(source: Source): Promise { + this.user.trace("Twitter.preparePost", source.id); + const post = await super.preparePost(source); if (post) { const userPluginSettings = JSON.parse( this.user.get("settings", "TWITTER_PLUGIN_SETTINGS", "{}"), diff --git a/src/platforms/YouTube/YouTube.ts b/src/platforms/YouTube/YouTube.ts index 8dec5b1..797dff9 100644 --- a/src/platforms/YouTube/YouTube.ts +++ b/src/platforms/YouTube/YouTube.ts @@ -1,6 +1,6 @@ import * as fs from "fs"; -import Folder, { FileGroup } from "../../models/Source"; +import Source, { FileGroup } from "../../models/Source"; import Platform from "../../models/Platform"; import { PlatformId } from ".."; @@ -54,9 +54,9 @@ export default class YouTube extends Platform { } /** @inheritdoc */ - async preparePost(folder: Folder): Promise { - this.user.trace("YouTube.preparePost", folder.id); - const post = await super.preparePost(folder); + async preparePost(source: Source): Promise { + this.user.trace("YouTube.preparePost", source.id); + const post = await super.preparePost(source); if (post) { const userPluginSettings = JSON.parse( this.user.get("settings", "YOUTUBE_PLUGIN_SETTINGS", "{}"), diff --git a/src/plugins/ImageSize.ts b/src/plugins/ImageSize.ts index b9ce2bb..b016bfb 100644 --- a/src/plugins/ImageSize.ts +++ b/src/plugins/ImageSize.ts @@ -166,13 +166,13 @@ export default class ImageSize extends Plugin { .toFormat("jpg") // default q = 80 .toFile(post.getFilePath(dst)); await post.replaceFile(file.name, dst); - file = await post.folder.getFileInfo(dst, file.order); + file = await post.source.getFileInfo(dst, file.order); } } if (file.width && file.size / 1024 >= maxkb) { const newFileName = file.basename + "-small." + file.extension; const dst = post.platform.assetsFolder + "/" + newFileName; - let newfile = await post.folder.getFileInfo(file.name, file.order); + let newfile = await post.source.getFileInfo(file.name, file.order); let factor = 1; let count = 1; while (newfile.size / 1024 >= maxkb) { @@ -187,7 +187,7 @@ export default class ImageSize extends Plugin { width: Math.round(file.width * factor), }) .toFile(post.getFilePath(dst)); - newfile = await post.folder.getFileInfo(dst, file.order); + newfile = await post.source.getFileInfo(dst, file.order); if (count++ > 5) { throw post.platform.user.error( "ImageSize.reduceFileSize", diff --git a/src/services/CommandHandler.ts b/src/services/CommandHandler.ts index 7a8df61..d30ac30 100644 --- a/src/services/CommandHandler.ts +++ b/src/services/CommandHandler.ts @@ -165,39 +165,39 @@ class CommandHandler { report = "Result: \n" + JSON.stringify(result, null, "\t"); break; } - case "get-folder": { + case "get-source": { if (!feed) throw user.error("User " + user.id + " has no feed"); - if (!args.folder) { + if (!args.source) { throw user.error( "CommandHandler " + command, - "Missing argument: folder", + "Missing argument: source", ); } - const folder = feed.getFolder(args.folder); - if (folder) { - report += folder.report() + "\n"; - result = folder; + const source = feed.getSource(args.source); + if (source) { + report += source.report() + "\n"; + result = source; } else { - report += "not found:" + args.folder + "\n"; + report += "not found:" + args.source + "\n"; } break; } - case "get-folders": { + case "get-sources": { if (!feed) throw user.error("User " + user.id + " has no feed"); - const folders = feed.getFolders(args.folders); - report += folders.length + " Folders\n------\n"; - folders.forEach((folder) => { - report += folder.report() + "\n"; + const sources = feed.getSources(args.sources); + report += sources.length + " Sources\n------\n"; + sources.forEach((source) => { + report += source.report() + "\n"; }); - result = folders; + result = sources; break; } case "get-post": { if (!feed) throw user.error("User " + user.id + " has no feed"); - if (!args.folder) { + if (!args.source) { throw user.error( "CommandHandler " + command, - "Missing argument: folder", + "Missing argument: source", ); } if (!args.platform) { @@ -206,19 +206,19 @@ class CommandHandler { "Missing argument: platform", ); } - const post = feed.getPost(args.folder, args.platform); + const post = feed.getPost(args.source, args.platform); if (post) { report += post.report(); result = post; } else { - report += "Not found:" + args.folder + ":" + args.platform; + report += "Not found:" + args.source + ":" + args.platform; } break; } case "get-posts": { if (!feed) throw user.error("User " + user.id + " has no feed"); const allposts = feed.getPosts({ - folders: args.folders, + sources: args.sources, platforms: args.platforms, status: args.status, }); @@ -231,10 +231,10 @@ class CommandHandler { } case "prepare-post": { if (!feed) throw user.error("User " + user.id + " has no feed"); - if (!args.folder) { + if (!args.source) { throw user.error( "CommandHandler " + command, - "Missing argument: folder", + "Missing argument: source", ); } if (!args.platform) { @@ -243,12 +243,12 @@ class CommandHandler { "Missing argument: platform", ); } - const preppost = await feed.preparePost(args.folder, args.platform); + const preppost = await feed.preparePost(args.source, args.platform); if (preppost) { report += preppost.report(); result = preppost; } else { - report += "Failed: " + args.folder + ":" + args.platform; + report += "Failed: " + args.source + ":" + args.platform; } break; } @@ -257,11 +257,11 @@ class CommandHandler { if (!args.platforms && args.platform) { args.platforms = [args.platform]; } - if (!args.folders && args.folder) { - args.folders = [args.folder]; + if (!args.sources && args.source) { + args.sources = [args.source]; } const prepposts = await feed.preparePosts({ - folders: args.folders, + sources: args.sources, platforms: args.platforms, }); prepposts.forEach((post) => { @@ -272,10 +272,10 @@ class CommandHandler { } case "schedule-post": { if (!feed) throw user.error("User " + user.id + " has no feed"); - if (!args.folder) { + if (!args.source) { throw user.error( "CommandHandler " + command, - "Missing argument: folder", + "Missing argument: source", ); } if (!args.platform) { @@ -291,7 +291,7 @@ class CommandHandler { ); } const schedpost = feed.schedulePost( - args.folder, + args.source, args.platform, args.date, ); @@ -304,13 +304,13 @@ class CommandHandler { if (!args.platforms && args.platform) { args.platforms = [args.platform]; } - if (!args.folders && args.folder) { - args.folders = [args.folder]; + if (!args.sources && args.source) { + args.sources = [args.source]; } - if (!args.folders) { + if (!args.sources) { throw user.error( "CommandHandler " + command, - "Missing argument: folders", + "Missing argument: sources", ); } if (!args.date) { @@ -321,7 +321,7 @@ class CommandHandler { } const schedposts = feed.schedulePosts( { - folders: args.folders, + sources: args.sources, platforms: args.platforms, }, new Date(args.date), @@ -357,10 +357,10 @@ class CommandHandler { } case "publish-post": { if (!feed) throw user.error("User " + user.id + " has no feed"); - if (!args.folder) { + if (!args.source) { throw user.error( "CommandHandler " + command, - "Missing argument: folder", + "Missing argument: source", ); } if (!args.platform) { @@ -370,7 +370,7 @@ class CommandHandler { ); } const pubpost = await feed.publishPost( - args.folder, + args.source, args.platform, args.dryrun, ); @@ -383,18 +383,18 @@ class CommandHandler { if (!args.platforms && args.platform) { args.platforms = [args.platform]; } - if (!args.folders && args.folder) { - args.folders = [args.folder]; + if (!args.sources && args.source) { + args.sources = [args.source]; } - if (!args.folders) { + if (!args.sources) { throw user.error( "CommandHandler " + command, - "Missing argument: folders", + "Missing argument: sources", ); } const pubposts = await feed.publishPosts( { - folders: args.folders, + sources: args.sources, platforms: args.platforms, }, args.dryrun, @@ -412,7 +412,7 @@ class CommandHandler { const nextposts = feed.scheduleNextPosts( args.date ? new Date(args.date) : undefined, { - folders: args.folders, + sources: args.sources, platforms: args.platforms, }, ); @@ -426,7 +426,7 @@ class CommandHandler { if (!feed) throw user.error("User " + user.id + " has no feed"); const dueposts = await feed.publishDuePosts( { - folders: args.folders, + sources: args.sources, platforms: args.platforms, }, args.dryrun, @@ -473,20 +473,20 @@ class CommandHandler { `${cmd} @userid refresh-platforms [--platforms=xxx,xxx]`, `${cmd} @userid get-platform --platform=xxx`, `${cmd} @userid get-platforms [--platforms=xxx,xxx]`, - `${cmd} @userid get-folder --folder=xxx`, - `${cmd} @userid get-folders [--folders=xxx,xxx]`, + `${cmd} @userid get-source --source=xxx`, + `${cmd} @userid get-sources [--sources=xxx,xxx]`, `${cmd} @userid get-post --post=xxx:xxx`, - `${cmd} @userid get-posts [--status=xxx] [--folders=xxx,xxx] [--platforms=xxx,xxx] `, + `${cmd} @userid get-posts [--status=xxx] [--sources=xxx,xxx] [--platforms=xxx,xxx] `, `${cmd} @userid prepare-post --post=xxx:xxx`, `${cmd} @userid schedule-post --post=xxx:xxx --date=xxxx-xx-xx `, - `${cmd} @userid schedule-posts [--folders=xxx,xxx|--folder=xxx] [--platforms=xxx,xxx|--platform=xxx] --date=xxxx-xx-xx`, + `${cmd} @userid schedule-posts [--sources=xxx,xxx|--source=xxx] [--platforms=xxx,xxx|--platform=xxx] --date=xxxx-xx-xx`, `${cmd} @userid schedule-next-post [--date=xxxx-xx-xx] [--platforms=xxx,xxx|--platform=xxx] `, `${cmd} @userid publish-post --post=xxx:xxx [--dry-run]`, - `${cmd} @userid publish-posts [--folders=xxx,xxx|--folder=xxx] [--platforms=xxx,xxx|--platform=xxx]`, + `${cmd} @userid publish-posts [--sources=xxx,xxx|--source=xxx] [--platforms=xxx,xxx|--platform=xxx]`, "\n# feed planning:", - `${cmd} @userid prepare-posts [--folders=xxx,xxx|--folder=xxx] [--platforms=xxx,xxx|--platform=xxx]`, - `${cmd} @userid schedule-next-posts [--date=xxxx-xx-xx] [--folders=xxx,xxx] [--platforms=xxx,xxx] `, - `${cmd} @userid publish-due-posts [--folders=xxx,xxx] [--platforms=xxx,xxx] [--dry-run]`, + `${cmd} @userid prepare-posts [--sources=xxx,xxx|--source=xxx] [--platforms=xxx,xxx|--platform=xxx]`, + `${cmd} @userid schedule-next-posts [--date=xxxx-xx-xx] [--sources=xxx,xxx] [--platforms=xxx,xxx] `, + `${cmd} @userid publish-due-posts [--sources=xxx,xxx] [--platforms=xxx,xxx] [--dry-run]`, "\n# admin only:", `${cmd} create-user --userid=xxx`, `${cmd} get-user --userid=xxx`, @@ -506,8 +506,8 @@ interface CommandArguments { userid?: string; platforms?: PlatformId[]; platform?: PlatformId; - folders?: string[]; - folder?: string; + sources?: string[]; + source?: string; date?: Date; status?: PostStatus; } diff --git a/src/services/Server.ts b/src/services/Server.ts index 8f1f3a2..5e92c62 100644 --- a/src/services/Server.ts +++ b/src/services/Server.ts @@ -49,16 +49,16 @@ export default class Server { const userid = parsed.searchParams.get("userid") || undefined; const date = parsed.searchParams.get("date"); const post = parsed.searchParams.get("post"); - const [folder, platform] = post + const [source, platform] = post ? (post.split(":") as [string, PlatformId]) : [ - parsed.searchParams.get("folder") || undefined, + parsed.searchParams.get("source") || undefined, (parsed.searchParams.get("platform") as PlatformId) || undefined, ]; const platforms = parsed.searchParams.get("platforms")?.split(",") as | PlatformId[] | undefined; - const folders = parsed.searchParams.get("folders")?.split(","); + const sources = parsed.searchParams.get("sources")?.split(","); const status = (parsed.searchParams.get("status") as PostStatus) || undefined; @@ -67,8 +67,8 @@ export default class Server { userid: userid, platforms: platforms, platform: platform, - folders: folders, - folder: folder, + sources: sources, + source: source, date: date ? new Date(date) : undefined, status: status, }; From 04c5ae9efb23c51ff2c89a432ed97c5299acb627 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 10 Aug 2024 10:00:12 +0200 Subject: [PATCH 3/6] feat: Rename FOLDER(S) to SOURCE(S) --- docs/NewPlatform.md | 2 +- src/cli.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/NewPlatform.md b/docs/NewPlatform.md index e1b68ca..b2be71a 100644 --- a/docs/NewPlatform.md +++ b/docs/NewPlatform.md @@ -151,7 +151,7 @@ settings using `User.get(...)`. ## A more elaborate setup As your platform gets bigger, you may want to chunk it -up in several classes. Create a source `src/platforms/FooBar`, +up in several classes. Create a folder `src/platforms/FooBar`, move your class there, and update the imports. ### FooBarApi.ts diff --git a/src/cli.ts b/src/cli.ts index 7d4a708..3290472 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -23,15 +23,15 @@ const USERID = (getOption("userid") as string) ?? ""; const OUTPUT = (getOption("output") as string) ?? "text"; const PLATFORMS = ((getOption("platforms") as string)?.split(",") as PlatformId[]) ?? undefined; -const FOLDERS = (getOption("sources") as string)?.split(",") ?? undefined; +const SOURCES = (getOption("sources") as string)?.split(",") ?? undefined; const DATE = (getOption("date") as string) ?? undefined; const STATUS = (getOption("status") as PostStatus) ?? undefined; let PLATFORM = (getOption("platform") as string as PlatformId) ?? undefined; -let FOLDER = (getOption("source") as string) ?? undefined; +let SOURCE = (getOption("source") as string) ?? undefined; const POST = (getOption("post") as string) ?? undefined; if (POST) { - [FOLDER, PLATFORM] = POST.split(":") as [string, PlatformId]; + [SOURCE, PLATFORM] = POST.split(":") as [string, PlatformId]; } // utilities @@ -51,8 +51,8 @@ async function main() { userid: USERID, platforms: PLATFORMS, platform: PLATFORM, - sources: FOLDERS, - source: FOLDER, + sources: SOURCES, + source: SOURCE, date: DATE ? new Date(DATE) : undefined, status: STATUS, }); From 98097c993a830400854616e8ad6720c14f1bf03a Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 10 Aug 2024 11:37:04 +0200 Subject: [PATCH 4/6] feat: Add support for source status (===post.status amalgamated) --- src/models/Feed.ts | 58 ++++++++++++++++++++++++++++++---- src/services/CommandHandler.ts | 25 +++++++++++++++ 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/src/models/Feed.ts b/src/models/Feed.ts index dd39234..2b1b82a 100644 --- a/src/models/Feed.ts +++ b/src/models/Feed.ts @@ -209,9 +209,9 @@ export default class Feed { * @param path - path to a single source * @returns the given source object */ - getSource(path: string): Source | undefined { + getSource(path: string): Source { this.user.trace("Feed", "getSource", path); - return this.getSources([path])[0]; + return new Source(this.path + "/" + path); } /** @@ -221,10 +221,52 @@ export default class Feed { */ getSources(paths?: string[]): Source[] { this.user.trace("Feed", "getSources", paths); - return ( - paths?.map((path) => new Source(this.path + "/" + path)) ?? - this.getAllSources() - ); + return paths?.map((path) => this.getSource(path)) ?? this.getAllSources(); + } + + /** + * Get one source status + * @param path - path to a single source + * @returns an amalgation of the sources post statusses + * + * if there are no posts, its unknown + * if at least one post is failed, its failed + * if at least one post is scheduled, its scheduled + * if all posts are published, its published + * otherwise its unscheduled + */ + getSourceStatus(path: string): PostStatus { + this.user.trace("Feed", "getSourceStatus", path); + const posts = this.getPosts({ sources: [path] }); + if (!posts.length) { + return PostStatus.UNKNOWN; + } + let haveScheduled = false; + let haveFailed = false; + let allPublished = true; + for (const post of posts) { + if (post.valid && !post.skip) { + if (post.status === PostStatus.SCHEDULED) { + haveScheduled = true; + } + if (post.status === PostStatus.FAILED) { + haveFailed = true; + } + if (post.status !== PostStatus.PUBLISHED) { + allPublished = false; + } + } + } + if (haveFailed) { + return PostStatus.FAILED; + } + if (haveScheduled) { + return PostStatus.SCHEDULED; + } + if (allPublished) { + return PostStatus.PUBLISHED; + } + return PostStatus.UNSCHEDULED; } /** @@ -235,7 +277,9 @@ export default class Feed { */ getPost(path: string, platformId: PlatformId): Post | undefined { this.user.trace("Feed", "getPost"); - return this.getPosts({ sources: [path], platforms: [platformId] })[0]; + const platform = this.getPlatform(platformId); + const source = this.getSource(path); + return platform.getPost(source); } /** diff --git a/src/services/CommandHandler.ts b/src/services/CommandHandler.ts index d30ac30..bd8862a 100644 --- a/src/services/CommandHandler.ts +++ b/src/services/CommandHandler.ts @@ -8,6 +8,7 @@ import { PlatformId } from "../platforms"; import { PostStatus } from "../models/Post"; import Server from "../services/Server"; +import Source from "../models/Source"; import User from "../models/User"; class CommandHandler { @@ -192,6 +193,30 @@ class CommandHandler { result = sources; break; } + case "get-sources-by-status": { + if (!feed) throw user.error("User " + user.id + " has no feed"); + const sources = feed.getSources(args.sources); + const groups = {} as { [status: string]: Source[] }; + sources.forEach((source) => { + const status = feed.getSourceStatus(source.path); + if (groups[status] === undefined) { + groups[status] = [source]; + } else { + groups[status].push(source); + } + report += source.report() + "\n"; + }); + Object.keys(groups).forEach((status) => { + report += " " + status + "\n------\n"; + const sources = groups[status]; + report += sources.length + " Sources\n------\n"; + sources.forEach((source) => { + report += source.report() + "\n"; + }); + }); + result = groups; + break; + } case "get-post": { if (!feed) throw user.error("User " + user.id + " has no feed"); if (!args.source) { From 7de3e8c4fc4c552433910bf6f3ac5a0aad082e4a Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 10 Aug 2024 22:37:26 +0200 Subject: [PATCH 5/6] fix: Small bugs when testing --- src/models/Feed.ts | 16 +++++++++++----- src/models/Platform.ts | 2 +- src/platforms/Twitter/Twitter.ts | 3 ++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/models/Feed.ts b/src/models/Feed.ts index 2b1b82a..ee74ab4 100644 --- a/src/models/Feed.ts +++ b/src/models/Feed.ts @@ -416,20 +416,26 @@ export default class Feed { const posts: Post[] = []; const platforms = this.getPlatforms(filters?.platforms); const sources = this.getSources(filters?.sources); - for (const platform of platforms) { - for (const source of sources) { + for (const source of sources) { + for (const platform of platforms) { const post = platform.getPost(source); if (!post) { throw this.user.error("Post not found"); } if (!post.valid) { - throw this.user.error("Post is not valid"); + this.user.warn("Feed", "schedulePosts", "Post is not valid"); + continue; } if (post.skip) { - throw this.user.error("Post is marked to be skipped"); + this.user.warn( + "Feed", + "schedulePosts", + "Post is marked to be skipped", + ); + continue; } if (post.status !== PostStatus.UNSCHEDULED) { - throw this.user.error("Post is not unscheduled"); + this.user.warn("Feed", "schedulePosts", "Rescheduling post"); } post.schedule(date); posts.push(post); diff --git a/src/models/Platform.ts b/src/models/Platform.ts index 1335d3f..f7f6b8e 100644 --- a/src/models/Platform.ts +++ b/src/models/Platform.ts @@ -98,7 +98,7 @@ export default class Platform { */ getPost(source: Source): Post | undefined { - this.user.trace("Platform", "getPost"); + this.user.trace("Platform", "getPost", this.id, source.id); const postFilePath = this.getPostFilePath(source); if (fs.existsSync(postFilePath)) { diff --git a/src/platforms/Twitter/Twitter.ts b/src/platforms/Twitter/Twitter.ts index 0dccf3d..feba023 100644 --- a/src/platforms/Twitter/Twitter.ts +++ b/src/platforms/Twitter/Twitter.ts @@ -86,7 +86,8 @@ export default class Twitter extends Platform { await plugin.process(post); } // twitter requires a real body - if (!post.body) { + if (!post.getCompiledBody()) { + this.user.warn("Twitter post has no body"); post.valid = false; } post.save(); From 9a7f95690d61a0260686d7b31e27119f749be545 Mon Sep 17 00:00:00 2001 From: pike Date: Sat, 10 Aug 2024 23:50:07 +0200 Subject: [PATCH 6/6] feat: Add ImageFrame plugin --- src/plugins/ImageFrame.ts | 113 ++++++++++++++++++++++++++++++++++++++ src/plugins/index.ts | 1 + 2 files changed, 114 insertions(+) create mode 100644 src/plugins/ImageFrame.ts diff --git a/src/plugins/ImageFrame.ts b/src/plugins/ImageFrame.ts new file mode 100644 index 0000000..40df1ea --- /dev/null +++ b/src/plugins/ImageFrame.ts @@ -0,0 +1,113 @@ +import { FileGroup, FileInfo } from "../models/Source"; + +import Plugin from "../models/Plugin"; +import Post from "../models/Post"; +import sharp from "sharp"; + +/** + * Plugin ImageFrame. + * + * Add single or double border around images from Post + * + */ + +interface ImageFrameSettings { + inner_width: string | number; // set to 0 or "" to ignore + inner_color: string; // css color + outer_width: string | number; // set to 0 or "" to ignore + outer_color: string; // css color +} + +// https://sharp.pixelplumbing.com/api-resize#extend + +export default class ImageFrame extends Plugin { + static defaults: ImageFrameSettings = { + inner_width: "1%", + inner_color: "black", + outer_width: "9%", + outer_color: "white", + }; + settings: ImageFrameSettings; + + constructor(settings?: object) { + super(); + this.settings = { + ...ImageFrame.defaults, + ...(settings ?? {}), + }; + } + + /** + * Process the post + */ + + async process(post: Post): Promise { + post.platform.user.trace(this.id, post.id, "process"); + for (const file of post.getFiles(FileGroup.IMAGE)) { + await this.addImageFrame(post, file); + } + } + + private async addImageFrame(post: Post, file: FileInfo) { + if (file.width && file.height) { + const size = Math.min(file.width, file.height); + const newFileName = file.basename + "-framed." + file.extension; + const src = file.name; + const dst = post.platform.assetsFolder + "/" + newFileName; + + const source = sharp(post.getFilePath(src)); + + let innerBuffer = await source.toBuffer(); + if (this.settings.inner_width) { + let inner_width = 0; + if (typeof this.settings.inner_width === "string") { + if (this.settings.inner_width.endsWith("%")) { + inner_width = Math.round( + (size * parseInt(this.settings.inner_width)) / 100, + ); + } else { + inner_width = parseInt(this.settings.inner_width); + } + } else { + inner_width = this.settings.inner_width; + } + innerBuffer = await source + .extend({ + top: inner_width, + bottom: inner_width, + left: inner_width, + right: inner_width, + background: this.settings.inner_color, + }) + .toBuffer(); + } + + let outerBuffer = innerBuffer; + if (this.settings.outer_width) { + let outer_width = 0; + if (typeof this.settings.outer_width === "string") { + if (this.settings.outer_width.endsWith("%")) { + outer_width = Math.round( + (size * parseInt(this.settings.outer_width)) / 100, + ); + } else { + outer_width = parseInt(this.settings.outer_width); + } + } else { + outer_width = this.settings.outer_width; + } + outerBuffer = await sharp(innerBuffer) + .extend({ + top: outer_width, + bottom: outer_width, + left: outer_width, + right: outer_width, + background: this.settings.outer_color, + }) + .toBuffer(); + } + await sharp(outerBuffer).toFile(post.getFilePath(dst)); + await post.replaceFile(src, dst); + } + } +} diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 264d4a2..fe09c96 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -1,2 +1,3 @@ export { default as LimitFiles } from "./LimitFiles"; export { default as ImageSize } from "./ImageSize"; +export { default as ImageFrame } from "./ImageFrame";