diff --git a/examples/create-new-status-with-image.ts b/examples/create-new-status-with-image.ts index 17715af01..26c10abaf 100644 --- a/examples/create-new-status-with-image.ts +++ b/examples/create-new-status-with-image.ts @@ -9,13 +9,13 @@ const main = async () => { }); // Upload the image - const attachment = await masto.mediaAttachments.create({ + const attachment = await masto.v2.mediaAttachments.create({ file: fs.createReadStream('../some_image.png'), description: 'Some image', }); // Publish! - const status = await masto.statuses.create({ + const status = await masto.v1.statuses.create({ status: 'Toot from TypeScript', visibility: 'direct', mediaIds: [attachment.id], diff --git a/examples/create-new-status.ts b/examples/create-new-status.ts index 053e75d42..31d3c45fd 100644 --- a/examples/create-new-status.ts +++ b/examples/create-new-status.ts @@ -6,7 +6,7 @@ const main = async () => { accessToken: 'YOUR TOKEN', }); - await masto.statuses.create({ + await masto.v1.statuses.create({ status: 'Toot from TypeScript', visibility: 'direct', }); diff --git a/examples/experimental-fetch.ts b/examples/experimental-fetch.ts deleted file mode 100644 index d5e199404..000000000 --- a/examples/experimental-fetch.ts +++ /dev/null @@ -1,30 +0,0 @@ -import fs from 'node:fs'; - -import { login } from 'masto/fetch'; - -const main = async () => { - const masto = await login({ - url: 'https://example.com', - accessToken: 'YOUR TOKEN', - }); - - // Upload the image - const attachment = await masto.mediaAttachments.create({ - file: new Blob([fs.readFileSync('../some_image.png')]), - description: 'Some image', - }); - - // Publish! - const status = await masto.statuses.create({ - status: 'Post from TypeScript', - visibility: 'direct', - mediaIds: [attachment.id], - }); - - console.log(status); -}; - -main().catch((error) => { - console.error(error); - process.exit(1); -}); diff --git a/examples/get-followers.ts b/examples/get-followers.ts index 1422d0666..b2c6151ff 100644 --- a/examples/get-followers.ts +++ b/examples/get-followers.ts @@ -9,9 +9,9 @@ const main = async () => { }); // Fetch your own account - const me = await masto.accounts.verifyCredentials(); + const me = await masto.v1.accounts.verifyCredentials(); - const { value: followers, done } = await masto.accounts.fetchFollowers( + const { value: followers, done } = await masto.v1.accounts.fetchFollowers( me.id, { limit: 60, minId: '123123' }, ); diff --git a/examples/moderate-reports.ts b/examples/moderate-reports.ts index 22b7d9d11..c2100c71c 100644 --- a/examples/moderate-reports.ts +++ b/examples/moderate-reports.ts @@ -7,10 +7,10 @@ const main = async () => { }); // Fetching reports - const reports = await masto.admin.report.fetchAll(); + const reports = await masto.v1.admin.report.fetchAll(); // Disable an account of the 1st report - await masto.admin.account.createAction(reports[0].account.id, { + await masto.v1.admin.account.createAction(reports[0].account.id, { type: 'disable', reportId: reports[0].id, text: 'Your account has been disabled', diff --git a/examples/object-oriented.ts b/examples/object-oriented.ts index bff4b473a..afa0d08fd 100644 --- a/examples/object-oriented.ts +++ b/examples/object-oriented.ts @@ -1,4 +1,4 @@ -import type { MastoClient, Notification, Status } from 'masto'; +import type { MastoClient, V1 } from 'masto'; import { login } from 'masto'; class MyBot { @@ -17,19 +17,19 @@ class MyBot { } async subscribe() { - const timeline = await this.masto.stream.streamUser(); + const timeline = await this.masto.v1.stream.streamUser(); // Add handlers timeline.on('update', this.handleUpdate); timeline.on('notification', this.handleNotification); } - private handleUpdate = (status: Status) => { + private handleUpdate = (status: V1.Status) => { const { content, account } = status; console.log(`${account.username} said ${content}`); }; - private handleNotification = async (notification: Notification) => { + private handleNotification = async (notification: V1.Notification) => { // When your status got favourited, log if (notification.type === 'favourite') { console.log(`${notification.account.username} favourited your status!`); @@ -37,7 +37,7 @@ class MyBot { // When you got a mention, reply if (notification.type === 'mention' && notification.status) { - await this.masto.statuses.create({ + await this.masto.v1.statuses.create({ status: 'I received your mention!', inReplyToId: notification.status.id, }); @@ -45,7 +45,7 @@ class MyBot { // When you got followed, follow them back if (notification.type === 'follow') { - await this.masto.accounts.follow(notification.account.id); + await this.masto.v1.accounts.follow(notification.account.id); } }; } diff --git a/examples/register-new-app.ts b/examples/register-new-app.ts index 74c0730a7..e17be0b53 100644 --- a/examples/register-new-app.ts +++ b/examples/register-new-app.ts @@ -5,7 +5,7 @@ const main = async () => { url: 'https://example.com', }); - const app = await masto.apps.create({ + const app = await masto.v1.apps.create({ clientName: 'My app', redirectUris: 'urn:ietf:wg:oauth:2.0:oob', scopes: 'read write', diff --git a/examples/timeline-rxjs.ts b/examples/timeline-rxjs.ts index b3b70dd35..d967242b7 100644 --- a/examples/timeline-rxjs.ts +++ b/examples/timeline-rxjs.ts @@ -1,14 +1,14 @@ -import type { Notification, Status } from 'masto'; +import type { V1 } from 'masto'; import { login } from 'masto'; import { fromEvent } from 'rxjs'; import { filter, map } from 'rxjs/operators'; const main = async () => { const masto = await login({ url: 'https://example.com' }); - const timeline = await masto.stream.streamPublicTimeline(); + const timeline = await masto.v1.stream.streamPublicTimeline(); - const update$ = fromEvent(timeline, 'update'); - const notification$ = fromEvent(timeline, 'notifications'); + const update$ = fromEvent(timeline, 'update'); + const notification$ = fromEvent(timeline, 'notifications'); update$.subscribe((status) => { console.log(status.account.username); diff --git a/examples/timeline-with-iterable.ts b/examples/timeline-with-iterable.ts index 2150729bb..8e3b9885b 100644 --- a/examples/timeline-with-iterable.ts +++ b/examples/timeline-with-iterable.ts @@ -5,7 +5,7 @@ const main = async () => { url: 'https://example.com', }); - const result = await masto.timelines.fetchPublic({ + const result = await masto.v1.timelines.fetchPublic({ limit: 30, }); @@ -15,9 +15,9 @@ const main = async () => { console.log(result.value); // You can also use `for-await-of` syntax to iterate over the timeline - for await (const statuses of masto.timelines.iteratePublic()) { + for await (const statuses of masto.v1.timelines.iteratePublic()) { for (const status of statuses) { - masto.statuses.favourite(status.id); + masto.v1.statuses.favourite(status.id); } } }; diff --git a/examples/timeline-with-streaming.ts b/examples/timeline-with-streaming.ts index d32b6bf92..d45b66a34 100644 --- a/examples/timeline-with-streaming.ts +++ b/examples/timeline-with-streaming.ts @@ -7,7 +7,7 @@ const main = async () => { }); // Connect to the streaming api - const stream = await masto.stream.streamPublicTimeline(); + const stream = await masto.v1.stream.streamPublicTimeline(); // Subscribe to updates stream.on('update', (status) => { diff --git a/examples/update-profile.ts b/examples/update-profile.ts index f94a1de8d..6925ed08a 100644 --- a/examples/update-profile.ts +++ b/examples/update-profile.ts @@ -8,7 +8,7 @@ const main = async () => { accessToken: 'YOUR TOKEN', }); - const newProfile = await masto.accounts.updateCredentials({ + const newProfile = await masto.v1.accounts.updateCredentials({ displayName: 'Fluffy elephant friend', note: 'Hi fediverse!', avatar: fs.createReadStream('../some_image.png'), diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 000000000..1e9aa3492 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,4 @@ +export * as V1 from './v1'; +export * as V2 from './v2'; +export * from './repository'; +export * from './iterable-repository'; diff --git a/src/repositories/iterable-repository.ts b/src/api/iterable-repository.ts similarity index 100% rename from src/repositories/iterable-repository.ts rename to src/api/iterable-repository.ts diff --git a/src/repositories/repository.ts b/src/api/repository.ts similarity index 100% rename from src/repositories/repository.ts rename to src/api/repository.ts diff --git a/src/entities/account-featured-tags.ts b/src/api/v1/entities/account-featured-tags.ts similarity index 100% rename from src/entities/account-featured-tags.ts rename to src/api/v1/entities/account-featured-tags.ts diff --git a/src/entities/account.ts b/src/api/v1/entities/account.ts similarity index 100% rename from src/entities/account.ts rename to src/api/v1/entities/account.ts diff --git a/src/entities/activity.ts b/src/api/v1/entities/activity.ts similarity index 100% rename from src/entities/activity.ts rename to src/api/v1/entities/activity.ts diff --git a/src/entities/admin/account.ts b/src/api/v1/entities/admin/account.ts similarity index 100% rename from src/entities/admin/account.ts rename to src/api/v1/entities/admin/account.ts diff --git a/src/entities/admin/canonical-email-block.ts b/src/api/v1/entities/admin/canonical-email-block.ts similarity index 100% rename from src/entities/admin/canonical-email-block.ts rename to src/api/v1/entities/admin/canonical-email-block.ts diff --git a/src/entities/admin/domain-allow.ts b/src/api/v1/entities/admin/domain-allow.ts similarity index 100% rename from src/entities/admin/domain-allow.ts rename to src/api/v1/entities/admin/domain-allow.ts diff --git a/src/entities/admin/domain-block.ts b/src/api/v1/entities/admin/domain-block.ts similarity index 100% rename from src/entities/admin/domain-block.ts rename to src/api/v1/entities/admin/domain-block.ts diff --git a/src/entities/admin/domain-email-block.ts b/src/api/v1/entities/admin/domain-email-block.ts similarity index 100% rename from src/entities/admin/domain-email-block.ts rename to src/api/v1/entities/admin/domain-email-block.ts diff --git a/src/entities/admin/index.ts b/src/api/v1/entities/admin/index.ts similarity index 100% rename from src/entities/admin/index.ts rename to src/api/v1/entities/admin/index.ts diff --git a/src/entities/admin/ip-block.ts b/src/api/v1/entities/admin/ip-block.ts similarity index 100% rename from src/entities/admin/ip-block.ts rename to src/api/v1/entities/admin/ip-block.ts diff --git a/src/entities/admin/report.ts b/src/api/v1/entities/admin/report.ts similarity index 100% rename from src/entities/admin/report.ts rename to src/api/v1/entities/admin/report.ts diff --git a/src/entities/announcement.ts b/src/api/v1/entities/announcement.ts similarity index 100% rename from src/entities/announcement.ts rename to src/api/v1/entities/announcement.ts diff --git a/src/entities/application.ts b/src/api/v1/entities/application.ts similarity index 100% rename from src/entities/application.ts rename to src/api/v1/entities/application.ts diff --git a/src/entities/card.ts b/src/api/v1/entities/card.ts similarity index 100% rename from src/entities/card.ts rename to src/api/v1/entities/card.ts diff --git a/src/entities/context.ts b/src/api/v1/entities/context.ts similarity index 100% rename from src/entities/context.ts rename to src/api/v1/entities/context.ts diff --git a/src/entities/conversation.ts b/src/api/v1/entities/conversation.ts similarity index 100% rename from src/entities/conversation.ts rename to src/api/v1/entities/conversation.ts diff --git a/src/entities/emoji.ts b/src/api/v1/entities/emoji.ts similarity index 100% rename from src/entities/emoji.ts rename to src/api/v1/entities/emoji.ts diff --git a/src/entities/featured-tags.ts b/src/api/v1/entities/featured-tags.ts similarity index 100% rename from src/entities/featured-tags.ts rename to src/api/v1/entities/featured-tags.ts diff --git a/src/entities/field.ts b/src/api/v1/entities/field.ts similarity index 100% rename from src/entities/field.ts rename to src/api/v1/entities/field.ts diff --git a/src/entities/filter-result.ts b/src/api/v1/entities/filter-result.ts similarity index 100% rename from src/entities/filter-result.ts rename to src/api/v1/entities/filter-result.ts diff --git a/src/api/v1/entities/filter.ts b/src/api/v1/entities/filter.ts new file mode 100644 index 000000000..8a070c4fc --- /dev/null +++ b/src/api/v1/entities/filter.ts @@ -0,0 +1,25 @@ +export type FilterContext = + | 'home' + | 'notifications' + | 'public' + | 'thread' + | 'account'; + +/** + * Represents a user-defined filter for determining which statuses should not be shown to the user. + * @see https://docs.joinmastodon.org/entities/filter/ + */ +export interface Filter { + /** The ID of the filter in the database. */ + id: string; + /** The text to be filtered. */ + phrase: string; + /** The contexts in which the filter should be applied. */ + context: FilterContext[]; + /** When the filter should no longer be applied */ + expiresAt?: string | null; + /** Should matching entities in home and notifications be dropped by the server? */ + irreversible: boolean; + /** Should the filter consider word boundaries? */ + wholeWord: boolean; +} diff --git a/src/entities/history.ts b/src/api/v1/entities/history.ts similarity index 100% rename from src/entities/history.ts rename to src/api/v1/entities/history.ts diff --git a/src/entities/identity-proof.ts b/src/api/v1/entities/identity-proof.ts similarity index 100% rename from src/entities/identity-proof.ts rename to src/api/v1/entities/identity-proof.ts diff --git a/src/entities/index.ts b/src/api/v1/entities/index.ts similarity index 92% rename from src/entities/index.ts rename to src/api/v1/entities/index.ts index 7154b264b..7e331835c 100644 --- a/src/entities/index.ts +++ b/src/api/v1/entities/index.ts @@ -4,7 +4,7 @@ export * from './activity'; export * as Admin from './admin'; export * from './announcement'; export * from './application'; -export * from './attachment'; +export * from './media-attachment'; export * from './card'; export * from './context'; export * from './conversation'; @@ -26,7 +26,7 @@ export * from './preference'; export * from './push-subscription'; export * from './reaction'; export * from './relationship'; -export * from './results'; +export * from './search'; export * from './scheduled-status'; export * from './source'; export * from './status'; @@ -35,3 +35,4 @@ export * from './status-source'; export * from './tag'; export * from './token'; export * from './suggestion'; +export * from './rule'; diff --git a/src/entities/instance.ts b/src/api/v1/entities/instance.ts similarity index 95% rename from src/entities/instance.ts rename to src/api/v1/entities/instance.ts index dcb8e21c6..668731294 100644 --- a/src/entities/instance.ts +++ b/src/api/v1/entities/instance.ts @@ -1,4 +1,5 @@ -import type { Account } from '.'; +import type { Account } from './account'; +import type { Rule } from './rule'; export interface InstanceStatusesConfiguration { maxCharacters: number; @@ -73,7 +74,7 @@ export interface Instance { /** A user that can be contacted, as an alternative to `email`. */ contactAccount?: Account | null; - rules?: InstanceRule[] | null; + rules?: Rule[] | null; } export interface InstanceURLs { @@ -89,8 +90,3 @@ export interface InstanceStats { /** Domains federated with this instance. Number. */ domainCount: number; } - -export interface InstanceRule { - id: string; - text: string; -} diff --git a/src/entities/link.ts b/src/api/v1/entities/link.ts similarity index 100% rename from src/entities/link.ts rename to src/api/v1/entities/link.ts diff --git a/src/entities/list.ts b/src/api/v1/entities/list.ts similarity index 100% rename from src/entities/list.ts rename to src/api/v1/entities/list.ts diff --git a/src/entities/marker.ts b/src/api/v1/entities/marker.ts similarity index 100% rename from src/entities/marker.ts rename to src/api/v1/entities/marker.ts diff --git a/src/api/v1/entities/media-attachment.ts b/src/api/v1/entities/media-attachment.ts new file mode 100644 index 000000000..ab2d42f6f --- /dev/null +++ b/src/api/v1/entities/media-attachment.ts @@ -0,0 +1,73 @@ +export type MediaAttachmentType = + | 'image' + | 'video' + | 'gifv' + | 'audio' + | 'unknown'; + +export interface MediaAttachmentMetaImage { + width: number; + height: number; + size: string; + aspect: number; +} + +export interface MediaAttachmentMetaVideo { + width: number; + height: number; + frameRate: string; + duration: number; + bitrate: number; + aspect: number; +} + +export interface MediaAttachmentMetaFocus { + x: number; + y: number; +} + +export interface MediaAttachmentMetaColors { + background: string; + foreground: string; + accent: string; +} + +export interface MediaAttachmentMeta { + small?: MediaAttachmentMetaImage | MediaAttachmentMetaVideo | null; + original?: MediaAttachmentMetaImage | MediaAttachmentMetaVideo | null; + focus?: MediaAttachmentMetaFocus | null; + colors?: MediaAttachmentMetaColors | null; +} + +/** + * Represents a file or media MediaAttachment that can be added to a status. + * @see https://docs.joinmastodon.org/entities/MediaAttachment/ + */ +export interface MediaAttachment { + /** The ID of the MediaAttachment in the database. */ + id: string; + /** The type of the MediaAttachment. */ + type: MediaAttachmentType; + /** The location of the original full-size MediaAttachment. */ + url?: string | null; + /** The location of a scaled-down preview of the MediaAttachment. */ + previewUrl: string; + /** The location of the full-size original MediaAttachment on the remote website. */ + remoteUrl?: string | null; + /** Remote version of previewUrl */ + previewRemoteUrl?: string | null; + /** A shorter URL for the MediaAttachment. */ + textUrl?: string | null; + /** Metadata returned by Paperclip. */ + meta?: MediaAttachmentMeta | null; + /** + * Alternate text that describes what is in the media MediaAttachment, + * to be used for the visually impaired or when media MediaAttachments do not load. + */ + description?: string | null; + /** + * A hash computed by the BlurHash algorithm, + * for generating colorful preview thumbnails when media has not been downloaded yet. + */ + blurhash?: string | null; +} diff --git a/src/entities/mention.ts b/src/api/v1/entities/mention.ts similarity index 100% rename from src/entities/mention.ts rename to src/api/v1/entities/mention.ts diff --git a/src/entities/notification.ts b/src/api/v1/entities/notification.ts similarity index 100% rename from src/entities/notification.ts rename to src/api/v1/entities/notification.ts diff --git a/src/entities/poll.ts b/src/api/v1/entities/poll.ts similarity index 100% rename from src/entities/poll.ts rename to src/api/v1/entities/poll.ts diff --git a/src/entities/preference.ts b/src/api/v1/entities/preference.ts similarity index 100% rename from src/entities/preference.ts rename to src/api/v1/entities/preference.ts diff --git a/src/entities/push-subscription.ts b/src/api/v1/entities/push-subscription.ts similarity index 57% rename from src/entities/push-subscription.ts rename to src/api/v1/entities/push-subscription.ts index ffcbb5e6a..a23955e62 100644 --- a/src/entities/push-subscription.ts +++ b/src/api/v1/entities/push-subscription.ts @@ -1,8 +1,8 @@ /** * Represents a subscription to the push streaming server. - * @see https://docs.joinmastodon.org/entities/push-subscription/ + * @see https://docs.joinmastodon.org/entities/WebPushSubscription/ */ -export interface PushSubscription { +export interface WebPushSubscription { /** The id of the push subscription in the database. */ id: string; /** Where push alerts will be sent to. */ @@ -10,10 +10,10 @@ export interface PushSubscription { /** The streaming server's VAPID key. */ serverKey: string; /** Which alerts should be delivered to the `endpoint`. */ - alerts: PushSubscriptionAlerts; + alerts: WebPushSubscriptionAlerts; } -export interface PushSubscriptionAlerts { +export interface WebPushSubscriptionAlerts { /** Receive a push notification when someone has followed you? Boolean. */ follow: boolean; /** Receive a push notification when a status you created has been favourited by someone else? Boolean. */ @@ -24,4 +24,14 @@ export interface PushSubscriptionAlerts { mention: boolean; /** Receive a push notification when a poll you voted in or created has ended? Boolean. */ poll: boolean; + /** Receive new subscribed account notifications? Defaults to false. */ + status: boolean; + /** Receive status edited notifications? Defaults to false. */ + update: boolean; + admin: { + /** Receive new user signup notifications? Defaults to false. Must have a role with the appropriate permissions. */ + signUp: boolean; + /** Receive new report notifications? Defaults to false. Must have a role with the appropriate permissions. */ + report: boolean; + }; } diff --git a/src/entities/reaction.ts b/src/api/v1/entities/reaction.ts similarity index 100% rename from src/entities/reaction.ts rename to src/api/v1/entities/reaction.ts diff --git a/src/entities/relationship.ts b/src/api/v1/entities/relationship.ts similarity index 100% rename from src/entities/relationship.ts rename to src/api/v1/entities/relationship.ts diff --git a/src/api/v1/entities/rule.ts b/src/api/v1/entities/rule.ts new file mode 100644 index 000000000..9fd602b44 --- /dev/null +++ b/src/api/v1/entities/rule.ts @@ -0,0 +1,4 @@ +export interface Rule { + id: string; + text: string; +} diff --git a/src/entities/scheduled-status.ts b/src/api/v1/entities/scheduled-status.ts similarity index 88% rename from src/entities/scheduled-status.ts rename to src/api/v1/entities/scheduled-status.ts index 8da65ccac..6be2acf34 100644 --- a/src/entities/scheduled-status.ts +++ b/src/api/v1/entities/scheduled-status.ts @@ -1,4 +1,4 @@ -import type { Attachment } from './attachment'; +import type { MediaAttachment } from './media-attachment'; import type { Status } from './status'; export interface StatusParams @@ -26,5 +26,5 @@ export interface ScheduledStatus { /** Parameters of the status */ params: StatusParams; /** Media attachments */ - mediaAttachments: Attachment[]; + mediaAttachments: MediaAttachment[]; } diff --git a/src/entities/results.ts b/src/api/v1/entities/search.ts similarity index 75% rename from src/entities/results.ts rename to src/api/v1/entities/search.ts index 3d1b79569..45f4d3bde 100644 --- a/src/entities/results.ts +++ b/src/api/v1/entities/search.ts @@ -1,14 +1,14 @@ -import type { Account, Status, Tag } from '.'; +import type { Account, Status } from '.'; /** * Represents the results of a search. * @see https://docs.joinmastodon.org/entities/results/ */ -export interface Results { +export interface Search { /** Accounts which match the given query */ accounts: Account[]; /** Statuses which match the given query */ statuses: Status[]; /** Hashtags which match the given query */ - hashtags: Tag[]; + hashtags: string[]; } diff --git a/src/entities/source.ts b/src/api/v1/entities/source.ts similarity index 100% rename from src/entities/source.ts rename to src/api/v1/entities/source.ts diff --git a/src/entities/status-edit.ts b/src/api/v1/entities/status-edit.ts similarity index 100% rename from src/entities/status-edit.ts rename to src/api/v1/entities/status-edit.ts diff --git a/src/entities/status-source.ts b/src/api/v1/entities/status-source.ts similarity index 100% rename from src/entities/status-source.ts rename to src/api/v1/entities/status-source.ts diff --git a/src/entities/status.ts b/src/api/v1/entities/status.ts similarity index 98% rename from src/entities/status.ts rename to src/api/v1/entities/status.ts index 26d25d973..1d91a9332 100644 --- a/src/entities/status.ts +++ b/src/api/v1/entities/status.ts @@ -1,10 +1,10 @@ import type { Account, Application, - Attachment, Card, Emoji, FilterResult, + MediaAttachment, Mention, Poll, Tag, @@ -36,7 +36,7 @@ export interface Status { /** Subject or summary line, below which status content is collapsed until expanded. */ spoilerText: string; /** Media that is attached to this status. */ - mediaAttachments: Attachment[]; + mediaAttachments: MediaAttachment[]; /** The application used to post this status. */ application: Application; diff --git a/src/entities/suggestion.ts b/src/api/v1/entities/suggestion.ts similarity index 100% rename from src/entities/suggestion.ts rename to src/api/v1/entities/suggestion.ts diff --git a/src/entities/tag.ts b/src/api/v1/entities/tag.ts similarity index 100% rename from src/entities/tag.ts rename to src/api/v1/entities/tag.ts diff --git a/src/entities/token.ts b/src/api/v1/entities/token.ts similarity index 100% rename from src/entities/token.ts rename to src/api/v1/entities/token.ts diff --git a/src/api/v1/index.ts b/src/api/v1/index.ts new file mode 100644 index 000000000..1dff724e9 --- /dev/null +++ b/src/api/v1/index.ts @@ -0,0 +1,4 @@ +export * from './entities'; +export * from './repositories'; +export * from './repository'; +export * from './repository-admin'; diff --git a/src/repositories/__tests__/email-repository.spec.ts b/src/api/v1/repositories/__tests__/email-repository.spec.ts similarity index 77% rename from src/repositories/__tests__/email-repository.spec.ts rename to src/api/v1/repositories/__tests__/email-repository.spec.ts index 29a3e78c8..ea585b44b 100644 --- a/src/repositories/__tests__/email-repository.spec.ts +++ b/src/api/v1/repositories/__tests__/email-repository.spec.ts @@ -1,7 +1,7 @@ import { SemVer } from 'semver'; -import { MastoConfig } from '../../config'; -import { HttpMockImpl, httpPost } from '../../http/http-mock-impl'; -import { SerializerNativeImpl } from '../../serializers'; +import { MastoConfig } from '../../../../config'; +import { HttpMockImpl, httpPost } from '../../../../http/http-mock-impl'; +import { SerializerNativeImpl } from '../../../../serializers'; import { EmailRepository } from '../email-repository'; describe('email', () => { diff --git a/src/repositories/__tests__/status-repository.spec.ts b/src/api/v1/repositories/__tests__/status-repository.spec.ts similarity index 90% rename from src/repositories/__tests__/status-repository.spec.ts rename to src/api/v1/repositories/__tests__/status-repository.spec.ts index 339170848..c3fe29aed 100644 --- a/src/repositories/__tests__/status-repository.spec.ts +++ b/src/api/v1/repositories/__tests__/status-repository.spec.ts @@ -1,12 +1,12 @@ import { SemVer } from 'semver'; -import { MastoConfig } from '../../config'; +import { MastoConfig } from '../../../../config'; import { httpDelete, httpGet, HttpMockImpl, httpPost, -} from '../../http/http-mock-impl'; -import { SerializerNativeImpl } from '../../serializers'; +} from '../../../../http/http-mock-impl'; +import { SerializerNativeImpl } from '../../../../serializers'; import { StatusRepository } from '../status-repository'; describe('status', () => { diff --git a/src/repositories/account-repository.ts b/src/api/v1/repositories/account-repository.ts similarity index 97% rename from src/repositories/account-repository.ts rename to src/api/v1/repositories/account-repository.ts index 7d9392f29..dff53c0ac 100644 --- a/src/repositories/account-repository.ts +++ b/src/api/v1/repositories/account-repository.ts @@ -1,5 +1,8 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators/version'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators/version'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import type { DefaultPaginationParams, Repository } from '../../repository'; import type { Account, AccountCredentials, @@ -11,9 +14,6 @@ import type { Source, Status, } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import type { DefaultPaginationParams, Repository } from './repository'; export interface CreateAccountParams { /** The desired username for the account */ diff --git a/src/repositories/admin/account-repository.ts b/src/api/v1/repositories/admin/account-repository.ts similarity index 96% rename from src/repositories/admin/account-repository.ts rename to src/api/v1/repositories/admin/account-repository.ts index 072b52d41..71237cea4 100644 --- a/src/repositories/admin/account-repository.ts +++ b/src/api/v1/repositories/admin/account-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../../config'; -import { version } from '../../decorators'; +import type { MastoConfig } from '../../../../config'; +import { version } from '../../../../decorators'; +import type { Http } from '../../../../http'; +import type { Repository } from '../../../repository'; import type { Admin } from '../../entities'; -import type { Http } from '../../http'; -import type { Repository } from '../repository'; export interface FetchAccountsParams { /** Filter for local accounts? */ diff --git a/src/repositories/admin/canonical-email-block-repository.ts b/src/api/v1/repositories/admin/canonical-email-block-repository.ts similarity index 100% rename from src/repositories/admin/canonical-email-block-repository.ts rename to src/api/v1/repositories/admin/canonical-email-block-repository.ts diff --git a/src/repositories/admin/domain-allow-repository.ts b/src/api/v1/repositories/admin/domain-allow-repository.ts similarity index 100% rename from src/repositories/admin/domain-allow-repository.ts rename to src/api/v1/repositories/admin/domain-allow-repository.ts diff --git a/src/repositories/admin/domain-block-repository.ts b/src/api/v1/repositories/admin/domain-block-repository.ts similarity index 100% rename from src/repositories/admin/domain-block-repository.ts rename to src/api/v1/repositories/admin/domain-block-repository.ts diff --git a/src/repositories/admin/email-domain-block-repository.ts b/src/api/v1/repositories/admin/email-domain-block-repository.ts similarity index 100% rename from src/repositories/admin/email-domain-block-repository.ts rename to src/api/v1/repositories/admin/email-domain-block-repository.ts diff --git a/src/repositories/admin/index.ts b/src/api/v1/repositories/admin/index.ts similarity index 100% rename from src/repositories/admin/index.ts rename to src/api/v1/repositories/admin/index.ts diff --git a/src/repositories/admin/ip-block-repository.ts b/src/api/v1/repositories/admin/ip-block-repository.ts similarity index 100% rename from src/repositories/admin/ip-block-repository.ts rename to src/api/v1/repositories/admin/ip-block-repository.ts diff --git a/src/repositories/admin/report-repository.ts b/src/api/v1/repositories/admin/report-repository.ts similarity index 92% rename from src/repositories/admin/report-repository.ts rename to src/api/v1/repositories/admin/report-repository.ts index 4a781aa4f..e2befc8ac 100644 --- a/src/repositories/admin/report-repository.ts +++ b/src/api/v1/repositories/admin/report-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../../config'; -import { version } from '../../decorators'; +import type { MastoConfig } from '../../../../config'; +import { version } from '../../../../decorators'; +import type { Http } from '../../../../http'; +import type { Repository } from '../../../repository'; import type { Admin } from '../../entities'; -import type { Http } from '../../http'; -import type { Repository } from '../repository'; export interface FetchReportsParams { readonly resolved?: boolean | null; diff --git a/src/repositories/announcement-repository.ts b/src/api/v1/repositories/announcement-repository.ts similarity index 88% rename from src/repositories/announcement-repository.ts rename to src/api/v1/repositories/announcement-repository.ts index 514e76a14..b7b963092 100644 --- a/src/repositories/announcement-repository.ts +++ b/src/api/v1/repositories/announcement-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; import type { Announcement } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; export class AnnouncementRepository implements Repository { constructor(private readonly http: Http, readonly config: MastoConfig) {} diff --git a/src/repositories/app-repository.ts b/src/api/v1/repositories/app-repository.ts similarity index 87% rename from src/repositories/app-repository.ts rename to src/api/v1/repositories/app-repository.ts index d78adc90d..d225046ec 100644 --- a/src/repositories/app-repository.ts +++ b/src/api/v1/repositories/app-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; import type { Client } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; export interface CreateAppParams { /** A name of your application */ diff --git a/src/repositories/block-repository.ts b/src/api/v1/repositories/block-repository.ts similarity index 64% rename from src/repositories/block-repository.ts rename to src/api/v1/repositories/block-repository.ts index baf67fe8c..8f9bfc5bf 100644 --- a/src/repositories/block-repository.ts +++ b/src/api/v1/repositories/block-repository.ts @@ -1,10 +1,10 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import { IterableRepository } from '../../iterable-repository'; +import type { DefaultPaginationParams } from '../../repository'; import type { Account } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import { IterableRepository } from './iterable-repository'; -import type { DefaultPaginationParams } from './repository'; export class BlockRepository extends IterableRepository { constructor(private readonly http: Http, readonly config: MastoConfig) { diff --git a/src/repositories/bookmark-repository.ts b/src/api/v1/repositories/bookmark-repository.ts similarity index 65% rename from src/repositories/bookmark-repository.ts rename to src/api/v1/repositories/bookmark-repository.ts index f235a9ab2..119940461 100644 --- a/src/repositories/bookmark-repository.ts +++ b/src/api/v1/repositories/bookmark-repository.ts @@ -1,10 +1,10 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import { IterableRepository } from '../../iterable-repository'; +import type { DefaultPaginationParams } from '../../repository'; import type { Status } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import { IterableRepository } from './iterable-repository'; -import type { DefaultPaginationParams } from './repository'; export class BookmarkRepository extends IterableRepository { constructor(private readonly http: Http, readonly config: MastoConfig) { diff --git a/src/repositories/conversation-repository.ts b/src/api/v1/repositories/conversation-repository.ts similarity index 79% rename from src/repositories/conversation-repository.ts rename to src/api/v1/repositories/conversation-repository.ts index 4c055df3b..005c50f44 100644 --- a/src/repositories/conversation-repository.ts +++ b/src/api/v1/repositories/conversation-repository.ts @@ -1,10 +1,10 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import { IterableRepository } from '../../iterable-repository'; +import type { DefaultPaginationParams } from '../../repository'; import type { Conversation } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import { IterableRepository } from './iterable-repository'; -import type { DefaultPaginationParams } from './repository'; export class ConversationRepository extends IterableRepository { constructor(private readonly http: Http, readonly config: MastoConfig) { diff --git a/src/repositories/custom-emoji-repository.ts b/src/api/v1/repositories/custom-emoji-repository.ts similarity index 71% rename from src/repositories/custom-emoji-repository.ts rename to src/api/v1/repositories/custom-emoji-repository.ts index ad4b2b6f0..f9ecd463a 100644 --- a/src/repositories/custom-emoji-repository.ts +++ b/src/api/v1/repositories/custom-emoji-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; import type { Emoji } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; export class CustomEmojiRepository implements Repository { constructor(private readonly http: Http, readonly config: MastoConfig) {} diff --git a/src/repositories/directory-repository.ts b/src/api/v1/repositories/directory-repository.ts similarity index 85% rename from src/repositories/directory-repository.ts rename to src/api/v1/repositories/directory-repository.ts index 9c5d68370..02c544ae5 100644 --- a/src/repositories/directory-repository.ts +++ b/src/api/v1/repositories/directory-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; import type { Account } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; export type DirectoryOrderType = 'active' | 'new'; diff --git a/src/repositories/domain-block-repository.ts b/src/api/v1/repositories/domain-block-repository.ts similarity index 81% rename from src/repositories/domain-block-repository.ts rename to src/api/v1/repositories/domain-block-repository.ts index e24e547e3..59b5bd048 100644 --- a/src/repositories/domain-block-repository.ts +++ b/src/api/v1/repositories/domain-block-repository.ts @@ -1,9 +1,9 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import { IterableRepository } from './iterable-repository'; -import type { DefaultPaginationParams } from './repository'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import { IterableRepository } from '../../iterable-repository'; +import type { DefaultPaginationParams } from '../../repository'; export class DomainBlockRepository extends IterableRepository { constructor(private readonly http: Http, readonly config: MastoConfig) { diff --git a/src/repositories/email-repository.ts b/src/api/v1/repositories/email-repository.ts similarity index 77% rename from src/repositories/email-repository.ts rename to src/api/v1/repositories/email-repository.ts index 89bb02f7d..78182c623 100644 --- a/src/repositories/email-repository.ts +++ b/src/api/v1/repositories/email-repository.ts @@ -1,5 +1,5 @@ -import type { MastoConfig } from '../config'; -import type { Http } from '../http'; +import type { MastoConfig } from '../../../config'; +import type { Http } from '../../../http'; export interface CreateConfirmationParams { readonly email?: string; diff --git a/src/repositories/endorsement-repository.ts b/src/api/v1/repositories/endorsement-repository.ts similarity index 65% rename from src/repositories/endorsement-repository.ts rename to src/api/v1/repositories/endorsement-repository.ts index 02c87399b..6f4d12883 100644 --- a/src/repositories/endorsement-repository.ts +++ b/src/api/v1/repositories/endorsement-repository.ts @@ -1,10 +1,10 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import { IterableRepository } from '../../iterable-repository'; +import type { DefaultPaginationParams } from '../../repository'; import type { Account } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import { IterableRepository } from './iterable-repository'; -import type { DefaultPaginationParams } from './repository'; export class EndorsementRepository extends IterableRepository { constructor(private readonly http: Http, readonly config: MastoConfig) { diff --git a/src/repositories/favourite-repository.ts b/src/api/v1/repositories/favourite-repository.ts similarity index 64% rename from src/repositories/favourite-repository.ts rename to src/api/v1/repositories/favourite-repository.ts index aba3dbd19..6172b5806 100644 --- a/src/repositories/favourite-repository.ts +++ b/src/api/v1/repositories/favourite-repository.ts @@ -1,10 +1,10 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import { IterableRepository } from '../../iterable-repository'; +import type { DefaultPaginationParams } from '../../repository'; import type { Status } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import { IterableRepository } from './iterable-repository'; -import type { DefaultPaginationParams } from './repository'; export class FavouriteRepository extends IterableRepository { constructor(private readonly http: Http, readonly config: MastoConfig) { diff --git a/src/repositories/featured-tag-repository.ts b/src/api/v1/repositories/featured-tag-repository.ts similarity index 89% rename from src/repositories/featured-tag-repository.ts rename to src/api/v1/repositories/featured-tag-repository.ts index 52c937650..4d850e9b5 100644 --- a/src/repositories/featured-tag-repository.ts +++ b/src/api/v1/repositories/featured-tag-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; import type { FeaturedTag, Tag } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; export interface CreateFeaturedTagParams { /** The hashtag to be featured. */ diff --git a/src/repositories/filter-repository.ts b/src/api/v1/repositories/filter-repository.ts similarity index 92% rename from src/repositories/filter-repository.ts rename to src/api/v1/repositories/filter-repository.ts index bb5b03dbe..38cb52a0a 100644 --- a/src/repositories/filter-repository.ts +++ b/src/api/v1/repositories/filter-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; import type { Filter, FilterContext } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; export interface CreateFilterParams { /** Text to be filtered */ diff --git a/src/repositories/follow-request-repository.ts b/src/api/v1/repositories/follow-request-repository.ts similarity index 80% rename from src/repositories/follow-request-repository.ts rename to src/api/v1/repositories/follow-request-repository.ts index 9b295d907..a1730b4d9 100644 --- a/src/repositories/follow-request-repository.ts +++ b/src/api/v1/repositories/follow-request-repository.ts @@ -1,10 +1,10 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import { IterableRepository } from '../../iterable-repository'; +import type { DefaultPaginationParams } from '../../repository'; import type { Account, Relationship } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import { IterableRepository } from './iterable-repository'; -import type { DefaultPaginationParams } from './repository'; export class FollowRequestRepository extends IterableRepository { constructor(private readonly http: Http, readonly config: MastoConfig) { diff --git a/src/repositories/followed-tag-repository.ts b/src/api/v1/repositories/followed-tag-repository.ts similarity index 56% rename from src/repositories/followed-tag-repository.ts rename to src/api/v1/repositories/followed-tag-repository.ts index dfd4a086f..b9e5373cd 100644 --- a/src/repositories/followed-tag-repository.ts +++ b/src/api/v1/repositories/followed-tag-repository.ts @@ -1,10 +1,10 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import { IterableRepository } from '../../iterable-repository'; +import type { DefaultPaginationParams } from '../../repository'; import type { Tag } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import { IterableRepository } from './iterable-repository'; -import type { DefaultPaginationParams } from './repository'; export class FollowedTagRepository extends IterableRepository { constructor(private readonly http: Http, readonly config: MastoConfig) { diff --git a/src/repositories/index.ts b/src/api/v1/repositories/index.ts similarity index 95% rename from src/repositories/index.ts rename to src/api/v1/repositories/index.ts index b63e9891f..b1b2918d0 100644 --- a/src/repositories/index.ts +++ b/src/api/v1/repositories/index.ts @@ -29,8 +29,6 @@ export * from './suggestion-repository'; export * from './timelines-repository'; export * from './trend-repository'; export * from './email-repository'; -export * from './iterable-repository'; -export * from './repository'; export * from './tag-repository'; export * from './followed-tag-repository'; export * as AdminRepositories from './admin'; diff --git a/src/repositories/instance-repository.ts b/src/api/v1/repositories/instance-repository.ts similarity index 84% rename from src/repositories/instance-repository.ts rename to src/api/v1/repositories/instance-repository.ts index a83839d6c..6db71b09a 100644 --- a/src/repositories/instance-repository.ts +++ b/src/api/v1/repositories/instance-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; import type { Activity, Instance } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; export class InstanceRepository implements Repository { constructor(private readonly http: Http, readonly config: MastoConfig) {} diff --git a/src/repositories/list-repository.ts b/src/api/v1/repositories/list-repository.ts similarity index 93% rename from src/repositories/list-repository.ts rename to src/api/v1/repositories/list-repository.ts index f810557c2..7cb759f4a 100644 --- a/src/repositories/list-repository.ts +++ b/src/api/v1/repositories/list-repository.ts @@ -1,9 +1,9 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import type { DefaultPaginationParams, Repository } from '../../repository'; import type { Account, List } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import type { DefaultPaginationParams, Repository } from './repository'; export interface ModifyListParams { /** The title of the list to be created. */ diff --git a/src/repositories/marker-repository.ts b/src/api/v1/repositories/marker-repository.ts similarity index 86% rename from src/repositories/marker-repository.ts rename to src/api/v1/repositories/marker-repository.ts index 929f75138..bade4ff5d 100644 --- a/src/repositories/marker-repository.ts +++ b/src/api/v1/repositories/marker-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; import type { Marker, MarkerItem, MarkerTimeline } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; export interface FetchMarkersParams { /** diff --git a/src/api/v1/repositories/media-attachment-repository.ts b/src/api/v1/repositories/media-attachment-repository.ts new file mode 100644 index 000000000..ca64f3bb9 --- /dev/null +++ b/src/api/v1/repositories/media-attachment-repository.ts @@ -0,0 +1,70 @@ +import type { MastoConfig } from '../../../config'; +import { deprecated, version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; +import type { MediaAttachment } from '../entities'; + +export interface CreateMediaAttachmentParams { + /** The file to be attached, using multipart form data. */ + readonly file: unknown; + /** A plain-text description of the media, for accessibility purposes. */ + readonly description?: string | null; + /** Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0 */ + readonly focus?: string | null; + /** Custom thumbnail */ + readonly thumbnail?: unknown | null; +} + +export type UpdateMediaAttachmentParams = Partial; + +export class MediaAttachmentRepository + implements + Repository< + MediaAttachment, + CreateMediaAttachmentParams, + UpdateMediaAttachmentParams + > +{ + constructor(private readonly http: Http, readonly config: MastoConfig) {} + + /** + * Creates an attachment to be used with a new status. + * @param params Parameters + * @return Attachment + * @see https://docs.joinmastodon.org/methods/statuses/media/ + */ + @deprecated('Use MastoClient.v2.media.create instead') + @version({ since: '0.0.0', until: '3.1.3' }) + create(params: CreateMediaAttachmentParams): Promise { + return this.http.post(`/api/v1/media`, params, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); + } + + /** + * Fetches an attachment to be used with a new status. + * @param id ID of the attachment + * @see https://github.com/tootsuite/mastodon/pull/13210 + */ + @version({ since: '3.1.3' }) + fetch(id: string): Promise { + return this.http.get(`/api/v1/media/${id}`); + } + + /** + * Update an Attachment, before it is attached to a status and posted. + * @param id The id of the Attachment entity to be updated + * @param params Parameters + * @return Attachment + * @see https://docs.joinmastodon.org/methods/statuses/media/ + */ + @version({ since: '0.0.0' }) + update( + id: string, + params: UpdateMediaAttachmentParams, + ): Promise { + return this.http.put(`/api/v1/media/${id}`, params, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); + } +} diff --git a/src/repositories/mutes-repository.ts b/src/api/v1/repositories/mutes-repository.ts similarity index 64% rename from src/repositories/mutes-repository.ts rename to src/api/v1/repositories/mutes-repository.ts index a3a1dfd9a..6acd86004 100644 --- a/src/repositories/mutes-repository.ts +++ b/src/api/v1/repositories/mutes-repository.ts @@ -1,10 +1,10 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import { IterableRepository } from '../../iterable-repository'; +import type { DefaultPaginationParams } from '../../repository'; import type { Account } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import { IterableRepository } from './iterable-repository'; -import type { DefaultPaginationParams } from './repository'; export class MuteRepository extends IterableRepository { constructor(private readonly http: Http, readonly config: MastoConfig) { diff --git a/src/repositories/notification-repository.ts b/src/api/v1/repositories/notification-repository.ts similarity index 87% rename from src/repositories/notification-repository.ts rename to src/api/v1/repositories/notification-repository.ts index a2be87e21..e63114806 100644 --- a/src/repositories/notification-repository.ts +++ b/src/api/v1/repositories/notification-repository.ts @@ -1,10 +1,10 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import { IterableRepository } from '../../iterable-repository'; +import type { DefaultPaginationParams } from '../../repository'; import type { Notification, NotificationType } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import { IterableRepository } from './iterable-repository'; -import type { DefaultPaginationParams } from './repository'; export interface FetchNotificationsParams extends DefaultPaginationParams { /** Instead of specifying every known type to exclude, you can specify only the types you want. */ diff --git a/src/repositories/poll-repository.ts b/src/api/v1/repositories/poll-repository.ts similarity index 83% rename from src/repositories/poll-repository.ts rename to src/api/v1/repositories/poll-repository.ts index 4a29cb618..ee8528c59 100644 --- a/src/repositories/poll-repository.ts +++ b/src/api/v1/repositories/poll-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; import type { Poll } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; export interface VotePollParams { /** Array of own votes containing index for each option (starting from 0) */ diff --git a/src/repositories/preferences-repository.ts b/src/api/v1/repositories/preferences-repository.ts similarity index 72% rename from src/repositories/preferences-repository.ts rename to src/api/v1/repositories/preferences-repository.ts index e0c67177b..e332bb2d4 100644 --- a/src/repositories/preferences-repository.ts +++ b/src/api/v1/repositories/preferences-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; import type { Preference } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; export class PreferenceRepository implements Repository { constructor(private readonly http: Http, readonly config: MastoConfig) {} diff --git a/src/repositories/push-subscription-repository.ts b/src/api/v1/repositories/push-subscription-repository.ts similarity index 76% rename from src/repositories/push-subscription-repository.ts rename to src/api/v1/repositories/push-subscription-repository.ts index 417cd8cb9..f7511a69f 100644 --- a/src/repositories/push-subscription-repository.ts +++ b/src/api/v1/repositories/push-subscription-repository.ts @@ -1,8 +1,11 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; -import type { PushSubscription, PushSubscriptionAlerts } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; +import type { + WebPushSubscription, + WebPushSubscriptionAlerts, +} from '../entities'; export type SubscriptionPolicy = 'all' | 'followed' | 'follower' | 'none'; @@ -19,7 +22,7 @@ export interface CreatePushSubscriptionParams { }; }; readonly data?: { - readonly alerts?: Partial | null; + readonly alerts?: Partial | null; } | null; readonly policy: SubscriptionPolicy; } @@ -32,7 +35,7 @@ export type UpdatePushSubscriptionParams = Pick< export class PushSubscriptionsRepository implements Repository< - PushSubscription, + WebPushSubscription, CreatePushSubscriptionParams, UpdatePushSubscriptionParams > @@ -48,8 +51,8 @@ export class PushSubscriptionsRepository * @see https://docs.joinmastodon.org/methods/notifications/push/ */ @version({ since: '2.4.0' }) - create(params: CreatePushSubscriptionParams): Promise { - return this.http.post( + create(params: CreatePushSubscriptionParams): Promise { + return this.http.post( '/api/v1/push/subscription', params, ); @@ -61,8 +64,8 @@ export class PushSubscriptionsRepository * @see https://docs.joinmastodon.org/methods/notifications/push/ */ @version({ since: '2.4.0' }) - fetch(): Promise { - return this.http.get('/api/v1/push/subscription'); + fetch(): Promise { + return this.http.get('/api/v1/push/subscription'); } /** @@ -72,7 +75,7 @@ export class PushSubscriptionsRepository * @see https://docs.joinmastodon.org/methods/notifications/push/ */ @version({ since: '2.4.0' }) - update(params: UpdatePushSubscriptionParams): Promise { + update(params: UpdatePushSubscriptionParams): Promise { return this.http.put('/api/v1/push/subscription', params); } diff --git a/src/repositories/report-repository.ts b/src/api/v1/repositories/report-repository.ts similarity index 88% rename from src/repositories/report-repository.ts rename to src/api/v1/repositories/report-repository.ts index cfaaf7bf6..8ef44cd39 100644 --- a/src/repositories/report-repository.ts +++ b/src/api/v1/repositories/report-repository.ts @@ -1,6 +1,6 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; -import type { Http } from '../http'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; export type ReportCategory = 'spam' | 'violation' | 'other'; diff --git a/src/repositories/scheduled-statuses-repository.ts b/src/api/v1/repositories/scheduled-statuses-repository.ts similarity index 83% rename from src/repositories/scheduled-statuses-repository.ts rename to src/api/v1/repositories/scheduled-statuses-repository.ts index d84c9ce5c..1bf1950ef 100644 --- a/src/repositories/scheduled-statuses-repository.ts +++ b/src/api/v1/repositories/scheduled-statuses-repository.ts @@ -1,17 +1,21 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import { IterableRepository } from '../../iterable-repository'; +import type { DefaultPaginationParams } from '../../repository'; import type { ScheduledStatus } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import { IterableRepository } from './iterable-repository'; -import type { DefaultPaginationParams } from './repository'; export interface UpdateScheduledStatusParams { /** ISO 8601 Date-time at which the status will be published. Must be at least 5 minutes into the future. */ readonly scheduledAt: string; } -export class ScheduledStatusesRepository extends IterableRepository { +export class ScheduledStatusesRepository extends IterableRepository< + ScheduledStatus, + never, + UpdateScheduledStatusParams +> { constructor(private readonly http: Http, readonly config: MastoConfig) { super(); } diff --git a/src/repositories/status-repository.ts b/src/api/v1/repositories/status-repository.ts similarity index 88% rename from src/repositories/status-repository.ts rename to src/api/v1/repositories/status-repository.ts index cd3383a19..13d6e7b3f 100644 --- a/src/repositories/status-repository.ts +++ b/src/api/v1/repositories/status-repository.ts @@ -1,16 +1,17 @@ -import type { MastoConfig } from '../config'; -import { deprecated, version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { deprecated, version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; import type { Account, Card, Context, + ScheduledStatus, Status, StatusEdit, StatusSource, StatusVisibility, } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; export interface CreateStatusParamsBase { /** ID of the status being replied to, if status is a reply */ @@ -21,12 +22,14 @@ export interface CreateStatusParamsBase { readonly spoilerText?: string | null; /** Visibility of the posted status. Enumerable oneOf public, unlisted, private, direct. */ readonly visibility?: StatusVisibility | null; - /** ISO 8601 Date-time at which to schedule a status. Providing this parameter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future. */ - readonly scheduledAt?: string | null; /** ISO 639 language code for this status. */ readonly language?: string | null; } +export interface CreateStatusExtraParams { + readonly idempotencyKey?: string | null; +} + export interface CreateStatusPollParam { /** Array of possible answers. If provided, `media_ids` cannot be used, and `poll[expires_in]` must be provided. */ readonly options: string[]; @@ -58,6 +61,11 @@ export type CreateStatusParams = | CreateStatusParamsWithStatus | CreateStatusParamsWithMediaIds; +export type CreateScheduledStatusParams = CreateStatusParams & { + /** ISO 8601 Date-time at which to schedule a status. Providing this parameter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future. */ + readonly scheduledAt?: string | null; +}; + export type UpdateStatusParams = CreateStatusParams; export interface ReblogStatusParams { @@ -86,11 +94,22 @@ export class StatusRepository implements Repository { * @return Status. When scheduled_at is present, ScheduledStatus is returned instead. * @see https://docs.joinmastodon.org/api/rest/statuses/#post-api-v1-statuses */ + create( + params: CreateStatusParams, + extra?: CreateStatusExtraParams, + ): Promise; + create( + params: CreateScheduledStatusParams, + extra?: CreateStatusExtraParams, + ): Promise; @version({ since: '0.0.0' }) - create(params: CreateStatusParams, idempotencyKey?: string): Promise { - if (idempotencyKey) { + create( + params: CreateStatusParams | CreateScheduledStatusParams, + extra: CreateStatusExtraParams = {}, + ): Promise { + if (extra.idempotencyKey) { return this.http.post('/api/v1/statuses', params, { - headers: { 'Idempotency-Key': idempotencyKey }, + headers: { 'Idempotency-Key': extra.idempotencyKey }, }); } @@ -275,11 +294,23 @@ export class StatusRepository implements Repository { return this.http.post(`/api/v1/statuses/${id}/unbookmark`); } + /** + * Get all known versions of a status, including the initial and current states. + * @param id The local id of the status in the database + * @returns StatusEdit + * @see https://docs.joinmastodon.org/methods/statuses/#history + */ @version({ since: '3.5.0' }) fetchHistory(id: string): Promise { return this.http.get(`/api/v1/statuses/${id}/history`); } + /** + * Obtain the source properties for a status so that it can be edited. + * @param id The local ID of the Status in the database + * @returns StatusSource + * @see https://docs.joinmastodon.org/methods/statuses/#source + */ @version({ since: '3.5.0' }) fetchSource(id: string): Promise { return this.http.get(`/api/v1/statuses/${id}/source`); diff --git a/src/repositories/stream-repository.ts b/src/api/v1/repositories/stream-repository.ts similarity index 95% rename from src/repositories/stream-repository.ts rename to src/api/v1/repositories/stream-repository.ts index 27fbf9053..90f5d5e88 100644 --- a/src/repositories/stream-repository.ts +++ b/src/api/v1/repositories/stream-repository.ts @@ -1,6 +1,6 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; -import type { Ws, WsEvents } from '../ws'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Ws, WsEvents } from '../../../ws'; export class StreamRepository { constructor(private readonly ws: Ws, readonly config: MastoConfig) {} diff --git a/src/api/v1/repositories/suggestion-repository.ts b/src/api/v1/repositories/suggestion-repository.ts new file mode 100644 index 000000000..112c0b279 --- /dev/null +++ b/src/api/v1/repositories/suggestion-repository.ts @@ -0,0 +1,39 @@ +import type { MastoConfig } from '../../../config'; +import { deprecated, version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; +import type { Account } from '../entities'; + +export interface FetchSuggestionParams { + /** Integer. Maximum number of results to return. Defaults to 40. */ + limit?: number | null; +} + +export class SuggestionRepository + implements Repository +{ + constructor(private readonly http: Http, readonly config: MastoConfig) {} + + /** + * Accounts the user has had past positive interactions with, but is not yet following. + * @param params + * @returns + * @see https://docs.joinmastodon.org/methods/suggestions/#v1 + */ + @deprecated('Use MastoClient.v2.suggestions.fetchAll instead') + @version({ since: '2.4.3' }) + fetchAll(params?: FetchSuggestionParams): Promise { + return this.http.get('/api/v1/suggestions', params); + } + + /** + * Remove an account from follow suggestions. + * @param id id of the account in the database to be removed from suggestions + * @return N/A + * @see https://docs.joinmastodon.org/methods/accounts/suggestions/ + */ + @version({ since: '2.4.3' }) + remove(id: string): Promise { + return this.http.delete(`/api/v1/suggestions/${id}`); + } +} diff --git a/src/repositories/tag-repository.ts b/src/api/v1/repositories/tag-repository.ts similarity index 84% rename from src/repositories/tag-repository.ts rename to src/api/v1/repositories/tag-repository.ts index 8a42c7d2c..105eb68a6 100644 --- a/src/repositories/tag-repository.ts +++ b/src/api/v1/repositories/tag-repository.ts @@ -1,8 +1,8 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; import type { Tag } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; export class TagRepository implements Repository { constructor(private readonly http: Http, readonly config: MastoConfig) {} diff --git a/src/repositories/timelines-repository.ts b/src/api/v1/repositories/timelines-repository.ts similarity index 94% rename from src/repositories/timelines-repository.ts rename to src/api/v1/repositories/timelines-repository.ts index fad597de7..d16439fd9 100644 --- a/src/repositories/timelines-repository.ts +++ b/src/api/v1/repositories/timelines-repository.ts @@ -1,9 +1,9 @@ -import type { MastoConfig } from '../config'; -import { deprecated, version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { deprecated, version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import type { DefaultPaginationParams } from '../../repository'; import type { Status } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import type { DefaultPaginationParams } from './repository'; export interface FetchTimelineParams extends DefaultPaginationParams { /** Show only local statuses? Defaults to false. */ diff --git a/src/repositories/trend-repository.ts b/src/api/v1/repositories/trend-repository.ts similarity index 55% rename from src/repositories/trend-repository.ts rename to src/api/v1/repositories/trend-repository.ts index 0a4223e86..66dca45c9 100644 --- a/src/repositories/trend-repository.ts +++ b/src/api/v1/repositories/trend-repository.ts @@ -1,9 +1,9 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { Paginator } from '../../../paginator'; +import type { DefaultPaginationParams } from '../../repository'; import type { Link, Status, Tag } from '../entities'; -import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import type { DefaultPaginationParams } from './repository'; export interface FetchTrendsParams { /** Maximum number of results to return. Defaults to 10. */ @@ -13,43 +13,37 @@ export interface FetchTrendsParams { export class TrendRepository { constructor(private readonly http: Http, readonly config: MastoConfig) {} + /** + * View trending statuses + * @returns Array of Status + * @see https://docs.joinmastodon.org/methods/trends/#statuses + */ @version({ since: '3.5.0' }) - iterateStatuses( + fetchStatuses( params?: DefaultPaginationParams, ): Paginator { return new Paginator(this.http, '/api/v1/trends/statuses', params); } + /** + * Links that have been shared more than others. + * @see https://docs.joinmastodon.org/methods/trends/#links + */ @version({ since: '3.5.0' }) - iterateLinks( + fetchLinks( params?: DefaultPaginationParams, ): Paginator { return new Paginator(this.http, '/api/v1/trends/links', params); } - get statuses(): Paginator { - return this.iterateStatuses(); - } - - get links(): Paginator { - return this.iterateLinks(); - } - /** * Tags that are being used more frequently within the past week. * @param params Parameters * @return Array of Tag with History - * @see https://docs.joinmastodon.org/methods/instance/trends/ + * @see https://docs.joinmastodon.org/methods/trends/#tags */ @version({ since: '3.0.0' }) fetchTags(params?: FetchTrendsParams): Promise { return this.http.get('/api/v1/trends/tags', params); } - - /** @deprecated Use `fetchTags` */ - fetchAll = this.fetchTags.bind(this); - /** @deprecated Use `iterateStatuses` instead */ - getStatuses = this.iterateStatuses.bind(this); - /** @deprecated Use `iterateStatuses` instead */ - getLinks = this.iterateLinks.bind(this); } diff --git a/src/clients/admin.ts b/src/api/v1/repository-admin.ts similarity index 85% rename from src/clients/admin.ts rename to src/api/v1/repository-admin.ts index 304727b9e..b2947250f 100644 --- a/src/clients/admin.ts +++ b/src/api/v1/repository-admin.ts @@ -1,6 +1,6 @@ -import type { MastoConfig } from '../config'; -import type { Http } from '../http'; -import { AdminRepositories } from '../repositories'; +import type { MastoConfig } from '../../config'; +import type { Http } from '../../http'; +import { AdminRepositories } from './repositories'; export class MastoAdminClient { readonly account: AdminRepositories.AccountRepository; @@ -57,8 +57,3 @@ export class MastoAdminClient { ); } } - -/** - * @deprecated This alias will be removed in v5.0.0 - */ -export const AdminFacadeRepositories = MastoAdminClient; diff --git a/src/api/v1/repository.ts b/src/api/v1/repository.ts new file mode 100644 index 000000000..7cda4ba44 --- /dev/null +++ b/src/api/v1/repository.ts @@ -0,0 +1,154 @@ +import type { MastoConfig } from '../../config'; +import { version } from '../../decorators'; +import type { Http } from '../../http'; +import { Paginator } from '../../paginator'; +import type { Ws } from '../../ws'; +import type { DefaultPaginationParams } from '../repository'; +import type { Search } from './entities'; +import { + AccountRepository, + AnnouncementRepository, + AppRepository, + BlockRepository, + BookmarkRepository, + ConversationRepository, + CustomEmojiRepository, + DirectoryRepository, + DomainBlockRepository, + EmailRepository, + EndorsementRepository, + FavouriteRepository, + FeaturedTagRepository, + FilterRepository, + FollowedTagRepository, + FollowRequestRepository, + InstanceRepository, + ListRepository, + MarkerRepository, + MediaAttachmentRepository, + MuteRepository, + NotificationsRepository, + PollRepository, + PreferenceRepository, + PushSubscriptionsRepository, + ReportRepository, + ScheduledStatusesRepository, + StatusRepository, + StreamRepository, + SuggestionRepository, + TagRepository, + TimelinesRepository, + TrendRepository, +} from './repositories'; +import { MastoAdminClient } from './repository-admin'; + +export type SearchType = 'accounts' | 'hashtags' | 'statuses'; + +export interface SearchParams extends DefaultPaginationParams { + /** Attempt WebFinger lookup. Defaults to false. */ + readonly q: string; + /** Enum(accounts, hashtags, statuses) */ + readonly type?: SearchType | null; + /** Attempt WebFinger look-up */ + readonly resolve?: boolean | null; + /** If provided, statuses returned will be authored only by this account */ + readonly accountId?: string | null; +} + +export class Repository { + readonly admin: MastoAdminClient; + readonly stream: StreamRepository; + readonly accounts: AccountRepository; + readonly announcements: AnnouncementRepository; + readonly apps: AppRepository; + readonly blocks: BlockRepository; + readonly bookmarks: BookmarkRepository; + readonly conversations: ConversationRepository; + readonly customEmojis: CustomEmojiRepository; + readonly directory: DirectoryRepository; + readonly domainBlocks: DomainBlockRepository; + readonly endorsements: EndorsementRepository; + readonly favourites: FavouriteRepository; + readonly featuredTags: FeaturedTagRepository; + readonly filters: FilterRepository; + readonly followRequests: FollowRequestRepository; + readonly instances: InstanceRepository; + readonly lists: ListRepository; + readonly markers: MarkerRepository; + readonly mediaAttachments: MediaAttachmentRepository; + readonly mutes: MuteRepository; + readonly notifications: NotificationsRepository; + readonly poll: PollRepository; + readonly preferences: PreferenceRepository; + readonly pushSubscriptions: PushSubscriptionsRepository; + readonly reports: ReportRepository; + readonly scheduledStatuses: ScheduledStatusesRepository; + readonly statuses: StatusRepository; + readonly suggestions: SuggestionRepository; + readonly timelines: TimelinesRepository; + readonly trends: TrendRepository; + readonly email: EmailRepository; + readonly tags: TagRepository; + readonly followedTags: FollowedTagRepository; + + constructor( + private readonly http: Http, + private readonly ws: Ws, + readonly config: MastoConfig, + ) { + this.admin = new MastoAdminClient(this.http, this.config); + this.stream = new StreamRepository(this.ws, this.config); + this.accounts = new AccountRepository(this.http, this.config); + this.announcements = new AnnouncementRepository(this.http, this.config); + this.apps = new AppRepository(this.http, this.config); + this.blocks = new BlockRepository(this.http, this.config); + this.bookmarks = new BookmarkRepository(this.http, this.config); + this.conversations = new ConversationRepository(this.http, this.config); + this.customEmojis = new CustomEmojiRepository(this.http, this.config); + this.directory = new DirectoryRepository(this.http, this.config); + this.domainBlocks = new DomainBlockRepository(this.http, this.config); + this.endorsements = new EndorsementRepository(this.http, this.config); + this.favourites = new FavouriteRepository(this.http, this.config); + this.featuredTags = new FeaturedTagRepository(this.http, this.config); + this.filters = new FilterRepository(this.http, this.config); + this.followRequests = new FollowRequestRepository(this.http, this.config); + this.instances = new InstanceRepository(this.http, this.config); + this.lists = new ListRepository(this.http, this.config); + this.markers = new MarkerRepository(this.http, this.config); + this.mediaAttachments = new MediaAttachmentRepository( + this.http, + this.config, + ); + this.mutes = new MuteRepository(this.http, this.config); + this.notifications = new NotificationsRepository(this.http, this.config); + this.poll = new PollRepository(this.http, this.config); + this.preferences = new PreferenceRepository(this.http, this.config); + this.pushSubscriptions = new PushSubscriptionsRepository( + this.http, + this.config, + ); + this.reports = new ReportRepository(this.http, this.config); + this.scheduledStatuses = new ScheduledStatusesRepository( + this.http, + this.config, + ); + this.statuses = new StatusRepository(this.http, this.config); + this.suggestions = new SuggestionRepository(this.http, this.config); + this.timelines = new TimelinesRepository(this.http, this.config); + this.trends = new TrendRepository(this.http, this.config); + this.email = new EmailRepository(this.http, this.config); + this.tags = new TagRepository(this.http, this.config); + this.followedTags = new FollowedTagRepository(this.http, this.config); + } + + /** + * Search, but hashtags is an array of strings instead of an array of Tag. + * @param params Parameters + * @return Results + * @see https://docs.joinmastodon.org/methods/search/ + */ + @version({ since: '1.1.0', until: '3.0.0' }) + search(params: SearchParams): Paginator { + return new Paginator(this.http, `/api/v1/search`, params); + } +} diff --git a/src/entities/filter.ts b/src/api/v2/entities/filter.ts similarity index 100% rename from src/entities/filter.ts rename to src/api/v2/entities/filter.ts diff --git a/src/api/v2/entities/index.ts b/src/api/v2/entities/index.ts new file mode 100644 index 000000000..69f4e0695 --- /dev/null +++ b/src/api/v2/entities/index.ts @@ -0,0 +1,2 @@ +export * from './filter'; +export * from './instance'; diff --git a/src/api/v2/entities/instance.ts b/src/api/v2/entities/instance.ts new file mode 100644 index 000000000..2f1b1b09a --- /dev/null +++ b/src/api/v2/entities/instance.ts @@ -0,0 +1,139 @@ +import type { V1 } from '../..'; + +export interface InstanceUsageUsers { + /** The number of active users in the past 4 weeks. */ + activeMonth: number; +} + +export interface InstanceUsage { + /** Usage data related to users on this instance. */ + users: InstanceUsageUsers; +} + +export interface InstanceThumbnailVersions { + /** The URL for the thumbnail image at 1x resolution. */ + '@1x': string; + /** The URL for the thumbnail image at 2x resolution. */ + '@2x': string; +} + +export interface InstanceThumbnail { + /** The URL for the thumbnail image. */ + url: string; + /** A hash computed by [the BlurHash algorithm](https://github.com/woltapp/blurhash), for generating colorful preview thumbnails when media has not been downloaded yet. */ + blurhash: string; + /** Links to scaled resolution images, for high DPI screens. */ + versions: InstanceThumbnailVersions; +} + +export interface InstanceUrls { + /** The WebSockets URL for connecting to the streaming API. */ + streamingApi: string; +} + +export interface InstanceAccountsConfiguration { + /** The maximum number of featured tags allowed for each account. */ + maxFeaturedTags: number; +} + +export interface InstanceStatusesConfiguration { + /** The maximum number of allowed characters per status. */ + maxCharacters: number; + /** The maximum number of media attachments that can be added to a status. */ + maxMediaAttachments: number; + /** Each URL in a status will be assumed to be exactly this many characters. */ + charactersReservedPerUrl: number; +} + +export interface InstanceMediaAttachmentsConfiguration { + /** Contains MIME types that can be uploaded. */ + supportedMimeTypes: string[]; + /** The maximum size of any uploaded image, in bytes. */ + imageSizeLimit: number; + /** The maximum number of pixels (width times height) for image uploads. */ + imageMatrixLimit: number; + /** The maximum size of any uploaded video, in bytes. */ + videoSizeLimit: number; + /** The maximum frame rate for any uploaded video. */ + videoFrameRateLimit: number; + /** The maximum number of pixels (width times height) for video uploads. */ + videoMatrixLimit: number; +} + +export interface InstancePollsConfiguration { + /** Each poll is allowed to have up to this many options. */ + maxOptions: number; + /** Each poll option is allowed to have this many characters. */ + maxCharactersPerOption: number; + /** The shortest allowed poll duration, in seconds. */ + minExpiration: number; + /** The longest allowed poll duration, in seconds. */ + maxExpiration: number; +} + +export interface InstanceTranslationConfiguration { + /** Whether the Translations API is available on this instance. */ + enabled: boolean; +} + +export interface InstanceConfiguration { + /** URLs of interest for clients apps. */ + urls: InstanceUrls; + /** Limits related to accounts. */ + accounts: InstanceAccountsConfiguration; + /** Limits related to authoring statuses. */ + statuses: InstanceStatusesConfiguration; + /** Hints for which attachments will be accepted. */ + mediaAttachments: InstanceMediaAttachmentsConfiguration; + /** Limits related to polls. */ + polls: InstancePollsConfiguration; + /** Hints related to translation. */ + translation: InstanceTranslationConfiguration; +} + +export interface InstanceRegistrations { + /** Whether registrations are enabled. */ + enabled: boolean; + /** Whether registrations require moderator approval. */ + approvalRequired: boolean; + /** A custom message to be shown when registrations are closed. */ + message?: string | null; +} + +export interface InstanceContact { + /** An email address that can be messaged regarding inquiries or issues. */ + email: string; + /** An account that can be contacted natively over the network regarding inquiries or issues. */ + account: V1.Account; +} + +/** + * Represents the software instance of Mastodon running on this domain. + * @see https://docs.joinmastodon.org/entities/Instance/ + */ +export interface Instance { + /** The domain name of the instance. */ + domain: string; + /** The title of the website. */ + title: string; + /** The version of Mastodon installed on the instance. */ + version: string; + /** The URL for the source code of the software running on this instance, in keeping with AGPL license requirements. */ + sourceUrl: string; + /** A short, plain-text description defined by the admin. */ + description: string; + /** Usage data for this instance. */ + usage: InstanceUsage; + /** An image used to represent this instance */ + thumbnail: InstanceThumbnail; + /** Primary languages of the website and its staff. */ + languages: string[]; + /** Configured values and limits for this website. */ + configuration: InstanceConfiguration; + /** Information about registering for this website. */ + registrations: InstanceRegistrations; + /** Hints related to contacting a representative of the website. */ + contact: InstanceContact; + /** An itemized list of rules for this website. */ + rules: V1.Rule[]; +} diff --git a/src/api/v2/entities/search.ts b/src/api/v2/entities/search.ts new file mode 100644 index 000000000..b9b731132 --- /dev/null +++ b/src/api/v2/entities/search.ts @@ -0,0 +1,14 @@ +import type { V1 } from '../..'; + +/** + * Represents the results of a search. + * @see https://docs.joinmastodon.org/entities/Search/ + */ +export interface Search { + /** Accounts which match the given query */ + accounts: V1.Account[]; + /** Statuses which match the given query */ + statuses: V1.Status[]; + /** Hashtags which match the given query */ + hashtags: V1.Tag[]; +} diff --git a/src/api/v2/index.ts b/src/api/v2/index.ts new file mode 100644 index 000000000..e55afa0ed --- /dev/null +++ b/src/api/v2/index.ts @@ -0,0 +1,3 @@ +export * from './entities'; +export * from './repositories'; +export * from './repository'; diff --git a/src/api/v2/repositories/filter-repository.ts b/src/api/v2/repositories/filter-repository.ts new file mode 100644 index 000000000..380688fc5 --- /dev/null +++ b/src/api/v2/repositories/filter-repository.ts @@ -0,0 +1,84 @@ +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; +import type { Filter, FilterContext } from '../entities'; + +export interface CreateFilterParams { + /** Text to be filtered */ + readonly phrase: string; + /** + * Array of enumerable strings `home`, `notifications`, `public`, `thread`. + * At least one context must be specified. + */ + readonly context: FilterContext[] | null; + /** Should the server irreversibly drop matching entities from home and notifications? */ + readonly irreversible?: boolean | null; + /** Consider word boundaries? */ + readonly wholeWord?: boolean | null; + /** ISO 8601 Date-time for when the filter expires. Otherwise, null for a filter that doesn't expire. */ + readonly expiresIn?: number | null; +} + +export type UpdateFilterParams = CreateFilterParams; + +export class FilterRepository + implements Repository +{ + constructor(private readonly http: Http, readonly config: MastoConfig) {} + + /** + * View all filters + * @return Filter + * @see https://docs.joinmastodon.org/methods/accounts/filters/ + */ + @version({ since: '2.4.3' }) + fetchAll(): Promise { + return this.http.get(`/api/v2/filters`); + } + + /** + * View a single filter + * @param id ID of the filter + * @return Returns Filter + * @see https://docs.joinmastodon.org/methods/accounts/filters/ + */ + @version({ since: '2.4.3' }) + fetch(id: string): Promise { + return this.http.get(`/api/v2/filters/${id}`); + } + + /** + * Create a filter + * @param params Parameters + * @return Filter + * @see https://docs.joinmastodon.org/methods/accounts/filters/ + */ + @version({ since: '2.4.3' }) + create(params?: CreateFilterParams): Promise { + return this.http.post(`/api/v2/filters`, params); + } + + /** + * Update a filter + * @param id ID of the filter in the database + * @param params Parameters + * @return Filter + * @see https://docs.joinmastodon.org/methods/accounts/filters/ + */ + @version({ since: '2.4.3' }) + update(id: string, params?: UpdateFilterParams): Promise { + return this.http.put(`/api/v2/filters/${id}`, params); + } + + /** + * Remove a filter + * @param id ID of the filter in the database + * @return N/A + * @see https://docs.joinmastodon.org/methods/accounts/filters/ + */ + @version({ since: '2.4.3' }) + remove(id: string): Promise { + return this.http.delete(`/api/v2/filters/${id}`); + } +} diff --git a/src/api/v2/repositories/index.ts b/src/api/v2/repositories/index.ts new file mode 100644 index 000000000..46db070b5 --- /dev/null +++ b/src/api/v2/repositories/index.ts @@ -0,0 +1,4 @@ +export * from './filter-repository'; +export * from './instance-repository'; +export * from './media-attachment-repository'; +export * from './suggestion-repository'; diff --git a/src/api/v2/repositories/instance-repository.ts b/src/api/v2/repositories/instance-repository.ts new file mode 100644 index 000000000..527c6030f --- /dev/null +++ b/src/api/v2/repositories/instance-repository.ts @@ -0,0 +1,19 @@ +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { Repository } from '../../repository'; +import type { Instance } from '../entities'; + +export class InstanceRepository implements Repository { + constructor(private readonly http: Http, readonly config: MastoConfig) {} + + /** + * Information about the server. + * @return Instance + * @see https://docs.joinmastodon.org/methods/instance/ + */ + @version({ since: '1.0.0' }) + fetch(): Promise { + return this.http.get('/api/v2/instance'); + } +} diff --git a/src/api/v2/repositories/media-attachment-repository.ts b/src/api/v2/repositories/media-attachment-repository.ts new file mode 100644 index 000000000..507b80a5d --- /dev/null +++ b/src/api/v2/repositories/media-attachment-repository.ts @@ -0,0 +1,82 @@ +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import { delay, timeout } from '../../../utils'; +import type { MediaAttachment } from '../../v1'; +import { MediaAttachmentRepository as MediaAttachmentRepositoryV1 } from '../../v1'; + +export interface CreateMediaAttachmentParams { + /** The file to be attached, using multipart form data. */ + readonly file: unknown; + /** A plain-text description of the media, for accessibility purposes. */ + readonly description?: string | null; + /** Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0 */ + readonly focus?: string | null; + /** Custom thumbnail */ + readonly thumbnail?: unknown | null; +} + +export interface CreateMediaAttachmentExtraParams { + /** Wait resolving promise for the media to be uploaded. Defaults to `false` */ + readonly skipPolling?: boolean; +} + +// Repository; + +export class MediaAttachmentRepository { + private readonly v1: MediaAttachmentRepositoryV1; + + constructor(private readonly http: Http, readonly config: MastoConfig) { + this.v1 = new MediaAttachmentRepositoryV1(http, config); + } + + /** + * @experimental + * @param id ID of the media + * @param interval interval of polling + * @returns Media attachment that has done processing + */ + waitFor(id: string, interval = 1000): Promise { + return timeout( + (async () => { + let media: MediaAttachment | undefined; + + while (media == undefined) { + await delay(interval); + const processing = await this.v1.fetch(id); + + if (processing.url != undefined) { + media = processing; + } + } + + return media; + })(), + this.config.timeout, + ); + } + + /** + * Creates an attachment to be used with a new status. + * @param params Parameters + * @return Attachment + * @see https://docs.joinmastodon.org/methods/statuses/media/ + */ + @version({ since: '3.1.3' }) + async create( + params: CreateMediaAttachmentParams, + extra: CreateMediaAttachmentExtraParams = {}, + ): Promise { + const media = await this.http.post( + `/api/v2/media`, + params, + { headers: { 'Content-Type': 'multipart/form-data' } }, + ); + + if (extra.skipPolling) { + return media; + } + + return this.waitFor(media.id); + } +} diff --git a/src/api/v2/repositories/suggestion-repository.ts b/src/api/v2/repositories/suggestion-repository.ts new file mode 100644 index 000000000..c9190ffe1 --- /dev/null +++ b/src/api/v2/repositories/suggestion-repository.ts @@ -0,0 +1,27 @@ +import type { MastoConfig } from '../../../config'; +import { version } from '../../../decorators'; +import type { Http } from '../../../http'; +import type { V1 } from '../..'; +import type { Repository } from '../../repository'; + +export interface FetchSuggestionParams { + /** Integer. Maximum number of results to return. Defaults to 40. */ + limit?: number | null; +} + +export class SuggestionRepository + implements Repository +{ + constructor(private readonly http: Http, readonly config: MastoConfig) {} + + /** + * View follow suggestions. + * Accounts that are promoted by staff, or that the user has had past positive interactions with, but is not yet following. + * @param params + * @returns + */ + @version({ since: '3.4.0' }) + fetchAll(params?: FetchSuggestionParams): Promise { + return this.http.get('/api/v2/suggestions', params); + } +} diff --git a/src/api/v2/repository.ts b/src/api/v2/repository.ts new file mode 100644 index 000000000..64063aa99 --- /dev/null +++ b/src/api/v2/repository.ts @@ -0,0 +1,54 @@ +import type { MastoConfig } from '../../config'; +import { version } from '../../decorators'; +import type { Http } from '../../http'; +import { Paginator } from '../../paginator'; +import type { DefaultPaginationParams } from '../repository'; +import type { Search } from './entities/search'; +import { + FilterRepository, + InstanceRepository, + MediaAttachmentRepository, + SuggestionRepository, +} from './repositories'; + +export type SearchType = 'accounts' | 'hashtags' | 'statuses'; + +export interface SearchParams extends DefaultPaginationParams { + /** Attempt WebFinger lookup. Defaults to false. */ + readonly q: string; + /** Enum(accounts, hashtags, statuses) */ + readonly type?: SearchType | null; + /** Attempt WebFinger look-up */ + readonly resolve?: boolean | null; + /** If provided, statuses returned will be authored only by this account */ + readonly accountId?: string | null; + /** Filter out unreviewed tags? Defaults to false. Use true when trying to find trending tags. */ + readonly excludeUnreviewed?: boolean | null; + /** Only include accounts that the user is following. Defaults to false. */ + readonly following?: boolean | null; +} + +export class Repository { + readonly filters: FilterRepository; + readonly instance: InstanceRepository; + readonly mediaAttachments: MediaAttachmentRepository; + readonly suggestions: SuggestionRepository; + + constructor(readonly http: Http, readonly config: MastoConfig) { + this.filters = new FilterRepository(http, config); + this.instance = new InstanceRepository(http, config); + this.mediaAttachments = new MediaAttachmentRepository(http, config); + this.suggestions = new SuggestionRepository(http, config); + } + + /** + * Perform a search + * @param params Parameters + * @return Results + * @see https://docs.joinmastodon.org/methods/search/ + */ + @version({ since: '1.1.0', until: '3.0.0' }) + search(params: SearchParams): Paginator { + return new Paginator(this.http, `/api/v1/search`, params); + } +} diff --git a/src/clients/index.ts b/src/clients/index.ts index e058d20bf..33bd2723b 100644 --- a/src/clients/index.ts +++ b/src/clients/index.ts @@ -1,2 +1 @@ -export * from './admin'; export * from './masto'; diff --git a/src/clients/masto.ts b/src/clients/masto.ts index 6e5cc01b8..960734ff5 100644 --- a/src/clients/masto.ts +++ b/src/clients/masto.ts @@ -1,163 +1,18 @@ +import { V1, V2 } from '../api'; import type { MastoConfig } from '../config'; -import { version } from '../decorators'; -import type { Results } from '../entities'; import type { Http } from '../http'; -import { Paginator } from '../paginator'; -import type { DefaultPaginationParams } from '../repositories'; -import { - AccountRepository, - AnnouncementRepository, - AppRepository, - BlockRepository, - BookmarkRepository, - ConversationRepository, - CustomEmojiRepository, - DirectoryRepository, - DomainBlockRepository, - EmailRepository, - EndorsementRepository, - FavouriteRepository, - FeaturedTagRepository, - FilterRepository, - FollowedTagRepository, - FollowRequestRepository, - InstanceRepository, - ListRepository, - MarkerRepository, - MediaAttachmentRepository, - MuteRepository, - NotificationsRepository, - PollRepository, - PreferenceRepository, - PushSubscriptionsRepository, - ReportRepository, - ScheduledStatusesRepository, - StatusRepository, - StreamRepository, - SuggestionRepository, - TagRepository, - TimelinesRepository, - TrendRepository, -} from '../repositories'; import type { Ws } from '../ws'; -import { MastoAdminClient } from './admin'; - -export type SearchType = 'accounts' | 'hashtags' | 'statuses'; - -export interface SearchParams extends DefaultPaginationParams { - /** Attempt WebFinger lookup. Defaults to false. */ - readonly q: string; - /** Enum(accounts, hashtags, statuses) */ - readonly type?: SearchType | null; - /** Attempt WebFinger look-up */ - readonly resolve?: boolean | null; - /** If provided, statuses returned will be authored only by this account */ - readonly accountId?: string | null; - /** Filter out unreviewed tags? Defaults to false. Use true when trying to find trending tags. */ - readonly excludeUnreviewed?: boolean | null; - /** Only include accounts that the user is following. Defaults to false. */ - readonly following?: boolean | null; -} export class MastoClient { - readonly admin: MastoAdminClient; - readonly stream: StreamRepository; - readonly accounts: AccountRepository; - readonly announcements: AnnouncementRepository; - readonly apps: AppRepository; - readonly blocks: BlockRepository; - readonly bookmarks: BookmarkRepository; - readonly conversations: ConversationRepository; - readonly customEmojis: CustomEmojiRepository; - readonly directory: DirectoryRepository; - readonly domainBlocks: DomainBlockRepository; - readonly endorsements: EndorsementRepository; - readonly favourites: FavouriteRepository; - readonly featuredTags: FeaturedTagRepository; - readonly filters: FilterRepository; - readonly followRequests: FollowRequestRepository; - readonly instances: InstanceRepository; - readonly lists: ListRepository; - readonly markers: MarkerRepository; - readonly mediaAttachments: MediaAttachmentRepository; - readonly mutes: MuteRepository; - readonly notifications: NotificationsRepository; - readonly poll: PollRepository; - readonly preferences: PreferenceRepository; - readonly pushSubscriptions: PushSubscriptionsRepository; - readonly reports: ReportRepository; - readonly scheduledStatuses: ScheduledStatusesRepository; - readonly statuses: StatusRepository; - readonly suggestions: SuggestionRepository; - readonly timelines: TimelinesRepository; - readonly trends: TrendRepository; - readonly email: EmailRepository; - readonly tags: TagRepository; - readonly followedTags: FollowedTagRepository; + readonly v1: V1.Repository; + readonly v2: V2.Repository; constructor( - private readonly http: Http, - private readonly ws: Ws, + readonly http: Http, + readonly ws: Ws, readonly config: MastoConfig, ) { - this.admin = new MastoAdminClient(this.http, this.config); - this.stream = new StreamRepository(this.ws, this.config); - this.accounts = new AccountRepository(this.http, this.config); - this.announcements = new AnnouncementRepository(this.http, this.config); - this.apps = new AppRepository(this.http, this.config); - this.blocks = new BlockRepository(this.http, this.config); - this.bookmarks = new BookmarkRepository(this.http, this.config); - this.conversations = new ConversationRepository(this.http, this.config); - this.customEmojis = new CustomEmojiRepository(this.http, this.config); - this.directory = new DirectoryRepository(this.http, this.config); - this.domainBlocks = new DomainBlockRepository(this.http, this.config); - this.endorsements = new EndorsementRepository(this.http, this.config); - this.favourites = new FavouriteRepository(this.http, this.config); - this.featuredTags = new FeaturedTagRepository(this.http, this.config); - this.filters = new FilterRepository(this.http, this.config); - this.followRequests = new FollowRequestRepository(this.http, this.config); - this.instances = new InstanceRepository(this.http, this.config); - this.lists = new ListRepository(this.http, this.config); - this.markers = new MarkerRepository(this.http, this.config); - this.mediaAttachments = new MediaAttachmentRepository( - this.http, - this.config, - ); - this.mutes = new MuteRepository(this.http, this.config); - this.notifications = new NotificationsRepository(this.http, this.config); - this.poll = new PollRepository(this.http, this.config); - this.preferences = new PreferenceRepository(this.http, this.config); - this.pushSubscriptions = new PushSubscriptionsRepository( - this.http, - this.config, - ); - this.reports = new ReportRepository(this.http, this.config); - this.scheduledStatuses = new ScheduledStatusesRepository( - this.http, - this.config, - ); - this.statuses = new StatusRepository(this.http, this.config); - this.suggestions = new SuggestionRepository(this.http, this.config); - this.timelines = new TimelinesRepository(this.http, this.config); - this.trends = new TrendRepository(this.http, this.config); - this.email = new EmailRepository(this.http, this.config); - this.tags = new TagRepository(this.http, this.config); - this.followedTags = new FollowedTagRepository(this.http, this.config); - } - - /** - * Search results - * @param params Parameters - * @return Results - * @see https://docs.joinmastodon.org/methods/search/ - */ - @version({ since: '2.4.1' }) - search(params: SearchParams): Paginator { - return new Paginator(this.http, `/api/v2/search`, params); + this.v1 = new V1.Repository(http, ws, config); + this.v2 = new V2.Repository(http, config); } } - -/** - * @deprecated This type alias will be removed in v5.x - */ -export const FacadeRepositories = MastoClient; diff --git a/src/entities/attachment.ts b/src/entities/attachment.ts deleted file mode 100644 index 18e1b1902..000000000 --- a/src/entities/attachment.ts +++ /dev/null @@ -1,69 +0,0 @@ -export type AttachmentType = 'image' | 'video' | 'gifv' | 'audio' | 'unknown'; - -export interface AttachmentMetaImage { - width: number; - height: number; - size: string; - aspect: number; -} - -export interface AttachmentMetaVideo { - width: number; - height: number; - frameRate: string; - duration: number; - bitrate: number; - aspect: number; -} - -export interface AttachmentMetaFocus { - x: number; - y: number; -} - -export interface AttachmentMetaColors { - background: string; - foreground: string; - accent: string; -} - -export interface AttachmentMeta { - small?: AttachmentMetaImage | AttachmentMetaVideo | null; - original?: AttachmentMetaImage | AttachmentMetaVideo | null; - focus?: AttachmentMetaFocus | null; - colors?: AttachmentMetaColors | null; -} - -/** - * Represents a file or media attachment that can be added to a status. - * @see https://docs.joinmastodon.org/entities/attachment/ - */ -export interface Attachment { - /** The ID of the attachment in the database. */ - id: string; - /** The type of the attachment. */ - type: AttachmentType; - /** The location of the original full-size attachment. */ - url?: string | null; - /** The location of a scaled-down preview of the attachment. */ - previewUrl: string; - - /** The location of the full-size original attachment on the remote website. */ - remoteUrl?: string | null; - /** Remote version of previewUrl */ - previewRemoteUrl?: string | null; - /** A shorter URL for the attachment. */ - textUrl?: string | null; - /** Metadata returned by Paperclip. */ - meta?: AttachmentMeta | null; - /** - * Alternate text that describes what is in the media attachment, - * to be used for the visually impaired or when media attachments do not load. - */ - description?: string | null; - /** - * A hash computed by the BlurHash algorithm, - * for generating colorful preview thumbnails when media has not been downloaded yet. - */ - blurhash?: string | null; -} diff --git a/src/entrypoints/nodejs.ts b/src/entrypoints/nodejs.ts index 25efedcab..f83d49e12 100644 --- a/src/entrypoints/nodejs.ts +++ b/src/entrypoints/nodejs.ts @@ -3,11 +3,11 @@ import 'isomorphic-fetch'; import { SemVer } from 'semver'; +import { InstanceRepository } from '../api/v1/repositories'; import { MastoClient } from '../clients'; import type { MastoConfigProps } from '../config'; import { MastoConfig } from '../config'; import { HttpNativeImpl } from '../http'; -import { InstanceRepository } from '../repositories'; import { SerializerNativeImpl } from '../serializers'; import { WsNativeImpl } from '../ws'; @@ -42,11 +42,9 @@ export const login = async ( }; export * from '../decorators'; -export * from '../entities'; +export * from '../api'; export * from '../errors'; export * from '../http'; -export * from '../http/http-axios-impl'; -export * from '../repositories'; export * from '../serializers'; export * from '../ws'; export * from '../clients'; diff --git a/src/repositories/media-attachment-repository.ts b/src/repositories/media-attachment-repository.ts deleted file mode 100644 index bf22c19fb..000000000 --- a/src/repositories/media-attachment-repository.ts +++ /dev/null @@ -1,126 +0,0 @@ -import type { MastoConfig } from '../config'; -import { deprecated, version } from '../decorators'; -import type { Attachment } from '../entities'; -import type { Http } from '../http'; -import { delay, timeout } from '../utils'; -import type { Repository } from './repository'; - -export interface CreateMediaAttachmentParams { - /** The file to be attached, using multipart form data. */ - readonly file: unknown; - /** A plain-text description of the media, for accessibility purposes. */ - readonly description?: string | null; - /** Two floating points (x,y), comma-delimited, ranging from -1.0 to 1.0 */ - readonly focus?: string | null; - /** Custom thumbnail */ - readonly thumbnail?: unknown | null; - - /** Wait resolving promise for the media to be uploaded. Defaults to `false` */ - readonly skipPolling?: boolean; -} - -export type UpdateMediaAttachmentParams = Partial; - -export class MediaAttachmentRepository - implements - Repository< - Attachment, - CreateMediaAttachmentParams, - UpdateMediaAttachmentParams - > -{ - constructor(private readonly http: Http, readonly config: MastoConfig) {} - - /** - * @experimental - * @param id ID of the media - * @param interval interval of polling - * @returns Media attachment that has done processing - */ - waitFor(id: string, interval = 1000): Promise { - return timeout( - (async () => { - let media: Attachment | undefined; - - while (media == undefined) { - await delay(interval); - const processing = await this.fetch(id); - - if (processing.url != undefined) { - media = processing; - } - } - - return media; - })(), - this.config.timeout, - ); - } - - /** - * Creates an attachment to be used with a new status. - * @param params Parameters - * @return Attachment - * @see https://docs.joinmastodon.org/methods/statuses/media/ - */ - @version({ since: '3.1.3' }) - async create({ - skipPolling = false, - ...params - }: CreateMediaAttachmentParams): Promise { - const media = await this.http.post(`/api/v2/media`, params, { - headers: { 'Content-Type': 'multipart/form-data' }, - }); - - if (skipPolling) return media; - return this.waitFor(media.id); - } - - /** - * Fetches an attachment to be used with a new status. - * @param id ID of the attachment - * @see https://github.com/tootsuite/mastodon/pull/13210 - */ - @version({ since: '3.1.3' }) - fetch(id: string): Promise { - return this.http.get(`/api/v1/media/${id}`); - } - - /** - * Update an Attachment, before it is attached to a status and posted. - * @param id The id of the Attachment entity to be updated - * @param params Parameters - * @return Attachment - * @see https://docs.joinmastodon.org/methods/statuses/media/ - */ - @version({ since: '0.0.0' }) - async update( - id: string, - { skipPolling = false, ...params }: UpdateMediaAttachmentParams, - ): Promise { - const media = await this.http.put( - `/api/v1/media/${id}`, - params, - { - headers: { 'Content-Type': 'multipart/form-data' }, - }, - ); - - if (skipPolling) return media; - return this.waitFor(media.id); - } - - /** - * Creates an attachment to be used with a new status. - * @param params Parameters - * @return Attachment - * @see https://docs.joinmastodon.org/methods/statuses/media/ - */ - @deprecated('Use Masto.media#create instead') - @version({ since: '0.0.0', until: '3.1.3' }) - v1__create(params: CreateMediaAttachmentParams): Promise { - return this.http.post(`/api/v1/media`, params, { - headers: { 'Content-Type': 'multipart/form-data' }, - }); - } -} diff --git a/src/repositories/suggestion-repository.ts b/src/repositories/suggestion-repository.ts deleted file mode 100644 index 52fb49328..000000000 --- a/src/repositories/suggestion-repository.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { MastoConfig } from '../config'; -import { version } from '../decorators'; -import type { Suggestion } from '../entities'; -import type { Http } from '../http'; -import type { Repository } from './repository'; - -export interface FetchSuggestionParams { - /** Integer. Maximum number of results to return. Defaults to 40. */ - limit?: number | null; -} - -export class SuggestionRepository - implements Repository -{ - constructor(private readonly http: Http, readonly config: MastoConfig) {} - - /** - * View follow suggestions. - * Accounts that are promoted by staff, or that the user has had past positive interactions with, but is not yet following. - * @param params - * @returns - */ - fetchAll(params?: FetchSuggestionParams): Promise { - return this.http.get('/api/v2/suggestions', params); - } - - /** - * Remove an account from follow suggestions. - * @param id id of the account in the database to be removed from suggestions - * @return N/A - * @see https://docs.joinmastodon.org/methods/accounts/suggestions/ - */ - @version({ since: '2.4.3' }) - remove(id: string): Promise { - return this.http.delete(`/api/v1/suggestions/${id}`); - } -} diff --git a/src/ws/ws.ts b/src/ws/ws.ts index e216a9892..4edc88f2a 100644 --- a/src/ws/ws.ts +++ b/src/ws/ws.ts @@ -1,23 +1,36 @@ import type EventEmitter from 'eventemitter3'; -import type { Conversation, Notification, Status } from '../entities'; +import type { + Announcement, + Conversation, + Notification, + Reaction, + Status, +} from '../api/v1/entities'; -/** Map of event name and callback argument */ +/** + * Map of event name and callback argument + * @see https://docs.joinmastodon.org/methods/streaming/#events + */ export interface EventTypeMap { - /** Status posted */ + /** A new Status has appeared. Payload contains a Status cast to a string. Available since v1.0.0 */ update: [Status]; - /** Status deleted */ + /** A status has been deleted. Payload contains the String ID of the deleted Status. Available since v1.0.0 */ delete: [Status['id']]; - /** User's notification */ + /** A new notification has appeared. Payload contains a Notification cast to a string. Available since v1.4.2 */ notification: [Notification]; - /** User's filter changed */ + /** Keyword filters have been changed. Either does not contain a payload (for WebSocket connections), or contains an undefined payload (for HTTP connections). Available since v2.4.3 */ filters_changed: []; - /** Status added to a conversation */ + /** A direct conversation has been updated. Payload contains a Conversation cast to a string. Available since v2.6.0 */ conversation: [Conversation]; - /** Status updated */ + /** A Status has been edited. Payload contains a Status cast to a string. Available since v3.5.0 */ 'status.update': [Status]; - /** for RxJS' `fromEvent` compatibility */ - [K: string]: unknown[]; + /** An announcement has been published. Payload contains an Announcement cast to a string. Available since v3.1.0 */ + announcement: [Announcement]; + /** An announcement has received an emoji reaction. Payload contains a Hash (with name, count, and announcement_id) cast to a string. Available since v3.1.0 */ + 'announcement.reaction': [Reaction]; + /** An announcement has been deleted. Payload contains the String ID of the deleted Announcement. Available since v3.1.0 */ + 'announcement.delete': [Announcement['id']]; } /** Supported event names */ diff --git a/tests/accounts.spec.ts b/tests/accounts.spec.ts index b2457282f..acd1bf8ab 100644 --- a/tests/accounts.spec.ts +++ b/tests/accounts.spec.ts @@ -12,37 +12,37 @@ describe('account', () => { }); it('verifies credential', async () => { - const me = await client.accounts.verifyCredentials(); + const me = await client.v1.accounts.verifyCredentials(); expect(me.username).not.toBeNull(); }); it('updates credential', async () => { const random = Math.random().toString(); - const me = await client.accounts.updateCredentials({ + const me = await client.v1.accounts.updateCredentials({ displayName: random, }); expect(me.displayName).toBe(random); }); it('fetches an account with ID', async () => { - const me = await client.accounts.verifyCredentials(); - const someone = await client.accounts.fetch(me.id); + const me = await client.v1.accounts.verifyCredentials(); + const someone = await client.v1.accounts.fetch(me.id); expect(me.id).toBe(someone.id); }); it('follows / unfollow by ID', async () => { - let relationship = await client.accounts.follow(TARGET_ID); + let relationship = await client.v1.accounts.follow(TARGET_ID); expect(relationship.following).toBe(true); - relationship = await client.accounts.unfollow(TARGET_ID); + relationship = await client.v1.accounts.unfollow(TARGET_ID); expect(relationship.following).toBe(false); }); it('blocks / unblock by ID', async () => { - let relationship = await client.accounts.block(TARGET_ID); + let relationship = await client.v1.accounts.block(TARGET_ID); expect(relationship.blocking).toBe(true); - relationship = await client.accounts.unblock(TARGET_ID); + relationship = await client.v1.accounts.unblock(TARGET_ID); expect(relationship.blocking).toBe(false); }); @@ -57,23 +57,23 @@ describe('account', () => { // }); it('mutes / unmute by ID', async () => { - let relationship = await client.accounts.mute(TARGET_ID); + let relationship = await client.v1.accounts.mute(TARGET_ID); expect(relationship.muting).toBe(true); - relationship = await client.accounts.unmute(TARGET_ID); + relationship = await client.v1.accounts.unmute(TARGET_ID); expect(relationship.muting).toBe(false); }); it('creates a note', async () => { const comment = Math.random().toString(); - const relationship = await client.accounts.createNote(TARGET_ID, { + const relationship = await client.v1.accounts.createNote(TARGET_ID, { comment, }); expect(relationship.note).toBe(comment); }); it('excludes replies from iterateStatuses', async () => { - const statuses = await client.accounts + const statuses = await client.v1.accounts .iterateStatuses(TARGET_ID, { excludeReplies: true, }) diff --git a/tests/followed-tags.spec.ts b/tests/followed-tags.spec.ts index 08a01de7b..180be2309 100644 --- a/tests/followed-tags.spec.ts +++ b/tests/followed-tags.spec.ts @@ -5,16 +5,16 @@ import { login } from '../test-utils/login'; describe('FollowedTag', () => { it('lists following tags', async () => { const masto = await login(); - let tag = await masto.tags.follow('mastodon'); + let tag = await masto.v1.tags.follow('mastodon'); expect(tag.following).toBe(true); - const tags = await masto.followedTags.fetchMany(); + const tags = await masto.v1.followedTags.fetchMany(); assert(tags.done === false); expect(tags.value).toHaveLength(1); expect(tags.value[0].name).toBe('mastodon'); - await masto.tags.unfollow('mastodon'); - tag = await masto.tags.fetch('mastodon'); + await masto.v1.tags.unfollow('mastodon'); + tag = await masto.v1.tags.fetch('mastodon'); expect(tag.following).toBe(false); }); diff --git a/tests/media.spec.ts b/tests/media.spec.ts index d034a2db5..d0ed71107 100644 --- a/tests/media.spec.ts +++ b/tests/media.spec.ts @@ -1,5 +1,5 @@ -import fs from 'fs'; -import path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; import type { MastoClient } from '../src/clients'; import { login } from '../test-utils/login'; @@ -12,7 +12,7 @@ describe('account', () => { }); it('creates a media attachment', async () => { - const media = await client.mediaAttachments.create({ + const media = await client.v1.mediaAttachments.create({ file: fs.createReadStream(path.join(__dirname, './image.png')), }); diff --git a/tests/statuses.spec.ts b/tests/statuses.spec.ts index 5bc4e7413..dcfb428fc 100644 --- a/tests/statuses.spec.ts +++ b/tests/statuses.spec.ts @@ -10,94 +10,94 @@ describe('account', () => { it('creates, updates, and removes a status', async () => { const random = Math.random().toString(); - const { id } = await client.statuses.create({ + const { id } = await client.v1.statuses.create({ status: random, visibility: 'direct', }); - let status = await client.statuses.fetch(id); + let status = await client.v1.statuses.fetch(id); expect(status.content).toBe(`

