Files
blogflare/packages/obsidian-plugin/src/api.ts
T
seahi 5df6fe45e8
continuous-integration/drone/push Build is passing
feat: Obsidian 插件支持文章封面解析与同步
2026-10-07 10:33:48 +08:00

202 lines
5.5 KiB
TypeScript

import { requestUrl } from 'obsidian';
import type { BlogFlareSettings } from './settings';
export interface MetaItem {
id: number;
name: string;
slug?: string;
}
export interface PostPayload {
title: string;
slug: string;
content: string;
cover?: string | null;
category_slug?: string;
series_slug?: string;
tags?: string[];
}
export interface PostMeta {
id: number;
slug: string;
title: string;
cover?: string | null;
published_at: string;
category_slug?: string;
category_name?: string;
series_slug?: string;
series_name?: string;
tags?: string[];
}
export interface PostDetail extends PostMeta {
content: string;
updated_at: string;
}
export function formatPostMarkdown(detail: PostDetail): string {
const frontmatterLines = [
'---',
`title: "${detail.title.replace(/"/g, '\\"')}"`,
`slug: ${detail.slug}`,
`blogflare_published_slug: ${detail.slug}`
];
if (detail.cover) {
frontmatterLines.push(`cover: "${detail.cover.replace(/"/g, '\\"')}"`);
}
if (detail.category_slug) {
frontmatterLines.push(`category_slug: ${detail.category_slug}`);
}
if (detail.series_slug) {
frontmatterLines.push(`series_slug: ${detail.series_slug}`);
}
if (detail.tags && detail.tags.length > 0) {
frontmatterLines.push('tags:');
for (const t of detail.tags) {
frontmatterLines.push(` - ${t}`);
}
}
frontmatterLines.push('---');
return frontmatterLines.join('\n') + '\n\n' + (detail.content || '');
}
export class BlogFlareAPI {
private settings: BlogFlareSettings;
constructor(settings: BlogFlareSettings) {
this.settings = settings;
}
private get headers() {
return {
'Authorization': `Bearer ${this.settings.apiToken}`,
'Content-Type': 'application/json'
};
}
private getBaseUrl(): string {
let url = this.settings.apiEndpoint.trim();
if (url.endsWith('/')) {
url = url.slice(0, -1);
}
return url;
}
async getCategories(): Promise<MetaItem[]> {
if (!this.getBaseUrl()) return [];
const res = await requestUrl({
url: `${this.getBaseUrl()}/api/v1/categories`,
headers: this.headers
});
return (res.json as { data: MetaItem[] }).data || [];
}
async getTags(): Promise<MetaItem[]> {
if (!this.getBaseUrl()) return [];
const res = await requestUrl({
url: `${this.getBaseUrl()}/api/v1/tags`,
headers: this.headers
});
return (res.json as { data: MetaItem[] }).data || [];
}
async getSeries(): Promise<MetaItem[]> {
if (!this.getBaseUrl()) return [];
const res = await requestUrl({
url: `${this.getBaseUrl()}/api/v1/series`,
headers: this.headers
});
return (res.json as { data: MetaItem[] }).data || [];
}
async createPost(data: PostPayload): Promise<{ success: boolean; slug?: string; error?: string }> {
if (!this.getBaseUrl()) throw new Error('API Endpoint is not configured');
const res = await requestUrl({
url: `${this.getBaseUrl()}/api/v1/posts`,
method: 'POST',
headers: this.headers,
body: JSON.stringify(data)
});
return res.json as { success: boolean; slug?: string; error?: string };
}
async updatePost(slug: string, data: Partial<PostPayload>): Promise<{ success: boolean; slug?: string; error?: string }> {
if (!this.getBaseUrl()) throw new Error('API Endpoint is not configured');
const res = await requestUrl({
url: `${this.getBaseUrl()}/api/v1/posts/${slug}`,
method: 'PUT',
headers: this.headers,
body: JSON.stringify(data)
});
return res.json as { success: boolean; slug?: string; error?: string };
}
async getPosts(): Promise<PostMeta[]> {
if (!this.getBaseUrl()) throw new Error('API Endpoint is not configured');
let allPosts: PostMeta[] = [];
let offset = 0;
const limit = 100;
let total = 0;
do {
const res = await requestUrl({
url: `${this.getBaseUrl()}/api/v1/posts?limit=${limit}&offset=${offset}`,
headers: this.headers
});
const json = res.json as { data: PostMeta[], meta: { total: number } };
const data = json.data || [];
if (data.length === 0) break; // 防止死循环
allPosts = allPosts.concat(data);
total = json.meta?.total || 0;
offset += limit;
} while (offset < total);
return allPosts;
}
async getPost(slug: string): Promise<PostDetail> {
if (!this.getBaseUrl()) throw new Error('API Endpoint is not configured');
const res = await requestUrl({
url: `${this.getBaseUrl()}/api/v1/posts/${slug}`,
headers: this.headers
});
return (res.json as { data: PostDetail }).data;
}
async createCategory(name: string, slug?: string): Promise<{ success: boolean; error?: string }> {
if (!this.getBaseUrl()) throw new Error('API Endpoint is not configured');
const res = await requestUrl({
url: `${this.getBaseUrl()}/api/v1/categories`,
method: 'POST',
headers: this.headers,
body: JSON.stringify({ name, slug: slug || name })
});
return res.json as { success: boolean; error?: string };
}
async createTag(name: string): Promise<{ success: boolean; error?: string }> {
if (!this.getBaseUrl()) throw new Error('API Endpoint is not configured');
const res = await requestUrl({
url: `${this.getBaseUrl()}/api/v1/tags`,
method: 'POST',
headers: this.headers,
body: JSON.stringify({ name })
});
return res.json as { success: boolean; error?: string };
}
async deletePost(slug: string): Promise<{ success: boolean; error?: string }> {
if (!this.getBaseUrl()) throw new Error('API Endpoint is not configured');
const res = await requestUrl({
url: `${this.getBaseUrl()}/api/v1/posts/${slug}`,
method: 'DELETE',
headers: this.headers
});
return res.json as { success: boolean; error?: string };
}
}