rspace-online/modules/rsocials/lib/postiz-client.ts

99 lines
2.8 KiB
TypeScript

/**
* Postiz API client — reads per-space credentials from module settings
* and forwards authenticated requests to the Postiz public API.
*
* Follows the same pattern as listmonk-proxy.ts.
*/
import { loadCommunity, getDocumentData } from "../../../server/community-store";
export interface PostizConfig {
url: string;
apiKey: string;
}
/** Read Postiz credentials from the space's module settings. */
export async function getPostizConfig(spaceSlug: string): Promise<PostizConfig | null> {
await loadCommunity(spaceSlug);
const data = getDocumentData(spaceSlug);
if (!data) return null;
const settings = data.meta.moduleSettings?.rsocials;
if (!settings) return null;
const url = settings.postizUrl as string | undefined;
const apiKey = settings.postizApiKey as string | undefined;
if (!url || !apiKey) return null;
return { url: url.replace(/\/+$/, ''), apiKey };
}
/** Make an authenticated request to the Postiz API. */
export async function postizFetch(
config: PostizConfig,
path: string,
opts: RequestInit = {},
): Promise<Response> {
const headers = new Headers(opts.headers);
headers.set("Authorization", `Bearer ${config.apiKey}`);
if (!headers.has("Content-Type") && opts.body) {
headers.set("Content-Type", "application/json");
}
return fetch(`${config.url}${path}`, { ...opts, headers });
}
/** GET /public/v1/integrations — list connected social channels. */
export async function getIntegrations(config: PostizConfig) {
const res = await postizFetch(config, "/public/v1/integrations");
if (!res.ok) throw new Error(`Postiz integrations error: ${res.status}`);
return res.json();
}
/** POST /public/v1/posts — create a single post (draft, schedule, or now). */
export async function createPost(
config: PostizConfig,
payload: {
content: string;
integrationIds: string[];
type: 'draft' | 'schedule' | 'now';
scheduledAt?: string;
group?: string;
},
) {
const res = await postizFetch(config, "/public/v1/posts", {
method: "POST",
body: JSON.stringify(payload),
});
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(`Postiz createPost error: ${res.status} ${text}`);
}
return res.json();
}
/** Create a thread — sends multiple grouped posts sharing a group ID. */
export async function createThread(
config: PostizConfig,
tweets: string[],
opts: {
integrationIds: string[];
type: 'draft' | 'schedule' | 'now';
scheduledAt?: string;
},
) {
const group = `thread-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
const results = [];
for (const content of tweets) {
const result = await createPost(config, {
content,
integrationIds: opts.integrationIds,
type: opts.type,
scheduledAt: opts.scheduledAt,
group,
});
results.push(result);
}
return { group, posts: results };
}