From 605d6a4c7d38df7eca97874d2feb310f4e73183e Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Thu, 6 Jun 2024 15:51:33 -1000 Subject: [PATCH] feat(client): :sparkles: Add client package --- .vscode/settings.json | 2 +- client/bun.lockb | Bin 0 -> 13645 bytes client/index.ts | 0 client/lysand/base.ts | 191 +++++ client/lysand/constants.ts | 5 + client/lysand/lysand.ts | 1133 +++++++++++++++++++++++++++++ client/package.json | 60 ++ client/types/account.ts | 34 + client/types/activity.ts | 6 + client/types/announcement.ts | 39 + client/types/application.ts | 14 + client/types/async_attachment.ts | 13 + client/types/attachment.ts | 47 ++ client/types/card.ts | 16 + client/types/context.ts | 6 + client/types/conversation.ts | 9 + client/types/emoji.ts | 7 + client/types/featured_tag.ts | 6 + client/types/field.ts | 6 + client/types/filter.ts | 10 + client/types/follow_request.ts | 25 + client/types/history.ts | 5 + client/types/identity_proof.ts | 7 + client/types/instance.ts | 38 + client/types/list.ts | 7 + client/types/marker.ts | 13 + client/types/mention.ts | 6 + client/types/notification.ts | 15 + client/types/poll.ts | 14 + client/types/preferences.ts | 9 + client/types/push_subscription.ts | 14 + client/types/reaction.ts | 11 + client/types/relationship.ts | 15 + client/types/report.ts | 16 + client/types/results.ts | 9 + client/types/role.ts | 3 + client/types/scheduled_status.ts | 9 + client/types/source.ts | 9 + client/types/stats.ts | 5 + client/types/status.ts | 50 ++ client/types/status_params.ts | 12 + client/types/status_source.ts | 5 + client/types/tag.ts | 8 + client/types/token.ts | 6 + client/types/urls.ts | 3 + tsconfig.json | 4 +- 46 files changed, 1920 insertions(+), 2 deletions(-) create mode 100755 client/bun.lockb create mode 100644 client/index.ts create mode 100644 client/lysand/base.ts create mode 100644 client/lysand/constants.ts create mode 100644 client/lysand/lysand.ts create mode 100644 client/package.json create mode 100644 client/types/account.ts create mode 100644 client/types/activity.ts create mode 100644 client/types/announcement.ts create mode 100644 client/types/application.ts create mode 100644 client/types/async_attachment.ts create mode 100644 client/types/attachment.ts create mode 100644 client/types/card.ts create mode 100644 client/types/context.ts create mode 100644 client/types/conversation.ts create mode 100644 client/types/emoji.ts create mode 100644 client/types/featured_tag.ts create mode 100644 client/types/field.ts create mode 100644 client/types/filter.ts create mode 100644 client/types/follow_request.ts create mode 100644 client/types/history.ts create mode 100644 client/types/identity_proof.ts create mode 100644 client/types/instance.ts create mode 100644 client/types/list.ts create mode 100644 client/types/marker.ts create mode 100644 client/types/mention.ts create mode 100644 client/types/notification.ts create mode 100644 client/types/poll.ts create mode 100644 client/types/preferences.ts create mode 100644 client/types/push_subscription.ts create mode 100644 client/types/reaction.ts create mode 100644 client/types/relationship.ts create mode 100644 client/types/report.ts create mode 100644 client/types/results.ts create mode 100644 client/types/role.ts create mode 100644 client/types/scheduled_status.ts create mode 100644 client/types/source.ts create mode 100644 client/types/stats.ts create mode 100644 client/types/status.ts create mode 100644 client/types/status_params.ts create mode 100644 client/types/status_source.ts create mode 100644 client/types/tag.ts create mode 100644 client/types/token.ts create mode 100644 client/types/urls.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 212a69e..b43fbb5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "conventionalCommits.scopes": ["docs", "build", "federation"] + "conventionalCommits.scopes": ["docs", "build", "federation", "client"] } diff --git a/client/bun.lockb b/client/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..cb3a04f63e806ea45b9623898a7adef9efbe0ec7 GIT binary patch literal 13645 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 + +export interface Output { + data: ReturnType; + headers: Headers; +} + +const objectToFormData = (obj: ConvertibleObject): FormData => { + return Object.keys(obj).reduce((formData, key) => { + if (obj[key] === undefined || obj[key] === null) return formData; + formData.append(key, String(obj[key])); + return formData; + }, new FormData()); +}; + +export class ResponseError extends Error {} + +export class BaseClient { + constructor( + protected baseUrl: URL, + private accessToken?: string, + ) {} + + get url(): URL { + return this.baseUrl; + } + + get token(): string | undefined { + return this.accessToken; + } + + private async request( + request: Request, + ): Promise> { + const result = await fetch(request); + + if (!result.ok) { + const error = await result.json(); + throw new ResponseError( + `Request failed (${result.status}): ${ + error.error || error.message || result.statusText + }`, + ); + } + + return { + data: await result.json(), + headers: result.headers, + }; + } + + private async constructRequest( + path: string, + method: HttpVerb, + body?: object | FormData, + extra?: RequestInit, + ): Promise { + return new Request(new URL(path, this.baseUrl).toString(), { + method, + headers: { + Authorization: this.accessToken + ? `Bearer ${this.accessToken}` + : "", + "Content-Type": "application/json", + "User-Agent": DEFAULT_UA, + ...extra?.headers, + }, + body: body + ? body instanceof FormData + ? body + : JSON.stringify(body) + : undefined, + ...extra, + }); + } + + protected async get( + path: string, + extra?: RequestInit, + ): Promise> { + return await this.request( + await this.constructRequest(path, "GET", undefined, extra), + ); + } + + protected async post( + path: string, + body?: object, + extra?: RequestInit, + ): Promise> { + return await this.request( + await this.constructRequest(path, "POST", body, extra), + ); + } + + protected async postForm( + path: string, + body: FormData | ConvertibleObject, + extra?: RequestInit, + ): Promise> { + return await this.request( + await this.constructRequest( + path, + "POST", + body instanceof FormData ? body : objectToFormData(body), + extra, + ), + ); + } + + protected async put( + path: string, + body?: object, + extra?: RequestInit, + ): Promise> { + return await this.request( + await this.constructRequest(path, "PUT", body, extra), + ); + } + + protected async putForm( + path: string, + body: FormData | ConvertibleObject, + extra?: RequestInit, + ): Promise> { + return await this.request( + await this.constructRequest( + path, + "PUT", + body instanceof FormData ? body : objectToFormData(body), + extra, + ), + ); + } + + protected async patch( + path: string, + body?: object, + extra?: RequestInit, + ): Promise> { + return await this.request( + await this.constructRequest(path, "PATCH", body, extra), + ); + } + + protected async patchForm( + path: string, + body: FormData | ConvertibleObject, + extra?: RequestInit, + ): Promise> { + return await this.request( + await this.constructRequest( + path, + "PATCH", + body instanceof FormData ? body : objectToFormData(body), + extra, + ), + ); + } + + protected async delete( + path: string, + body?: object, + extra?: RequestInit, + ): Promise> { + return await this.request( + await this.constructRequest(path, "DELETE", body, extra), + ); + } + + protected async deleteForm( + path: string, + body: FormData | ConvertibleObject, + extra?: RequestInit, + ): Promise> { + return await this.request( + await this.constructRequest( + path, + "DELETE", + body instanceof FormData ? body : objectToFormData(body), + extra, + ), + ); + } +} diff --git a/client/lysand/constants.ts b/client/lysand/constants.ts new file mode 100644 index 0000000..1682f60 --- /dev/null +++ b/client/lysand/constants.ts @@ -0,0 +1,5 @@ +import pkg from "../package.json"; + +export const NO_REDIRECT = "urn:ietf:wg:oauth:2.0:oob"; +export const DEFAULT_SCOPE = ["read", "write", "follow"]; +export const DEFAULT_UA = `LysandClient/${pkg.version} (+${pkg.homepage})`; diff --git a/client/lysand/lysand.ts b/client/lysand/lysand.ts new file mode 100644 index 0000000..e770d48 --- /dev/null +++ b/client/lysand/lysand.ts @@ -0,0 +1,1133 @@ +import { OAuth2Client } from "@badgateway/oauth2-client"; +import type { Account } from "../types/account"; +import type { Activity } from "../types/activity"; +import type { Announcement } from "../types/announcement"; +import type { Application, ApplicationData } from "../types/application"; +import type { Attachment } from "../types/attachment"; +import type { Context } from "../types/context"; +import type { Conversation } from "../types/conversation"; +import type { Emoji } from "../types/emoji"; +import type { FeaturedTag } from "../types/featured_tag"; +import type { List } from "../types/list"; +import type { Marker } from "../types/marker"; +import type { Notification } from "../types/notification"; +import type { Poll } from "../types/poll"; +import type { Preferences } from "../types/preferences"; +import type { Relationship } from "../types/relationship"; +import type { ScheduledStatus } from "../types/scheduled_status"; +import type { Status } from "../types/status"; +import type { Tag } from "../types/tag"; +import type { Token } from "../types/token"; +import { BaseClient, type Output } from "./base"; +import { DEFAULT_SCOPE, NO_REDIRECT } from "./constants"; + +type StatusContentType = + | "text/plain" + | "text/markdown" + | "text/html" + | "text/x.misskeymarkdown"; + +interface InstanceV2Output { + domain: string; + title: string; + version: string; + lysand_version: string; + source_url: string; + description: string; + usage: { + users: { + active_month: number; + }; + }; + thumbnail: { + url: string | null; + }; + banner: { + url: string | null; + }; + languages: string[]; + configuration: { + urls: { + streaming: string | null; + status: string | null; + }; + accounts: { + max_featured_tags: number; + }; + statuses: { + max_characters: number; + max_media_attachments: number; + characters_reserved_per_url: number; + }; + media_attachments: { + supported_mime_types: string[]; + image_size_limit: number; + image_matrix_limit: number; + video_size_limit: number; + video_frame_rate_limit: number; + video_matrix_limit: number; + }; + polls: { + max_characters_per_option: number; + max_expiration: number; + max_options: number; + min_expiration: number; + }; + translation: { + enabled: boolean; + }; + }; + registrations: { + enabled: boolean; + approval_required: boolean; + message: string | null; + url: string | null; + }; + contact: { + email: string | null; + account: Account | null; + }; + rules: { + id: string; + text: string; + hint: string; + }[]; + sso: { + forced: boolean; + providers: { + name: string; + icon: string; + id: string; + }[]; + }; +} + +export class LysandClient extends BaseClient { + public acceptFollowRequest( + id: string, + extra?: RequestInit, + ): Promise> { + return this.post( + `/api/v1/follow_requests/${id}/authorize`, + undefined, + extra, + ); + } + + public addAccountToList( + id: string, + account_ids: string[], + extra?: RequestInit, + ): Promise> { + return this.post( + `/api/v1/lists/${id}/accounts`, + { account_ids }, + extra, + ); + } + + public addReactionToAnnouncement( + id: string, + name: string, + extra?: RequestInit, + ): Promise> { + return this.put( + `/api/v1/announcements/${id}/reactions/${name}`, + undefined, + extra, + ); + } + + public blockAccount( + id: string, + extra?: RequestInit, + ): Promise> { + return this.post( + `/api/v1/accounts/${id}/block`, + undefined, + extra, + ); + } + + public blockDomain( + domain: string, + extra?: RequestInit, + ): Promise> { + return this.post("/api/v1/domain_blocks", { domain }, extra); + } + + public bookmarkStatus( + id: string, + extra?: RequestInit, + ): Promise> { + return this.post( + `/api/v1/statuses/${id}/bookmark`, + undefined, + extra, + ); + } + + public cancelScheduledStatus( + id: string, + extra?: RequestInit, + ): Promise> { + return this.delete( + `/api/v1/scheduled_statuses/${id}/cancel`, + undefined, + extra, + ); + } + + public createApp( + client_name: string, + options?: Partial<{ + redirect_uris: string; + scopes: string[]; + website?: string; + }>, + ): Promise> { + return this.postForm("/api/v1/apps", { + client_name, + ...options, + scopes: options?.scopes?.join(" ") || DEFAULT_SCOPE.join(" "), + redirect_uris: options?.redirect_uris || NO_REDIRECT, + }); + } + + public createEmojiReaction( + id: string, + emoji: string, + extra?: RequestInit, + ): Promise> { + return this.post( + `/api/v1/statuses/${id}/reactions/${emoji}`, + undefined, + extra, + ); + } + + public createFeaturedTag( + name: string, + extra?: RequestInit, + ): Promise> { + return this.post("/api/v1/featured_tags", { name }, extra); + } + + public createList( + title: string, + extra?: RequestInit, + ): Promise> { + return this.post("/api/v1/lists", { title }, extra); + } + + public deleteAccountsFromList( + id: string, + account_ids: string[], + extra?: RequestInit, + ): Promise> { + return this.delete( + `/api/v1/lists/${id}/accounts`, + { account_ids }, + extra, + ); + } + + public deleteConversation( + id: string, + extra?: RequestInit, + ): Promise> { + return this.delete( + `/api/v1/conversations/${id}`, + undefined, + extra, + ); + } + + public deleteEmojiReaction( + id: string, + emoji: string, + extra?: RequestInit, + ): Promise> { + return this.delete( + `/api/v1/statuses/${id}/reactions/${emoji}`, + undefined, + extra, + ); + } + + public deleteFeaturedTag( + id: string, + extra?: RequestInit, + ): Promise> { + return this.delete( + `/api/v1/featured_tags/${id}`, + undefined, + extra, + ); + } + + public deleteList(id: string, extra?: RequestInit): Promise> { + return this.delete(`/api/v1/lists/${id}`, undefined, extra); + } + + public deletePushSubscription(extra?: RequestInit): Promise> { + return this.delete("/api/v1/push/subscription", undefined, extra); + } + + public deleteStatus( + id: string, + extra?: RequestInit, + ): Promise> { + return this.delete(`/api/v1/statuses/${id}`, undefined, extra); + } + + // TODO: directStreaming + + public dismissInstanceAnnouncement( + id: string, + extra?: RequestInit, + ): Promise> { + return this.post( + `/api/v1/instance/announcements/${id}/dismiss`, + undefined, + extra, + ); + } + + public dismissNotification( + id: string, + extra?: RequestInit, + ): Promise> { + return this.post( + `/api/v1/notifications/${id}/dismiss`, + undefined, + extra, + ); + } + + public dismissNotifications(extra?: RequestInit): Promise> { + return this.post("/api/v1/notifications/clear", undefined, extra); + } + + public editStatus( + id: string, + options: Partial<{ + status: string; + content_type: StatusContentType; + media_ids: string[]; + poll: Partial<{ + expires_in: number; + hide_totals: boolean; + multiple: boolean; + options: string[]; + }>; + sensitive: boolean; + spoiler_text: string; + language: string; + }>, + extra?: RequestInit, + ): Promise> { + return this.put( + `/api/v1/statuses/${id}`, + { ...options }, + extra, + ); + } + + public favouriteStatus( + id: string, + extra?: RequestInit, + ): Promise> { + return this.post( + `/api/v1/statuses/${id}/favourite`, + undefined, + extra, + ); + } + + public fetchAccessToken( + client_id: string, + client_secret: string, + code?: string, + redirect_uri: string = NO_REDIRECT, + extra?: RequestInit, + ): Promise> { + return this.postForm( + "/oauth/token", + { + client_id, + client_secret, + code, + redirect_uri, + grant_type: "authorization_code", + }, + extra, + ); + } + + public followAccount( + id: string, + extra?: RequestInit, + ): Promise> { + return this.post( + `/api/v1/accounts/${id}/follow`, + undefined, + extra, + ); + } + + public followTag(id: string, extra?: RequestInit): Promise> { + return this.post(`/api/v1/tags/${id}/follow`, undefined, extra); + } + + public generateAuthUrl( + client_id: string, + client_secret: string, + options: Partial<{ + redirect_uri: string; + scopes: string[]; + }>, + ): Promise { + const oauthClient = new OAuth2Client({ + server: this.baseUrl.toString(), + clientId: client_id, + clientSecret: client_secret, + tokenEndpoint: "/oauth/token", + authorizationEndpoint: "/oauth/authorize", + }); + + return oauthClient.authorizationCode.getAuthorizeUri({ + redirectUri: options.redirect_uri || NO_REDIRECT, + scope: options.scopes || DEFAULT_SCOPE, + }); + } + + public getAccount( + id: string, + extra?: RequestInit, + ): Promise> { + return this.get(`/api/v1/accounts/${id}`, extra); + } + + public getAccountFollowers( + id: string, + options?: Partial<{ + max_id: string; + since_id: string; + limit: number; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get( + `/api/v1/accounts/${id}/followers?${params}`, + extra, + ); + } + + public getAccountFollowing( + id: string, + options?: Partial<{ + max_id: string; + since_id: string; + limit: number; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get( + `/api/v1/accounts/${id}/following?${params}`, + extra, + ); + } + + public getAccountLists( + id: string, + extra?: RequestInit, + ): Promise> { + return this.get(`/api/v1/accounts/${id}/lists`, extra); + } + + public getAccountStatuses( + id: string, + options?: Partial<{ + max_id: string; + min_id: string; + since_id: string; + limit: number; + only_media: boolean; + pinned: boolean; + exclude_replies: boolean; + exclude_reblogs: boolean; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.min_id) params.set("min_id", options.min_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + if (options.only_media) params.set("only_media", "true"); + if (options.pinned) params.set("pinned", "true"); + if (options.exclude_replies) params.set("exclude_replies", "true"); + if (options.exclude_reblogs) params.set("exclude_reblogs", "true"); + } + + return this.get( + `/api/v1/accounts/${id}/statuses?${params}`, + extra, + ); + } + + public getAccountsInList( + id: string, + options: Partial<{ + max_id: string; + since_id: string; + limit: number; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get( + `/api/v1/lists/${id}/accounts?${params}`, + extra, + ); + } + + public getBlocks( + options?: Partial<{ + max_id: string; + since_id: string; + limit: number; + }>, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get(`/api/v1/blocks?${params}`); + } + + public getBookmarks( + options?: Partial<{ + max_id: string; + min_id: string; + since_id: string; + limit: number; + }>, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.min_id) params.set("min_id", options.min_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get(`/api/v1/bookmarks?${params}`); + } + + public getConversationTimeline( + id: string, + options?: Partial<{ + max_id: string; + min_id: string; + since_id: string; + limit: number; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.min_id) params.set("min_id", options.min_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get( + `/api/v1/conversations/${id}/timeline?${params}`, + extra, + ); + } + + public getDomainBlocks( + options?: Partial<{ + max_id: string; + since_id: string; + limit: number; + }>, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get(`/api/v1/domain_blocks?${params}`); + } + + public getEndorsements( + options?: Partial<{ + max_id: string; + since_id: string; + limit: number; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get(`/api/v1/endorsements?${params}`, extra); + } + + public getFavourites( + options?: Partial<{ + max_id: string; + min_id: string; + limit: number; + }>, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.min_id) params.set("min_id", options.min_id); + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get(`/api/v1/favourites?${params}`); + } + + public getFeaturedTags( + extra?: RequestInit, + ): Promise> { + return this.get("/api/v1/featured_tags", extra); + } + + // TODO: getFilter + // TODO: getFilters + + public getFollowRequests( + options?: Partial<{ + limit: number; + }>, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get(`/api/v1/follow_requests?${params}`); + } + + public getFollowedTags(extra?: RequestInit): Promise> { + return this.get("/api/v1/followed_tags", extra); + } + + public getHomeTimeline( + options?: Partial<{ + max_id: string; + min_id: string; + since_id: string; + limit: number; + local: boolean; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.min_id) params.set("min_id", options.min_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + if (options.local) params.set("local", "true"); + } + + return this.get(`/api/v1/timelines/home?${params}`, extra); + } + + public getInstance(extra?: RequestInit): Promise> { + return this.get("/api/v2/instance", extra); + } + + public getInstanceActivity( + extra?: RequestInit, + ): Promise> { + return this.get("/api/v1/instance/activity", extra); + } + + public getInstanceAnnouncements( + extra?: RequestInit, + ): Promise> { + return this.get( + "/api/v1/instance/announcements", + extra, + ); + } + + public getInstanceCustomEmojis( + extra?: RequestInit, + ): Promise> { + return this.get("/api/v1/custom_emojis", extra); + } + + public getInstanceDirectory( + options?: Partial<{ + limit: number; + local: boolean; + offset: number; + order: "active" | "new"; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.limit) params.set("limit", options.limit.toString()); + if (options.local) params.set("local", "true"); + if (options.offset) params.set("offset", options.offset.toString()); + if (options.order) params.set("order", options.order); + } + + return this.get(`/api/v1/directory?${params}`, extra); + } + + public getInstancePeers(extra?: RequestInit): Promise> { + return this.get("/api/v1/instance/peers", extra); + } + + public getInstanceTrends( + options?: Partial<{ limit: number }>, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get(`/api/v1/trends?${params}`); + } + + public getList(id: string, extra?: RequestInit): Promise> { + return this.get(`/api/v1/lists/${id}`, extra); + } + + /** + * GET /api/v1/timelines/list/:id + * + * @param id Local ID of the list in the database. + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of statuses. + */ + public getListTimeline( + id: string, + options?: Partial<{ + max_id: string; + min_id: string; + since_id: string; + limit: number; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.min_id) params.set("min_id", options.min_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get( + `/api/v1/timelines/list/${id}?${params}`, + extra, + ); + } + + /** + * GET /api/v1/lists + * + * @return Array of lists. + */ + public getLists(extra?: RequestInit): Promise> { + return this.get("/api/v1/lists", extra); + } + + /** + * GET /api/v1/timelines/public + * + * @param options.only_media Show only statuses with media attached? Defaults to false. + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @param options.only_media Show only statuses with media attached? Defaults to false. + * @return Array of statuses. + */ + public getLocalTimeline( + options?: Partial<{ + max_id: string; + min_id: string; + since_id: string; + limit: number; + only_media: boolean; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.min_id) params.set("min_id", options.min_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + if (options.only_media) params.set("only_media", "true"); + } + + return this.get(`/api/v1/timelines/public?${params}`, extra); + } + + /** + * GET /api/v1/markers + * + * @param timelines Array of timeline names, String enum anyOf home, notifications. + * @return Marker or empty object. + */ + public getMarkers( + timelines: ("home" | "notifications")[], + ): Promise>> { + const params = new URLSearchParams(); + + for (const timeline of timelines) { + params.append("timelines[]", timeline); + } + + return this.get>( + `/api/v1/markers?${params}`, + ); + } + + /** + * GET /api/v1/media/:id + * + * @param id Target media ID. + * @return Attachment + */ + public getMedia( + id: string, + extra?: RequestInit, + ): Promise> { + return this.get(`/api/v1/media/${id}`, extra); + } + + /** + * GET /api/v1/mutes + * + * @param options.limit Max number of results to return. Defaults to 40. + * @param options.max_id Return results older than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of accounts. + */ + public getMutes( + options?: Partial<{ + max_id: string; + since_id: string; + limit: number; + }>, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get(`/api/v1/mutes?${params}`); + } + + /** + * GET /api/v1/notifications/:id + * + * @param id Target notification ID. + * @return Notification. + */ + public getNotification( + id: string, + extra?: RequestInit, + ): Promise> { + return this.get(`/api/v1/notifications/${id}`, extra); + } + + /** + * GET /api/v1/notifications + * + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @param options.exclude_types Array of types to exclude. + * @param options.account_id Return only notifications received from this account. + * @return Array of notifications. + */ + public getNotifications( + options?: Partial<{ + max_id: string; + min_id: string; + since_id: string; + limit: number; + exclude_types: string[]; + account_id: string; + }>, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.min_id) params.set("min_id", options.min_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + if (options.exclude_types) { + for (const type of options.exclude_types) { + params.append("exclude_types[]", type); + } + } + if (options.account_id) + params.set("account_id", options.account_id); + } + + return this.get(`/api/v1/notifications?${params}`); + } + + /** + * GET /api/v1/polls/:id + * + * @param id Target poll ID. + * @return Poll. + */ + public getPoll(id: string, extra?: RequestInit): Promise> { + return this.get(`/api/v1/polls/${id}`, extra); + } + + /** + * GET /api/v1/preferences + * + * @return Preferences. + */ + public getPreferences(extra?: RequestInit): Promise> { + return this.get("/api/v1/preferences", extra); + } + + /** + * GET /api/v1/timelines/public + * + * @param options.only_media Show only statuses with media attached? Defaults to false. + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of statuses. + */ + public getPublicTimeline( + options?: Partial<{ + max_id: string; + min_id: string; + since_id: string; + limit: number; + only_media: boolean; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.min_id) params.set("min_id", options.min_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + if (options.only_media) params.set("only_media", "true"); + } + + return this.get(`/api/v1/timelines/public?${params}`, extra); + } + + /** + * GET /api/v1/push/subscription + * + * @return PushSubscription. + */ + public getPushSubscription( + extra?: RequestInit, + ): Promise> { + return this.get("/api/v1/push/subscription", extra); + } + + /** + * GET /api/v1/accounts/relationships + * + * @param id The account ID. + * @param options.with_suspended Include relationships with suspended accounts? Defaults to false. + * @return Relationship + */ + public getRelationship( + id: string, + options?: Partial<{ + with_suspended: boolean; + }>, + extra?: RequestInit, + ): Promise> { + return this.getRelationships([id], options, extra).then((r) => ({ + data: r.data[0], + headers: r.headers, + })); + } + + /** + * GET /api/v1/accounts/relationships + * + * @param ids Array of account IDs. + * @param options.with_suspended Include relationships with suspended accounts? Defaults to false. + * @return Array of Relationship. + */ + public getRelationships( + ids: string[], + options?: Partial<{ + with_suspended: boolean; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + for (const id of ids) { + params.append("id[]", id); + } + + if (options) { + if (options.with_suspended) params.set("with_suspended", "true"); + } + + return this.get( + `/api/v1/accounts/relationships?${params}`, + extra, + ); + } + + /** + * GET /api/v1/scheduled_statuses/:id + * + * @param id Target status ID. + * @return ScheduledStatus. + */ + public getScheduledStatus( + id: string, + extra?: RequestInit, + ): Promise> { + return this.get( + `/api/v1/scheduled_statuses/${id}`, + extra, + ); + } + + /** + * GET /api/v1/scheduled_statuses + * + * @param options.limit Max number of results to return. Defaults to 20. + * @param options.max_id Return results older than ID. + * @param options.since_id Return results newer than ID. + * @param options.min_id Return results immediately newer than ID. + * @return Array of scheduled statuses. + */ + public getScheduledStatuses( + options?: Partial<{ + max_id: string; + min_id: string; + since_id: string; + limit: number; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.max_id) params.set("max_id", options.max_id); + if (options.min_id) params.set("min_id", options.min_id); + if (options.since_id) params.set("since_id", options.since_id); + if (options.limit) params.set("limit", options.limit.toString()); + } + + return this.get( + `/api/v1/scheduled_statuses?${params}`, + extra, + ); + } + + public getStatus(id: string, extra?: RequestInit): Promise> { + return this.get(`/api/v1/statuses/${id}`, extra); + } + + public getStatusContext( + id: string, + options?: Partial<{ + limit: number; + max_id: string; + since_id: string; + }>, + extra?: RequestInit, + ): Promise> { + const params = new URLSearchParams(); + + if (options) { + if (options.limit) params.set("limit", options.limit.toString()); + if (options.max_id) params.set("max_id", options.max_id); + if (options.since_id) params.set("since_id", options.since_id); + } + + return this.get( + `/api/v1/statuses/${id}/context?${params}`, + extra, + ); + } +} diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..ec41472 --- /dev/null +++ b/client/package.json @@ -0,0 +1,60 @@ +{ + "name": "@lysand-org/client", + "displayName": "Lysand Client", + "version": "0.0.0", + "author": { + "email": "jesse.wierzbinski@lysand.org", + "name": "Jesse Wierzbinski (CPlusPatch)", + "url": "https://cpluspatch.com" + }, + "readme": "README.md", + "repository": { + "type": "git", + "url": "https://github.com/lysand-org/api.git", + "directory": "client" + }, + "bugs": { + "url": "https://github.com/lysand-org/api/issues" + }, + "license": "MIT", + "contributors": [ + { + "name": "Jesse Wierzbinski", + "email": "jesse.wierzbinski@lysand.org", + "url": "https://cpluspatch.com" + } + ], + "maintainers": [ + { + "name": "Jesse Wierzbinski", + "email": "jesse.wierzbinski@lysand.org", + "url": "https://cpluspatch.com" + } + ], + "description": "Client for Mastodon and Lysand API", + "categories": ["Other"], + "type": "module", + "engines": { + "bun": ">=1.1.8" + }, + "exports": { + ".": { + "import": "./dist/index.js", + "default": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/lysand" + }, + "homepage": "https://lysand.org", + "keywords": ["lysand", "mastodon", "api", "typescript", "rest"], + "packageManager": "bun@1.1.8", + "dependencies": { + "@badgateway/oauth2-client": "^2.3.0", + "@types/mime-types": "^2.1.4", + "magic-regexp": "^0.8.0", + "mime-types": "^2.1.35" + } +} diff --git a/client/types/account.ts b/client/types/account.ts new file mode 100644 index 0000000..9d796c8 --- /dev/null +++ b/client/types/account.ts @@ -0,0 +1,34 @@ +import type { Emoji } from "./emoji"; +import type { Field } from "./field"; +import type { Role } from "./role"; +import type { Source } from "./source"; + +export type Account = { + id: string; + username: string; + acct: string; + display_name: string; + locked: boolean; + discoverable?: boolean; + group: boolean | null; + noindex: boolean | null; + suspended: boolean | null; + limited: boolean | null; + created_at: string; + followers_count: number; + following_count: number; + statuses_count: number; + note: string; + url: string; + avatar: string; + avatar_static: string; + header: string; + header_static: string; + emojis: Array; + moved: Account | null; + fields: Array; + bot: boolean | null; + source?: Source; + role?: Role; + mute_expires_at?: string; +}; diff --git a/client/types/activity.ts b/client/types/activity.ts new file mode 100644 index 0000000..8dbc511 --- /dev/null +++ b/client/types/activity.ts @@ -0,0 +1,6 @@ +export type Activity = { + week: string; + statuses: string; + logins: string; + registrations: string; +}; diff --git a/client/types/announcement.ts b/client/types/announcement.ts new file mode 100644 index 0000000..5a4642c --- /dev/null +++ b/client/types/announcement.ts @@ -0,0 +1,39 @@ +import type { Emoji } from "./emoji"; +import type { StatusTag } from "./status"; + +export type Announcement = { + id: string; + content: string; + starts_at: string | null; + ends_at: string | null; + published: boolean; + all_day: boolean; + published_at: string; + updated_at: string | null; + read: boolean | null; + mentions: Array; + statuses: Array; + tags: Array; + emojis: Array; + reactions: Array; +}; + +export type AnnouncementAccount = { + id: string; + username: string; + url: string; + acct: string; +}; + +export type AnnouncementStatus = { + id: string; + url: string; +}; + +export type AnnouncementReaction = { + name: string; + count: number; + me: boolean | null; + url: string | null; + static_url: string | null; +}; diff --git a/client/types/application.ts b/client/types/application.ts new file mode 100644 index 0000000..caf062d --- /dev/null +++ b/client/types/application.ts @@ -0,0 +1,14 @@ +export type Application = { + name: string; + website?: string | null; + vapid_key?: string | null; +}; + +export type ApplicationData = { + id: string; + name: string; + website?: string | null; + client_id: string; + client_secret: string; + vapid_key?: string | null; +}; diff --git a/client/types/async_attachment.ts b/client/types/async_attachment.ts new file mode 100644 index 0000000..7b2b5b0 --- /dev/null +++ b/client/types/async_attachment.ts @@ -0,0 +1,13 @@ +import type { Meta } from "./attachment"; + +export type AsyncAttachment = { + id: string; + type: "unknown" | "image" | "gifv" | "video" | "audio"; + url: string | null; + remote_url: string | null; + preview_url: string; + text_url: string | null; + meta: Meta | null; + description: string | null; + blurhash: string | null; +}; diff --git a/client/types/attachment.ts b/client/types/attachment.ts new file mode 100644 index 0000000..c3a3697 --- /dev/null +++ b/client/types/attachment.ts @@ -0,0 +1,47 @@ +export type Sub = { + // For Image, Gifv, and Video + width?: number; + height?: number; + size?: string; + aspect?: number; + + // For Gifv and Video + frame_rate?: string; + + // For Audio, Gifv, and Video + duration?: number; + bitrate?: number; +}; + +export type Focus = { + x: number; + y: number; +}; + +export type Meta = { + original?: Sub; + small?: Sub; + focus?: Focus; + length?: string; + duration?: number; + fps?: number; + size?: string; + width?: number; + height?: number; + aspect?: number; + audio_encode?: string; + audio_bitrate?: string; + audio_channel?: string; +}; + +export type Attachment = { + id: string; + type: "unknown" | "image" | "gifv" | "video" | "audio"; + url: string; + remote_url: string | null; + preview_url: string | null; + text_url: string | null; + meta: Meta | null; + description: string | null; + blurhash: string | null; +}; diff --git a/client/types/card.ts b/client/types/card.ts new file mode 100644 index 0000000..6d12122 --- /dev/null +++ b/client/types/card.ts @@ -0,0 +1,16 @@ +export type Card = { + url: string; + title: string; + description: string; + type: "link" | "photo" | "video" | "rich"; + image: string | null; + author_name: string | null; + author_url: string | null; + provider_name: string | null; + provider_url: string | null; + html: string | null; + width: number | null; + height: number | null; + embed_url: string | null; + blurhash: string | null; +}; diff --git a/client/types/context.ts b/client/types/context.ts new file mode 100644 index 0000000..23f9643 --- /dev/null +++ b/client/types/context.ts @@ -0,0 +1,6 @@ +import type { Status } from "./status"; + +export type Context = { + ancestors: Array; + descendants: Array; +}; diff --git a/client/types/conversation.ts b/client/types/conversation.ts new file mode 100644 index 0000000..4a44c9b --- /dev/null +++ b/client/types/conversation.ts @@ -0,0 +1,9 @@ +import type { Account } from "./account"; +import type { Status } from "./status"; + +export type Conversation = { + id: string; + accounts: Array; + last_status: Status | null; + unread: boolean; +}; diff --git a/client/types/emoji.ts b/client/types/emoji.ts new file mode 100644 index 0000000..cc8de67 --- /dev/null +++ b/client/types/emoji.ts @@ -0,0 +1,7 @@ +export type Emoji = { + shortcode: string; + static_url: string; + url: string; + visible_in_picker: boolean; + category?: string; +}; diff --git a/client/types/featured_tag.ts b/client/types/featured_tag.ts new file mode 100644 index 0000000..2fe3ba6 --- /dev/null +++ b/client/types/featured_tag.ts @@ -0,0 +1,6 @@ +export type FeaturedTag = { + id: string; + name: string; + statuses_count: number; + last_status_at: string; +}; diff --git a/client/types/field.ts b/client/types/field.ts new file mode 100644 index 0000000..7798269 --- /dev/null +++ b/client/types/field.ts @@ -0,0 +1,6 @@ +export type Field = { + name: string; + value: string; + verified_at?: string | null; + verified?: boolean | false; +}; diff --git a/client/types/filter.ts b/client/types/filter.ts new file mode 100644 index 0000000..a39928c --- /dev/null +++ b/client/types/filter.ts @@ -0,0 +1,10 @@ +export type Filter = { + id: string; + phrase: string; + context: Array; + expires_at: string | null; + irreversible: boolean; + whole_word: boolean; +}; + +export type FilterContext = string; diff --git a/client/types/follow_request.ts b/client/types/follow_request.ts new file mode 100644 index 0000000..edcc447 --- /dev/null +++ b/client/types/follow_request.ts @@ -0,0 +1,25 @@ +import type { Emoji } from "./emoji"; +import type { Field } from "./field"; + +export type FollowRequest = { + id: number; + username: string; + acct: string; + display_name: string; + locked: boolean; + bot: boolean; + discoverable?: boolean; + group: boolean; + created_at: string; + note: string; + url: string; + avatar: string; + avatar_static: string; + header: string; + header_static: string; + followers_count: number; + following_count: number; + statuses_count: number; + emojis: Array; + fields: Array; +}; diff --git a/client/types/history.ts b/client/types/history.ts new file mode 100644 index 0000000..22176bd --- /dev/null +++ b/client/types/history.ts @@ -0,0 +1,5 @@ +export type History = { + day: string; + uses: number; + accounts: number; +}; diff --git a/client/types/identity_proof.ts b/client/types/identity_proof.ts new file mode 100644 index 0000000..00d9efc --- /dev/null +++ b/client/types/identity_proof.ts @@ -0,0 +1,7 @@ +export type IdentityProof = { + provider: string; + provider_username: string; + updated_at: string; + proof_url: string; + profile_url: string; +}; diff --git a/client/types/instance.ts b/client/types/instance.ts new file mode 100644 index 0000000..b276880 --- /dev/null +++ b/client/types/instance.ts @@ -0,0 +1,38 @@ +import type { Account } from "./account"; +import type { Stats } from "./stats"; +import type { URLs } from "./urls"; + +export type Instance = { + uri: string; + title: string; + description: string; + email: string; + version: string; + thumbnail: string | null; + urls: URLs | null; + stats: Stats; + languages: Array; + registrations: boolean; + approval_required: boolean; + invites_enabled?: boolean; + configuration: { + statuses: { + max_characters: number; + max_media_attachments?: number; + characters_reserved_per_url?: number; + }; + polls?: { + max_options: number; + max_characters_per_option: number; + min_expiration: number; + max_expiration: number; + }; + }; + contact_account?: Account; + rules?: Array; +}; + +export type InstanceRule = { + id: string; + text: string; +}; diff --git a/client/types/list.ts b/client/types/list.ts new file mode 100644 index 0000000..3ae0d37 --- /dev/null +++ b/client/types/list.ts @@ -0,0 +1,7 @@ +export type List = { + id: string; + title: string; + replies_policy: RepliesPolicy | null; +}; + +export type RepliesPolicy = "followed" | "list" | "none"; diff --git a/client/types/marker.ts b/client/types/marker.ts new file mode 100644 index 0000000..d69b0ef --- /dev/null +++ b/client/types/marker.ts @@ -0,0 +1,13 @@ +export type Marker = { + home?: { + last_read_id: string; + version: number; + updated_at: string; + }; + notifications?: { + last_read_id: string; + version: number; + updated_at: string; + unread_count?: number; + }; +}; diff --git a/client/types/mention.ts b/client/types/mention.ts new file mode 100644 index 0000000..dba8929 --- /dev/null +++ b/client/types/mention.ts @@ -0,0 +1,6 @@ +export type Mention = { + id: string; + username: string; + url: string; + acct: string; +}; diff --git a/client/types/notification.ts b/client/types/notification.ts new file mode 100644 index 0000000..d240f49 --- /dev/null +++ b/client/types/notification.ts @@ -0,0 +1,15 @@ +import type { Account } from "./account"; +import type { Reaction } from "./reaction"; +import type { Status } from "./status"; + +export type Notification = { + account: Account | null; + created_at: string; + id: string; + status?: Status; + reaction?: Reaction; + type: NotificationType; + target?: Account; +}; + +export type NotificationType = string; diff --git a/client/types/poll.ts b/client/types/poll.ts new file mode 100644 index 0000000..48fed02 --- /dev/null +++ b/client/types/poll.ts @@ -0,0 +1,14 @@ +export type Poll = { + id: string; + expires_at: string | null; + expired: boolean; + multiple: boolean; + votes_count: number; + options: Array; + voted: boolean; +}; + +export type PollOption = { + title: string; + votes_count: number | null; +}; diff --git a/client/types/preferences.ts b/client/types/preferences.ts new file mode 100644 index 0000000..c0efe96 --- /dev/null +++ b/client/types/preferences.ts @@ -0,0 +1,9 @@ +import type { StatusVisibility } from "./status"; + +export type Preferences = { + "posting:default:visibility": StatusVisibility; + "posting:default:sensitive": boolean; + "posting:default:language": string | null; + "reading:expand:media": "default" | "show_all" | "hide_all"; + "reading:expand:spoilers": boolean; +}; diff --git a/client/types/push_subscription.ts b/client/types/push_subscription.ts new file mode 100644 index 0000000..0449551 --- /dev/null +++ b/client/types/push_subscription.ts @@ -0,0 +1,14 @@ +export type Alerts = { + follow: boolean; + favourite: boolean; + mention: boolean; + reblog: boolean; + poll: boolean; +}; + +export type PushSubscription = { + id: string; + endpoint: string; + server_key: string; + alerts: Alerts; +}; diff --git a/client/types/reaction.ts b/client/types/reaction.ts new file mode 100644 index 0000000..e7fffdf --- /dev/null +++ b/client/types/reaction.ts @@ -0,0 +1,11 @@ +import type { Account } from "./account"; + +export type Reaction = { + count: number; + me: boolean; + name: string; + url?: string; + static_url?: string; + accounts?: Array; + account_ids?: Array; +}; diff --git a/client/types/relationship.ts b/client/types/relationship.ts new file mode 100644 index 0000000..1d5540d --- /dev/null +++ b/client/types/relationship.ts @@ -0,0 +1,15 @@ +export type Relationship = { + id: string; + following: boolean; + followed_by: boolean; + blocking: boolean; + blocked_by: boolean; + muting: boolean; + muting_notifications: boolean; + requested: boolean; + domain_blocking: boolean; + showing_reblogs: boolean; + endorsed: boolean; + notifying: boolean; + note: string | null; +}; diff --git a/client/types/report.ts b/client/types/report.ts new file mode 100644 index 0000000..385e5d8 --- /dev/null +++ b/client/types/report.ts @@ -0,0 +1,16 @@ +import type { Account } from "./account"; + +export type Report = { + id: string; + action_taken: boolean; + action_taken_at: string | null; + status_ids: Array | null; + rule_ids: Array | null; + // These parameters don't exist in Pleroma + category: Category | null; + comment: string | null; + forwarded: boolean | null; + target_account?: Account | null; +}; + +export type Category = "spam" | "violation" | "other"; diff --git a/client/types/results.ts b/client/types/results.ts new file mode 100644 index 0000000..68ffd53 --- /dev/null +++ b/client/types/results.ts @@ -0,0 +1,9 @@ +import type { Account } from "./account"; +import type { Status } from "./status"; +import type { Tag } from "./tag"; + +export type Results = { + accounts: Array; + statuses: Array; + hashtags: Array; +}; diff --git a/client/types/role.ts b/client/types/role.ts new file mode 100644 index 0000000..fdb98ca --- /dev/null +++ b/client/types/role.ts @@ -0,0 +1,3 @@ +export type Role = { + name: string; +}; diff --git a/client/types/scheduled_status.ts b/client/types/scheduled_status.ts new file mode 100644 index 0000000..e8f8a2a --- /dev/null +++ b/client/types/scheduled_status.ts @@ -0,0 +1,9 @@ +import type { Attachment } from "./attachment"; +import type { StatusParams } from "./status_params"; + +export type ScheduledStatus = { + id: string; + scheduled_at: string; + params: StatusParams; + media_attachments: Array | null; +}; diff --git a/client/types/source.ts b/client/types/source.ts new file mode 100644 index 0000000..161be04 --- /dev/null +++ b/client/types/source.ts @@ -0,0 +1,9 @@ +import type { Field } from "./field"; + +export type Source = { + privacy: string | null; + sensitive: boolean | null; + language: string | null; + note: string; + fields: Array; +}; diff --git a/client/types/stats.ts b/client/types/stats.ts new file mode 100644 index 0000000..7654b38 --- /dev/null +++ b/client/types/stats.ts @@ -0,0 +1,5 @@ +export type Stats = { + user_count: number; + status_count: number; + domain_count: number; +}; diff --git a/client/types/status.ts b/client/types/status.ts new file mode 100644 index 0000000..c61c984 --- /dev/null +++ b/client/types/status.ts @@ -0,0 +1,50 @@ +import type { Account } from "./account"; +import type { Application } from "./application"; +import type { Attachment } from "./attachment"; +import type { Card } from "./card"; +import type { Emoji } from "./emoji"; +import type { Mention } from "./mention"; +import type { Poll } from "./poll"; +import type { Reaction } from "./reaction"; + +export type Status = { + id: string; + uri: string; + url: string; + account: Account; + in_reply_to_id: string | null; + in_reply_to_account_id: string | null; + reblog: Status | null; + content: string; + plain_content: string | null; + created_at: string; + edited_at: string | null; + emojis: Emoji[]; + replies_count: number; + reblogs_count: number; + favourites_count: number; + reblogged: boolean | null; + favourited: boolean | null; + muted: boolean | null; + sensitive: boolean; + spoiler_text: string; + visibility: StatusVisibility; + media_attachments: Array; + mentions: Array; + tags: Array; + card: Card | null; + poll: Poll | null; + application: Application | null; + language: string | null; + pinned: boolean | null; + emoji_reactions: Array; + quote: boolean; + bookmarked: boolean; +}; + +export type StatusTag = { + name: string; + url: string; +}; + +export type StatusVisibility = "public" | "unlisted" | "private" | "direct"; diff --git a/client/types/status_params.ts b/client/types/status_params.ts new file mode 100644 index 0000000..89babfb --- /dev/null +++ b/client/types/status_params.ts @@ -0,0 +1,12 @@ +import type { StatusVisibility } from "./status"; + +export type StatusParams = { + text: string; + in_reply_to_id: string | null; + media_ids: Array | null; + sensitive: boolean | null; + spoiler_text: string | null; + visibility: StatusVisibility | null; + scheduled_at: string | null; + application_id: number | null; +}; diff --git a/client/types/status_source.ts b/client/types/status_source.ts new file mode 100644 index 0000000..346c346 --- /dev/null +++ b/client/types/status_source.ts @@ -0,0 +1,5 @@ +export type StatusSource = { + id: string; + text: string; + spoiler_text: string; +}; diff --git a/client/types/tag.ts b/client/types/tag.ts new file mode 100644 index 0000000..da41dd3 --- /dev/null +++ b/client/types/tag.ts @@ -0,0 +1,8 @@ +import type { History } from "./history"; + +export type Tag = { + name: string; + url: string; + history: Array; + following?: boolean; +}; diff --git a/client/types/token.ts b/client/types/token.ts new file mode 100644 index 0000000..0021a49 --- /dev/null +++ b/client/types/token.ts @@ -0,0 +1,6 @@ +export type Token = { + access_token: string; + token_type: string; + scope: string; + created_at: number; +}; diff --git a/client/types/urls.ts b/client/types/urls.ts new file mode 100644 index 0000000..59cca70 --- /dev/null +++ b/client/types/urls.ts @@ -0,0 +1,3 @@ +export type URLs = { + streaming_api: string; +}; diff --git a/tsconfig.json b/tsconfig.json index c8aa060..1cf67a4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,9 @@ "allowJs": true, "emitDecoratorMetadata": false, "experimentalDecorators": true, - "verbatimModuleSyntax": true + "verbatimModuleSyntax": true, + "noUnusedLocals": true, + "noUnusedParameters": true }, "include": ["*.ts", "*.d.ts", "**/*.ts", "**/*.d.ts"] }