202 lines
5.5 KiB
TypeScript
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 };
|
|
}
|
|
}
|