Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 68 additions & 61 deletions src/models/Folder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as fs from "fs";
import * as path from "path";
import * as sharp from "sharp";

import Logger from "../services/Logger";

Expand All @@ -13,12 +14,7 @@ import Logger from "../services/Logger";
export default class Folder {
id: string;
path: string;
files?: {
text: string[];
image: string[];
video: string[];
other: string[];
};
files?: FileInfo[];

constructor(path: string) {
this.id = path;
Expand All @@ -32,86 +28,97 @@ export default class Folder {

report(): string {
Logger.trace("Folder", "report");
const files = this.getFiles();
let report = "";
report += "\nFolder: " + this.id;
report += "\n - path: " + this.path;
report +=
"\n - files: " +
Object.keys(files)
.map((k) => files[k].join())
.join();
report += "\n - files: " + this.getFileNames();
return report;
}

/**
* Get the filenames in this folder
* @returns array of filenames relative to folder
*/

getFileNames(): string[] {
Logger.trace("Folder", "getFileNames");
if (this.files !== undefined) {
return this.files.map((file) => file.name);
}
const files = fs.readdirSync(this.path).filter((file) => {
const regex = /^[^._]/;
return fs.statSync(this.path + "/" + file).isFile() && file.match(regex);
});
return files;
}

/**
* Get the files in this folder
*
* reads info from disk once, then caches that
* @returns grouped filenames relative to folder
* @returns array of fileinfo for all files in this folder
*/

getFiles(): {
text: string[];
image: string[];
video: string[];
other: string[];
} {
async getFiles(): Promise<FileInfo[]> {
Logger.trace("Folder", "getFiles");
if (this.files != undefined) {
return {
text: [...this.files.text],
image: [...this.files.image],
video: [...this.files.video],
other: [...this.files.other],
};
if (this.files !== undefined) {
return structuredClone(this.files);
}
const files = fs.readdirSync(this.path).filter((file) => {
return (
fs.statSync(this.path + "/" + file).isFile() &&
!file.startsWith("_") &&
!file.startsWith(".")
);
});
this.files = {
text: [],
image: [],
video: [],
other: [],
};
this.files.text = files.filter((file) =>
["txt"].includes(file.split(".")?.pop() ?? ""),
);
this.files.image = files.filter((file) =>
["jpg", "jpeg", "png"].includes(file.split(".")?.pop() ?? ""),
);
this.files.video = files.filter((file) =>
["mp4"].includes(file.split(".")?.pop() ?? ""),
);
this.files.other = files.filter(
(file) =>
!this.files.text?.includes(file) &&
!this.files.image?.includes(file) &&
!this.files.video?.includes(file),
);
return {
text: [...this.files.text],
image: [...this.files.image],
video: [...this.files.video],
other: [...this.files.other],
};
const fileNames = this.getFileNames();
this.files = [];
for (let index = 0; index < fileNames.length; index++) {
this.files.push(await this.getFile(fileNames[index], index));
}
return structuredClone(this.files);
}

public async getFile(name: string, order: number): Promise<FileInfo> {
Logger.trace("Folder", "getFile", name);
const filepath = this.path + "/" + name;
const mime = Folder.guessMimeType(name);
const group = mime !== "application/unknown" ? mime.split("/")[0] : "other";
const stats = fs.statSync(filepath);
const extension = path.extname(name);
const file = {
name: name,
basename: path.basename(name, extension || ""),
extension: extension.substring(1),
group: group,
mimetype: mime,
size: stats.size,
order: order,
} as FileInfo;
if (group === "image") {
const metadata = await sharp(filepath).metadata();
file.width = metadata.width;
file.height = metadata.height;
}
return file;
}

public static guessMimeType(filename: string) {
const extension = path.extname(filename);
const mimeTypes = {
".txt": "text/plain",
".png": "image/png",
".mov": "video/quicktime",
".mp4": "video/mp4",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
};
return mimeTypes[extension] ?? "application/unknown";
return mimeTypes[extension.toLowerCase()] ?? "application/unknown";
}
}

export interface FileInfo {
name: string;
basename: string;
extension: string;
group: string;
size: number;
mimetype: string;
order: number;
width?: number;
height?: number;
}
13 changes: 7 additions & 6 deletions src/models/Platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,24 +139,25 @@ export default class Platform {
// some default logic. override this
// in your own platform if you need.

post.files = folder.getFiles();
post.files = await folder.getFiles();
const textFiles = post.getFiles("text");

if (post.files.text?.includes("body.txt")) {
if (post.hasFile("body.txt")) {
post.body = fs.readFileSync(post.folder.path + "/body.txt", "utf8");
} else if (post.files.text.length === 1) {
const bodyFile = post.files.text[0];
} else if (textFiles.length === 1) {
const bodyFile = textFiles[0].name;
post.body = fs.readFileSync(post.folder.path + "/" + bodyFile, "utf8");
} else {
post.body = this.defaultBody;
}

if (post.files.text?.includes("title.txt")) {
if (post.hasFile("title.txt")) {
post.title = fs.readFileSync(post.folder.path + "/title.txt", "utf8");
} else {
post.title = post.body.split("\n", 1)[0];
}

if (post.files.text?.includes("tags.txt")) {
if (post.hasFile("tags.txt")) {
post.tags = fs.readFileSync(post.folder.path + "/tags.txt", "utf8");
}

Expand Down
113 changes: 92 additions & 21 deletions src/models/Post.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as fs from "fs";

import Folder from "./Folder";
import Folder, { FileInfo } from "./Folder";

import Logger from "../services/Logger";
import Platform from "./Platform";

Expand All @@ -24,12 +25,7 @@ export default class Post {
title: string = "";
body?: string;
tags?: string;
files?: {
text: string[];
image: string[];
video: string[];
other: string[];
};
files?: FileInfo[];
link?: string;
remoteId?: string;

Expand All @@ -42,7 +38,7 @@ 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());
const assetsPath = this.getFilePath(platform.assetsFolder());
if (!fs.existsSync(assetsPath)) {
fs.mkdirSync(assetsPath, { recursive: true });
}
Expand Down Expand Up @@ -95,26 +91,101 @@ export default class Post {
}

/**
* @param filename relative path in this post.folder
* @returns the full path to that file
* @returns the files grouped by their group property
*/
getGroupedFiles(): { [group: string]: FileInfo[] } {
return this.files.reduce(function (collector, file) {
(collector[file["group"]] = collector[file["group"]] || []).push(file);
return collector;
}, {});
}

/**
* @param groups - names of groups to return files from
* @returns the files within those groups, sorted by order
*/
getFullPath(filename: string): string {
return this.folder.path + "/" + filename;
getFiles(...groups: string[]): FileInfo[] {
if (!groups.length) {
return this.files.sort((a, b) => a.order - b.order);
}
return this.files
.filter((file) => groups.includes(file.group))
.sort((a, b) => a.order - b.order);
}

/**
* 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
* @param groups - names of groups to require files from
* @returns boolean if files in post
*/
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);
hasFiles(...groups: string[]): boolean {
if (!groups.length) {
return !!this.files.length;
}
return !!this.files.filter((file) => groups.includes(file.group)).length;
}

/**
* @param group - the name of the group for which to remove the files
*/
removeFiles(group: string) {
this.files = this.files.filter((file) => file.group !== group);
}

/**
* @param group - the name of the group for which to remove some files
* @param size - the number of files to leave in the group
*/
limitFiles(group: string, size: number) {
this.getFiles(group).forEach((file, index) => {
if (index > size) {
this.removeFile(file.name);
}
});
}

/**
* @param name the name of the file
* @returns the files info
*/
hasFile(name: string): boolean {
return this.getFile(name) !== undefined;
}

/**
* @param name the name of the file
* @returns the files info
*/
getFile(name: string): FileInfo | undefined {
return this.files.find((file) => file.name === name);
}

/**
* @param name the name of the file to remove
*/
removeFile(name: string) {
this.files = this.files.filter((file) => file.name !== name);
}

/**
* @param search - the name of the file to replace
* @param replace - the name of the file to replace it with
* @returns the info of the replaced file
*/
async replaceFile(search: string, replace: string): Promise<FileInfo> {
const index = this.files.findIndex((file) => file.name === search);
if (index > -1) {
const oldFile = this.getFile(search);
this.files[index] = await this.folder.getFile(replace, oldFile.order);
}
return this.files[index];
}

/**
* @param name relative path in this post.folder
* @returns the full path to that file
*/
getFilePath(name: string): string {
return this.folder.path + "/" + name;
}

/**
Expand Down
13 changes: 6 additions & 7 deletions src/platforms/Ayrshare/AsFacebook.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as fs from "fs";
import * as sharp from "sharp";

import Ayrshare from "./Ayrshare";
Expand All @@ -20,17 +19,17 @@ export default class AsFacebook extends Ayrshare {
const post = await super.preparePost(folder);
if (post) {
// facebook : max 10mb images
for (const src of post.files.image) {
for (const file of post.getFiles("image")) {
const src = file.name;
const dst = this.assetsFolder() + "/facebook-" + src;
const size = fs.statSync(post.getFullPath(src)).size / (1024 * 1024);
if (size >= 10) {
if (file.size / (1024 * 1024) >= 10) {
console.log("Resizing " + src + " for facebook ..");
await sharp(post.getFullPath(src))
await sharp(post.getFilePath(src))
.resize({
width: 1200,
})
.toFile(post.getFullPath(dst));
post.useAlternativeFile(src, dst);
.toFile(post.getFilePath(dst));
await post.replaceFile(src, dst);
}
}
post.save();
Expand Down
Loading