xenforo.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import request from "request-promise-native";
  2. import { Response } from "request";
  3. import { Dict } from "./util";
  4. enum ReqMethod {
  5. GET = "get",
  6. POST = "post",
  7. DELETE = "delete"
  8. }
  9. export interface RequestError {
  10. code: string;
  11. message: string;
  12. params: { key: string; value: any; }[];
  13. }
  14. export type RequestErrorSet = { errors: RequestError[] };
  15. export class XenforoClient {
  16. constructor(private endpoint: string, private userKey: string) {
  17. }
  18. private async makeRequest<T>(uri: string, method: ReqMethod, data?: any) {
  19. let result = await request(`${this.endpoint}/${uri}`, {
  20. method: method,
  21. headers: {
  22. "XF-Api-Key": this.userKey
  23. },
  24. form: data || undefined,
  25. resolveWithFullResponse: true
  26. }) as Response;
  27. if (result.statusCode != 200) {
  28. throw await JSON.parse(result.body) as RequestErrorSet;
  29. } else {
  30. return await JSON.parse(result.body) as T;
  31. }
  32. }
  33. async getThread(id: number, opts?: GetThreadOptions) {
  34. return await this.makeRequest<GetThreadResponse>(`threads/${id}`, ReqMethod.GET, opts);
  35. }
  36. async deleteThread(id: number, opts?: DeleteThreadOptions) {
  37. return await this.makeRequest<SuccessResponse>(`threads/${id}`, ReqMethod.DELETE, opts);
  38. }
  39. async createThread(forumId: number, title: string, message: string, opts?: CreateThreadOptions) {
  40. return await this.makeRequest<CreateThreadResponse>(`threads/`, ReqMethod.POST, {
  41. node_id: forumId,
  42. title: title,
  43. message: message,
  44. ...opts
  45. });
  46. }
  47. async getPost(id: number) {
  48. let post = await this.makeRequest<{post: Post}>(`posts/${id}`, ReqMethod.GET);
  49. return post.post;
  50. }
  51. async getForumThreads(id: number) {
  52. return await this.makeRequest<GetForumThreadsResponse>(`forums/${id}/threads`, ReqMethod.GET);
  53. }
  54. }
  55. //#region Request types
  56. interface DeleteThreadOptions {
  57. hard_delete?: boolean;
  58. reason?: boolean;
  59. starter_alert?: boolean;
  60. starter_alert_reason?: boolean;
  61. }
  62. interface GetThreadOptions {
  63. with_posts?: boolean;
  64. page?: number;
  65. }
  66. interface CreateThreadOptions {
  67. prefix_id?: number;
  68. tags?: string[];
  69. custom_fields?: Dict<string>;
  70. discussion_open?: boolean;
  71. sticky?: boolean;
  72. attachment_key?: boolean;
  73. }
  74. //#endregion
  75. //#region Response types
  76. type GetThreadResponse = {
  77. thread: Thread;
  78. messages: Post[];
  79. pagination: any;
  80. };
  81. type SuccessResponse = {
  82. success: boolean;
  83. }
  84. type CreateThreadResponse = SuccessResponse & { thread: Thread; };
  85. type GetForumThreadsResponse = {
  86. threads: Thread[];
  87. pagination: object;
  88. sticky: Thread[];
  89. };
  90. //#endregion
  91. //#region Data types
  92. export interface Forum {
  93. allow_posting: boolean;
  94. allow_poll: boolean;
  95. require_prefix: boolean;
  96. min_tags: number;
  97. }
  98. export interface User {
  99. about?: string;
  100. activity_visible?: boolean;
  101. age?: number;
  102. alert_optout?: any[];
  103. allow_post_profile?: string;
  104. allow_receive_news_feed?: string;
  105. allow_send_personal_conversation?: string;
  106. allow_view_identities: string;
  107. allow_view_profile?: string;
  108. avatar_urls: object;
  109. can_ban: boolean;
  110. can_converse: boolean;
  111. can_edit: boolean;
  112. can_follow: boolean;
  113. can_ignore: boolean;
  114. can_post_profile: boolean;
  115. can_view_profile: boolean;
  116. can_view_profile_posts: boolean;
  117. can_warn: boolean;
  118. content_show_signature?: boolean;
  119. creation_watch_state?: string;
  120. custom_fields?: object;
  121. custom_title?: string;
  122. dob?: object;
  123. email?: string;
  124. email_on_conversation?: boolean;
  125. gravatar?: string;
  126. interaction_watch_state?: boolean;
  127. is_admin?: boolean;
  128. is_banned?: boolean;
  129. is_discouraged?: boolean;
  130. is_followed?: boolean;
  131. is_ignored?: boolean;
  132. is_moderator?: boolean;
  133. is_super_admin?: boolean;
  134. last_activity?: number;
  135. location: string;
  136. push_on_conversation?: boolean;
  137. push_optout?: any[];
  138. receive_admin_email?: boolean;
  139. secondary_group_ids?: any[];
  140. show_dob_date?: boolean;
  141. show_dob_year?: boolean;
  142. signature: string;
  143. timezone?: string;
  144. use_tfa?: any[];
  145. user_group_id?: number;
  146. user_state?: string;
  147. user_title: string;
  148. visible?: boolean;
  149. warning_points?: number;
  150. website?: string;
  151. user_id: number;
  152. username: string;
  153. message_count: number;
  154. register_date: number;
  155. trophy_points: number;
  156. is_staff: boolean;
  157. reaction_score: number;
  158. }
  159. export interface Node {
  160. breadcrumbs: any[];
  161. type_data: object;
  162. node_id: number;
  163. title: string;
  164. node_name: string;
  165. description: string;
  166. node_type_id: string;
  167. parent_node_id: number;
  168. display_order: number;
  169. display_in_list: boolean;
  170. }
  171. export interface Thread {
  172. username: string;
  173. is_watching?: boolean;
  174. visitor_post_count?: number;
  175. custom_fields: object;
  176. tags: any[];
  177. prefix?: string;
  178. can_edit: boolean;
  179. can_edit_tags: boolean;
  180. can_reply: boolean;
  181. can_soft_delete: boolean;
  182. can_hard_delete: boolean;
  183. can_view_attachments: boolean;
  184. Forum?: Node;
  185. thread_id: number;
  186. node_id: number;
  187. title: string;
  188. reply_count: number;
  189. view_count: number;
  190. user_id: number;
  191. post_date: number;
  192. sticky: boolean;
  193. discussion_state: string;
  194. discussion_open: boolean;
  195. discussion_type: string;
  196. first_post_id: number;
  197. last_post_date: number;
  198. last_post_id: number;
  199. last_post_user_id: number;
  200. last_post_username: string;
  201. first_post_reaction_score: number;
  202. prefix_id: number;
  203. }
  204. export interface Attachment {
  205. filename: string;
  206. file_size: number;
  207. height: number;
  208. width: number;
  209. thumbnail_url: string;
  210. video_url: string;
  211. attachment_id: number;
  212. content_type: string;
  213. content_id: number;
  214. attach_date: number;
  215. view_count: number;
  216. }
  217. export interface Post {
  218. username: string;
  219. is_first_post: boolean;
  220. is_last_post: boolean;
  221. can_edit: boolean;
  222. can_soft_delete: boolean;
  223. can_hard_delete: boolean;
  224. can_react: boolean;
  225. can_view_attachments: boolean;
  226. Thread?: Thread;
  227. Attachments?: Attachment[];
  228. is_reacted_to: boolean;
  229. visitor_reaction_id: number;
  230. post_id: number;
  231. thread_id: number;
  232. user_id: number;
  233. post_date: number;
  234. message: string;
  235. message_state: string;
  236. attach_count: number;
  237. warning_message: string;
  238. position: number;
  239. last_edit_date: number;
  240. reaction_score: number;
  241. User: User;
  242. }
  243. //#endregion