obsidian-book-tracker/src/main.ts

151 lines
3.9 KiB
TypeScript

import { Notice, Plugin, requestUrl } from "obsidian";
import {
type BookTrackerPluginSettings,
DEFAULT_SETTINGS,
BookTrackerSettingTab,
} from "@ui/settings";
import { Templater } from "@utils/Templater";
import { CONTENT_TYPE_EXTENSIONS } from "./const";
import { Storage } from "@utils/Storage";
import { ReadingLog } from "@utils/ReadingLog";
import { registerReadingLogCodeBlockProcessor } from "@ui/code-blocks";
import type { Book } from "./types";
import { SearchGoodreadsCommand } from "@commands/SearchGoodreadsCommand";
import { LogReadingStartedCommand } from "@commands/LogReadingStartedCommand";
import { LogReadingProgressCommand } from "@commands/LogReadingProgressCommand";
import { LogReadingFinishedCommand } from "@commands/LogReadingFinishedCommand";
import { ResetReadingStatusCommand } from "@commands/ResetReadingStatusCommand";
export default class BookTrackerPlugin extends Plugin {
settings: BookTrackerPluginSettings;
templater: Templater;
storage: Storage;
readingLog: ReadingLog;
async onload() {
await this.loadSettings();
this.templater = new Templater(this.app);
this.storage = new Storage(this.app, this);
this.readingLog = new ReadingLog(this.storage);
this.addCommand(
new SearchGoodreadsCommand(this.app, this.createEntry.bind(this))
);
this.addCommand(new LogReadingStartedCommand(this.app, this.settings));
this.addCommand(
new LogReadingProgressCommand(
this.app,
this.readingLog,
this.settings
)
);
this.addCommand(
new LogReadingFinishedCommand(
this.app,
this.readingLog,
this.settings
)
);
this.addCommand(
new ResetReadingStatusCommand(
this.app,
this.readingLog,
this.settings
)
);
this.addSettingTab(new BookTrackerSettingTab(this));
registerReadingLogCodeBlockProcessor(this);
}
onunload() {}
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
}
async saveSettings() {
await this.saveData(this.settings);
}
async downloadCoverImage(
coverImageUrl: string,
fileName: string
): Promise<string> {
const response = await requestUrl(coverImageUrl);
const contentType = response.headers["content-type"];
const extension = CONTENT_TYPE_EXTENSIONS[contentType || ""] || "";
if (extension === "") {
throw new Error("Unsupported content type: " + contentType);
}
let filePath = this.settings.coverFolder + "/";
if (this.settings.groupCoversByFirstLetter) {
let groupName = fileName.charAt(0).toUpperCase();
if (!/^[A-Z]$/.test(groupName)) {
groupName = "#";
}
filePath += groupName + "/";
}
filePath += fileName + "." + extension;
const existingFile = this.app.vault.getFileByPath(filePath);
if (existingFile) {
if (this.settings.overwriteExistingCovers) {
await this.app.vault.modifyBinary(
existingFile,
response.arrayBuffer
);
} else {
new Notice("Cover image already exists: " + filePath);
return filePath;
}
}
await this.app.vault.createBinary(filePath, response.arrayBuffer);
return filePath;
}
async createEntry(book: Book): Promise<void> {
try {
const fileName = this.templater
.renderTemplate(this.settings.fileNameFormat, {
title: book.title,
authors: book.authors.map((a) => a.name).join(", "),
})
.replace(/[/\:*?<>|""]/g, "");
const data: Record<string, unknown> = { book };
if (this.settings.downloadCovers && book.coverImageUrl) {
data.coverImagePath = await this.downloadCoverImage(
book.coverImageUrl,
fileName
);
}
const renderedContent = await this.templater.renderTemplateFile(
this.settings.templateFile,
data
);
if (renderedContent) {
await this.app.vault.create(
this.settings.tbrFolder + "/" + fileName + ".md",
renderedContent
);
}
} catch (error) {
console.error("Failed to create book entry:", error);
}
}
}