import { Dict } from "./util"; import got, { Method, HTTPError } from "got"; import { tryDo, assertOk, isHttpError } from "@shared/common/async_utils"; export interface RequestError { code: string; message: string; params: { key: string; value: unknown; }[]; } export type RequestErrorSet = { errors: RequestError[] }; export class XenforoClient { constructor(private endpoint: string, private userKey: string) { } private async makeRequest(uri: string, method: Method, data?: TData): Promise { const result = await tryDo(got(`${this.endpoint}/${uri}`, { responseType: "json", method: method, headers: { "XF-Api-Key": this.userKey }, form: data })); if (!result.ok) { if (isHttpError(result.error)) throw result.error.response.body as RequestErrorSet; else throw { errors: [{ code: "UNK", message: "Unkown error" }] } as RequestErrorSet; } else { return result.result?.body as TResult; } } async getMe(): Promise { const { me }: {me: User} = await assertOk(this.makeRequest("me/", "get")); return me; } async postReply(thread_id: number, message: string, attachment_key?: string): Promise { return await assertOk(this.makeRequest("posts/", "post", { thread_id, message, attachment_key })); } async editThread(id: number, opts?: EditThreadOptions): Promise { return await assertOk(this.makeRequest(`threads/${id}`, "post", opts)); } async editPost(id: number, opts?: EditPostOptions): Promise { return await assertOk(this.makeRequest(`posts/${id}`, "post", opts)); } async getThread(id: number, opts?: GetThreadOptions): Promise { return await assertOk(this.makeRequest(`threads/${id}`, "post", opts)); } async deleteThread(id: number, opts?: DeleteThreadOptions): Promise { return await assertOk(this.makeRequest(`threads/${id}`, "delete", opts)); } async createThread(forumId: number, title: string, message: string, opts?: CreateThreadOptions): Promise { return await assertOk(this.makeRequest("threads/", "post", { node_id: forumId, title: title, message: message, ...opts })); } async getPost(id: number): Promise { const { post }: {post: Post} = await this.makeRequest(`posts/${id}`, "get"); return post; } async getForumThreads(id: number): Promise { return await this.makeRequest(`forums/${id}/threads`, "get"); } } //#region Request types interface DeleteThreadOptions { hard_delete?: boolean; reason?: boolean; starter_alert?: boolean; starter_alert_reason?: boolean; } interface GetThreadOptions { with_posts?: boolean; page?: number; } interface CreateThreadOptions { prefix_id?: number; tags?: string[]; custom_fields?: Dict; discussion_open?: boolean; sticky?: boolean; attachment_key?: boolean; } interface EditThreadOptions { prefix_id?: number; title?: string; discussion_open?: boolean; sticky?: boolean; custom_fields?: Dict; add_tags?: unknown[]; remove_tags?: unknown[]; } interface EditPostOptions { message?: string; silent?: boolean; clear_edit?: boolean; author_alert?: boolean; author_alert_reason?: string; attachment_key?: string; } //#endregion //#region Response types type GetThreadResponse = { thread: Thread; messages: Post[]; pagination: unknown; }; type SuccessResponse = { success: boolean; } type EditPostResponse = SuccessResponse & { post: Post }; type CreateThreadResponse = SuccessResponse & { thread: Thread; }; type GetForumThreadsResponse = { threads: Thread[]; pagination: unknown; sticky: Thread[]; }; //#endregion //#region Data types export interface Forum { allow_posting: boolean; allow_poll: boolean; require_prefix: boolean; min_tags: number; } export interface User { about?: string; activity_visible?: boolean; age?: number; alert_optout?: unknown[]; allow_post_profile?: string; allow_receive_news_feed?: string; allow_send_personal_conversation?: string; allow_view_identities: string; allow_view_profile?: string; avatar_urls: unknown; can_ban: boolean; can_converse: boolean; can_edit: boolean; can_follow: boolean; can_ignore: boolean; can_post_profile: boolean; can_view_profile: boolean; can_view_profile_posts: boolean; can_warn: boolean; content_show_signature?: boolean; creation_watch_state?: string; custom_fields?: unknown; custom_title?: string; dob?: unknown; email?: string; email_on_conversation?: boolean; gravatar?: string; interaction_watch_state?: boolean; is_admin?: boolean; is_banned?: boolean; is_discouraged?: boolean; is_followed?: boolean; is_ignored?: boolean; is_moderator?: boolean; is_super_admin?: boolean; last_activity?: number; location: string; push_on_conversation?: boolean; push_optout?: unknown[]; receive_admin_email?: boolean; secondary_group_ids?: unknown[]; show_dob_date?: boolean; show_dob_year?: boolean; signature: string; timezone?: string; use_tfa?: unknown[]; user_group_id?: number; user_state?: string; user_title: string; visible?: boolean; warning_points?: number; website?: string; user_id: number; username: string; message_count: number; register_date: number; trophy_points: number; is_staff: boolean; reaction_score: number; } export interface Node { breadcrumbs: unknown[]; type_data: unknown; node_id: number; title: string; node_name: string; description: string; node_type_id: string; parent_node_id: number; display_order: number; display_in_list: boolean; } export interface Thread { username: string; is_watching?: boolean; visitor_post_count?: number; custom_fields: unknown; tags: unknown[]; prefix?: string; can_edit: boolean; can_edit_tags: boolean; can_reply: boolean; can_soft_delete: boolean; can_hard_delete: boolean; can_view_attachments: boolean; Forum?: Node; thread_id: number; node_id: number; title: string; reply_count: number; view_count: number; user_id: number; post_date: number; sticky: boolean; discussion_state: string; discussion_open: boolean; discussion_type: string; first_post_id: number; last_post_date: number; last_post_id: number; last_post_user_id: number; last_post_username: string; first_post_reaction_score: number; prefix_id: number; } export interface Attachment { filename: string; file_size: number; height: number; width: number; thumbnail_url: string; video_url: string; attachment_id: number; content_type: string; content_id: number; attach_date: number; view_count: number; } export interface Post { username: string; is_first_post: boolean; is_last_post: boolean; can_edit: boolean; can_soft_delete: boolean; can_hard_delete: boolean; can_react: boolean; can_view_attachments: boolean; Thread?: Thread; Attachments?: Attachment[]; is_reacted_to: boolean; visitor_reaction_id: number; post_id: number; thread_id: number; user_id: number; post_date: number; message: string; message_state: string; attach_count: number; warning_message: string; position: number; last_edit_date: number; reaction_score: number; User: User; } //#endregion