${random}

`); const random2 = Math.random().toString(); - status = await client.statuses.update(id, { status: random2 }); + status = await client.v1.statuses.update(id, { status: random2 }); expect(status.content).toBe(`

${random2}

`); - await client.statuses.remove(id); - await expect(client.statuses.fetch(id)).rejects.toThrow(); + await client.v1.statuses.remove(id); + await expect(client.v1.statuses.fetch(id)).rejects.toThrow(); }); it('favourites and unfavourites a status', async () => { - let status = await client.statuses.create({ + let status = await client.v1.statuses.create({ status: 'status', visibility: 'direct', }); - status = await client.statuses.favourite(status.id); + status = await client.v1.statuses.favourite(status.id); expect(status.favourited).toBe(true); - status = await client.statuses.unfavourite(status.id); + status = await client.v1.statuses.unfavourite(status.id); expect(status.favourited).toBe(false); - await client.statuses.remove(status.id); + await client.v1.statuses.remove(status.id); }); it('mutes and unmute a status', async () => { - let status = await client.statuses.create({ + let status = await client.v1.statuses.create({ status: 'status', visibility: 'direct', }); - status = await client.statuses.mute(status.id); + status = await client.v1.statuses.mute(status.id); expect(status.muted).toBe(true); - status = await client.statuses.unmute(status.id); + status = await client.v1.statuses.unmute(status.id); expect(status.muted).toBe(false); - await client.statuses.remove(status.id); + await client.v1.statuses.remove(status.id); }); it('reblogs and unreblog a status', async () => { - let status = await client.statuses.create({ + let status = await client.v1.statuses.create({ status: 'status', visibility: 'private', }); - status = await client.statuses.reblog(status.id); + status = await client.v1.statuses.reblog(status.id); expect(status.reblogged).toBe(true); - status = await client.statuses.unreblog(status.id); + status = await client.v1.statuses.unreblog(status.id); expect(status.reblogged).toBe(false); - await client.statuses.remove(status.id); + await client.v1.statuses.remove(status.id); }); it('pins and unpin a status', async () => { - let status = await client.statuses.create({ + let status = await client.v1.statuses.create({ status: 'status', visibility: 'private', }); - status = await client.statuses.pin(status.id); + status = await client.v1.statuses.pin(status.id); expect(status.pinned).toBe(true); - status = await client.statuses.unpin(status.id); + status = await client.v1.statuses.unpin(status.id); expect(status.pinned).toBe(false); - await client.statuses.remove(status.id); + await client.v1.statuses.remove(status.id); }); it('bookmarks and unbookmark a status', async () => { - let status = await client.statuses.create({ + let status = await client.v1.statuses.create({ status: 'status', visibility: 'direct', }); - status = await client.statuses.bookmark(status.id); + status = await client.v1.statuses.bookmark(status.id); expect(status.bookmarked).toBe(true); - status = await client.statuses.unbookmark(status.id); + status = await client.v1.statuses.unbookmark(status.id); expect(status.bookmarked).toBe(false); - await client.statuses.remove(status.id); + await client.v1.statuses.remove(status.id); }); }); diff --git a/tests/suggestions.spec.ts b/tests/suggestions.spec.ts index c4c09bfb1..5739c1c6f 100644 --- a/tests/suggestions.spec.ts +++ b/tests/suggestions.spec.ts @@ -3,7 +3,7 @@ import { login } from '../test-utils/login'; describe('Suggestions', () => { it('fetches suggestions', async () => { const masto = await login(); - const all = await masto.suggestions.fetchAll(); + const all = await masto.v1.suggestions.fetchAll(); expect(all).toEqual(expect.any(Array)); }); });