diff --git a/packages/megalodon/package.json b/packages/megalodon/package.json index 43479b2e7..bd1b80c1d 100644 --- a/packages/megalodon/package.json +++ b/packages/megalodon/package.json @@ -5,7 +5,8 @@ "typings": "./lib/src/index.d.ts", "scripts": { "build": "tsc -p ./", - "lint": "eslint --ext .js,.ts src", + "lint": "pnpm rome check --apply src/**/*.ts", + "format": "pnpm rome format --write src/**/*.ts", "doc": "typedoc --out ../docs ./src", "test": "NODE_ENV=test jest -u --maxWorkers=3" }, diff --git a/packages/megalodon/src/entities/account.ts b/packages/megalodon/src/entities/account.ts index 77f0e71d5..06a85eb98 100644 --- a/packages/megalodon/src/entities/account.ts +++ b/packages/megalodon/src/entities/account.ts @@ -2,26 +2,26 @@ /// /// namespace Entity { - export type Account = { - id: string - username: string - acct: string - display_name: string - locked: boolean - created_at: string - followers_count: number - following_count: number - statuses_count: number - note: string - url: string - avatar: string - avatar_static: string - header: string - header_static: string - emojis: Array - moved: Account | null - fields: Array - bot: boolean | null - source?: Source - } + export type Account = { + id: string; + username: string; + acct: string; + display_name: string; + locked: boolean; + created_at: string; + followers_count: number; + following_count: number; + statuses_count: number; + note: string; + url: string; + avatar: string; + avatar_static: string; + header: string; + header_static: string; + emojis: Array; + moved: Account | null; + fields: Array; + bot: boolean | null; + source?: Source; + }; } diff --git a/packages/megalodon/src/entities/activity.ts b/packages/megalodon/src/entities/activity.ts index 2494916a9..6bc0b6d80 100644 --- a/packages/megalodon/src/entities/activity.ts +++ b/packages/megalodon/src/entities/activity.ts @@ -1,8 +1,8 @@ namespace Entity { - export type Activity = { - week: string - statuses: string - logins: string - registrations: string - } + export type Activity = { + week: string; + statuses: string; + logins: string; + registrations: string; + }; } diff --git a/packages/megalodon/src/entities/announcement.ts b/packages/megalodon/src/entities/announcement.ts index 00fa8a04b..7c7983163 100644 --- a/packages/megalodon/src/entities/announcement.ts +++ b/packages/megalodon/src/entities/announcement.ts @@ -3,32 +3,32 @@ /// namespace Entity { - export type Announcement = { - id: string - content: string - starts_at: string | null - ends_at: string | null - published: boolean - all_day: boolean - published_at: string - updated_at: string - read?: boolean - mentions: Array - statuses: Array - tags: Array - emojis: Array - reactions: Array - } + export type Announcement = { + id: string; + content: string; + starts_at: string | null; + ends_at: string | null; + published: boolean; + all_day: boolean; + published_at: string; + updated_at: string; + read?: boolean; + mentions: Array; + statuses: Array; + tags: Array; + emojis: Array; + reactions: Array; + }; - export type AnnouncementAccount = { - id: string - username: string - url: string - acct: string - } + export type AnnouncementAccount = { + id: string; + username: string; + url: string; + acct: string; + }; - export type AnnouncementStatus = { - id: string - url: string - } + export type AnnouncementStatus = { + id: string; + url: string; + }; } diff --git a/packages/megalodon/src/entities/application.ts b/packages/megalodon/src/entities/application.ts index 3af64fcf9..9b98b1277 100644 --- a/packages/megalodon/src/entities/application.ts +++ b/packages/megalodon/src/entities/application.ts @@ -1,7 +1,7 @@ namespace Entity { - export type Application = { - name: string - website?: string | null - vapid_key?: string | null - } + export type Application = { + name: string; + website?: string | null; + vapid_key?: string | null; + }; } diff --git a/packages/megalodon/src/entities/async_attachment.ts b/packages/megalodon/src/entities/async_attachment.ts index b383f90c5..9cc17acc5 100644 --- a/packages/megalodon/src/entities/async_attachment.ts +++ b/packages/megalodon/src/entities/async_attachment.ts @@ -1,14 +1,14 @@ /// namespace Entity { - export type AsyncAttachment = { - id: string - type: 'unknown' | 'image' | 'gifv' | 'video' | 'audio' - url: string | null - remote_url: string | null - preview_url: string - text_url: string | null - meta: Meta | null - description: string | null - blurhash: string | null - } + export type AsyncAttachment = { + id: string; + type: "unknown" | "image" | "gifv" | "video" | "audio"; + url: string | null; + remote_url: string | null; + preview_url: string; + text_url: string | null; + meta: Meta | null; + description: string | null; + blurhash: string | null; + }; } diff --git a/packages/megalodon/src/entities/attachment.ts b/packages/megalodon/src/entities/attachment.ts index aab1deade..082c79edd 100644 --- a/packages/megalodon/src/entities/attachment.ts +++ b/packages/megalodon/src/entities/attachment.ts @@ -1,49 +1,49 @@ namespace Entity { - export type Sub = { - // For Image, Gifv, and Video - width?: number - height?: number - size?: string - aspect?: number + export type Sub = { + // For Image, Gifv, and Video + width?: number; + height?: number; + size?: string; + aspect?: number; - // For Gifv and Video - frame_rate?: string + // For Gifv and Video + frame_rate?: string; - // For Audio, Gifv, and Video - duration?: number - bitrate?: number - } + // For Audio, Gifv, and Video + duration?: number; + bitrate?: number; + }; - export type Focus = { - x: number - y: number - } + export type Focus = { + x: number; + y: number; + }; - export type Meta = { - original?: Sub - small?: Sub - focus?: Focus - length?: string - duration?: number - fps?: number - size?: string - width?: number - height?: number - aspect?: number - audio_encode?: string - audio_bitrate?: string - audio_channel?: string - } + export type Meta = { + original?: Sub; + small?: Sub; + focus?: Focus; + length?: string; + duration?: number; + fps?: number; + size?: string; + width?: number; + height?: number; + aspect?: number; + audio_encode?: string; + audio_bitrate?: string; + audio_channel?: string; + }; - export type Attachment = { - id: string - type: 'unknown' | 'image' | 'gifv' | 'video' | 'audio' - url: string - remote_url: string | null - preview_url: string | null - text_url: string | null - meta: Meta | null - description: string | null - blurhash: string | null - } + export type Attachment = { + id: string; + type: "unknown" | "image" | "gifv" | "video" | "audio"; + url: string; + remote_url: string | null; + preview_url: string | null; + text_url: string | null; + meta: Meta | null; + description: string | null; + blurhash: string | null; + }; } diff --git a/packages/megalodon/src/entities/card.ts b/packages/megalodon/src/entities/card.ts index b39cbb8f2..356d99aee 100644 --- a/packages/megalodon/src/entities/card.ts +++ b/packages/megalodon/src/entities/card.ts @@ -1,16 +1,16 @@ namespace Entity { - export type Card = { - url: string - title: string - description: string - type: 'link' | 'photo' | 'video' | 'rich' - image?: string - author_name?: string - author_url?: string - provider_name?: string - provider_url?: string - html?: string - width?: number - height?: number - } + export type Card = { + url: string; + title: string; + description: string; + type: "link" | "photo" | "video" | "rich"; + image?: string; + author_name?: string; + author_url?: string; + provider_name?: string; + provider_url?: string; + html?: string; + width?: number; + height?: number; + }; } diff --git a/packages/megalodon/src/entities/context.ts b/packages/megalodon/src/entities/context.ts index 3f2eda58f..a794a7c5a 100644 --- a/packages/megalodon/src/entities/context.ts +++ b/packages/megalodon/src/entities/context.ts @@ -1,8 +1,8 @@ /// namespace Entity { - export type Context = { - ancestors: Array - descendants: Array - } + export type Context = { + ancestors: Array; + descendants: Array; + }; } diff --git a/packages/megalodon/src/entities/conversation.ts b/packages/megalodon/src/entities/conversation.ts index cdadf1e0f..2bdc19666 100644 --- a/packages/megalodon/src/entities/conversation.ts +++ b/packages/megalodon/src/entities/conversation.ts @@ -2,10 +2,10 @@ /// namespace Entity { - export type Conversation = { - id: string - accounts: Array - last_status: Status | null - unread: boolean - } + export type Conversation = { + id: string; + accounts: Array; + last_status: Status | null; + unread: boolean; + }; } diff --git a/packages/megalodon/src/entities/emoji.ts b/packages/megalodon/src/entities/emoji.ts index c2bc5a1ef..10c32ab0b 100644 --- a/packages/megalodon/src/entities/emoji.ts +++ b/packages/megalodon/src/entities/emoji.ts @@ -1,9 +1,9 @@ namespace Entity { - export type Emoji = { - shortcode: string - static_url: string - url: string - visible_in_picker: boolean - category: string - } + export type Emoji = { + shortcode: string; + static_url: string; + url: string; + visible_in_picker: boolean; + category: string; + }; } diff --git a/packages/megalodon/src/entities/featured_tag.ts b/packages/megalodon/src/entities/featured_tag.ts index 06ae6d7a9..fc9f8c69c 100644 --- a/packages/megalodon/src/entities/featured_tag.ts +++ b/packages/megalodon/src/entities/featured_tag.ts @@ -1,8 +1,8 @@ namespace Entity { - export type FeaturedTag = { - id: string - name: string - statuses_count: number - last_status_at: string - } + export type FeaturedTag = { + id: string; + name: string; + statuses_count: number; + last_status_at: string; + }; } diff --git a/packages/megalodon/src/entities/field.ts b/packages/megalodon/src/entities/field.ts index 03e4604b0..de4b6b2b7 100644 --- a/packages/megalodon/src/entities/field.ts +++ b/packages/megalodon/src/entities/field.ts @@ -1,7 +1,7 @@ namespace Entity { - export type Field = { - name: string - value: string - verified_at: string | null - } + export type Field = { + name: string; + value: string; + verified_at: string | null; + }; } diff --git a/packages/megalodon/src/entities/filter.ts b/packages/megalodon/src/entities/filter.ts index ffbacb728..55b7305cc 100644 --- a/packages/megalodon/src/entities/filter.ts +++ b/packages/megalodon/src/entities/filter.ts @@ -1,12 +1,12 @@ namespace Entity { - export type Filter = { - id: string - phrase: string - context: Array - expires_at: string | null - irreversible: boolean - whole_word: boolean - } + export type Filter = { + id: string; + phrase: string; + context: Array; + expires_at: string | null; + irreversible: boolean; + whole_word: boolean; + }; - export type FilterContext = string + export type FilterContext = string; } diff --git a/packages/megalodon/src/entities/history.ts b/packages/megalodon/src/entities/history.ts index 070969426..4676357d6 100644 --- a/packages/megalodon/src/entities/history.ts +++ b/packages/megalodon/src/entities/history.ts @@ -1,7 +1,7 @@ namespace Entity { - export type History = { - day: string - uses: number - accounts: number - } + export type History = { + day: string; + uses: number; + accounts: number; + }; } diff --git a/packages/megalodon/src/entities/identity_proof.ts b/packages/megalodon/src/entities/identity_proof.ts index ff857addb..3b42e6f41 100644 --- a/packages/megalodon/src/entities/identity_proof.ts +++ b/packages/megalodon/src/entities/identity_proof.ts @@ -1,9 +1,9 @@ namespace Entity { - export type IdentityProof = { - provider: string - provider_username: string - updated_at: string - proof_url: string - profile_url: string - } + export type IdentityProof = { + provider: string; + provider_username: string; + updated_at: string; + proof_url: string; + profile_url: string; + }; } diff --git a/packages/megalodon/src/entities/instance.ts b/packages/megalodon/src/entities/instance.ts index e0aaf99b1..9c0f572db 100644 --- a/packages/megalodon/src/entities/instance.ts +++ b/packages/megalodon/src/entities/instance.ts @@ -3,39 +3,39 @@ /// namespace Entity { - export type Instance = { - uri: string - title: string - description: string - email: string - version: string - thumbnail: string | null - urls: URLs - stats: Stats - languages: Array - contact_account: Account | null - max_toot_chars?: number - registrations?: boolean - configuration?: { - statuses: { - max_characters: number - max_media_attachments: number - characters_reserved_per_url: number - } - media_attachments: { - supported_mime_types: Array - image_size_limit: number - image_matrix_limit: number - video_size_limit: number - video_frame_limit: number - video_matrix_limit: number - } - polls: { - max_options: number - max_characters_per_option: number - min_expiration: number - max_expiration: number - } - } - } + export type Instance = { + uri: string; + title: string; + description: string; + email: string; + version: string; + thumbnail: string | null; + urls: URLs; + stats: Stats; + languages: Array; + contact_account: Account | null; + max_toot_chars?: number; + registrations?: boolean; + configuration?: { + statuses: { + max_characters: number; + max_media_attachments: number; + characters_reserved_per_url: number; + }; + media_attachments: { + supported_mime_types: Array; + image_size_limit: number; + image_matrix_limit: number; + video_size_limit: number; + video_frame_limit: number; + video_matrix_limit: number; + }; + polls: { + max_options: number; + max_characters_per_option: number; + min_expiration: number; + max_expiration: number; + }; + }; + }; } diff --git a/packages/megalodon/src/entities/list.ts b/packages/megalodon/src/entities/list.ts index 2cee0db3c..97e75286b 100644 --- a/packages/megalodon/src/entities/list.ts +++ b/packages/megalodon/src/entities/list.ts @@ -1,6 +1,6 @@ namespace Entity { - export type List = { - id: string - title: string - } + export type List = { + id: string; + title: string; + }; } diff --git a/packages/megalodon/src/entities/marker.ts b/packages/megalodon/src/entities/marker.ts index 33cb98a10..7ee99282c 100644 --- a/packages/megalodon/src/entities/marker.ts +++ b/packages/megalodon/src/entities/marker.ts @@ -1,15 +1,15 @@ namespace Entity { - export type Marker = { - home?: { - last_read_id: string - version: number - updated_at: string - } - notifications?: { - last_read_id: string - version: number - updated_at: string - unread_count?: number - } - } + export type Marker = { + home?: { + last_read_id: string; + version: number; + updated_at: string; + }; + notifications?: { + last_read_id: string; + version: number; + updated_at: string; + unread_count?: number; + }; + }; } diff --git a/packages/megalodon/src/entities/mention.ts b/packages/megalodon/src/entities/mention.ts index 046912971..4fe36a655 100644 --- a/packages/megalodon/src/entities/mention.ts +++ b/packages/megalodon/src/entities/mention.ts @@ -1,8 +1,8 @@ namespace Entity { - export type Mention = { - id: string - username: string - url: string - acct: string - } + export type Mention = { + id: string; + username: string; + url: string; + acct: string; + }; } diff --git a/packages/megalodon/src/entities/notification.ts b/packages/megalodon/src/entities/notification.ts index d42dfe375..fae32c795 100644 --- a/packages/megalodon/src/entities/notification.ts +++ b/packages/megalodon/src/entities/notification.ts @@ -2,14 +2,14 @@ /// namespace Entity { - export type Notification = { - account: Account - created_at: string - id: string - status?: Status - emoji?: string - type: NotificationType - } + export type Notification = { + account: Account; + created_at: string; + id: string; + status?: Status; + emoji?: string; + type: NotificationType; + }; - export type NotificationType = string + export type NotificationType = string; } diff --git a/packages/megalodon/src/entities/poll.ts b/packages/megalodon/src/entities/poll.ts index c4f8f4f6d..2539d68b2 100644 --- a/packages/megalodon/src/entities/poll.ts +++ b/packages/megalodon/src/entities/poll.ts @@ -1,14 +1,14 @@ /// namespace Entity { - export type Poll = { - id: string - expires_at: string | null - expired: boolean - multiple: boolean - votes_count: number - options: Array - voted: boolean - own_votes: Array - } + export type Poll = { + id: string; + expires_at: string | null; + expired: boolean; + multiple: boolean; + votes_count: number; + options: Array; + voted: boolean; + own_votes: Array; + }; } diff --git a/packages/megalodon/src/entities/poll_option.ts b/packages/megalodon/src/entities/poll_option.ts index ae4c63849..e818a8607 100644 --- a/packages/megalodon/src/entities/poll_option.ts +++ b/packages/megalodon/src/entities/poll_option.ts @@ -1,6 +1,6 @@ namespace Entity { - export type PollOption = { - title: string - votes_count: number | null - } + export type PollOption = { + title: string; + votes_count: number | null; + }; } diff --git a/packages/megalodon/src/entities/preferences.ts b/packages/megalodon/src/entities/preferences.ts index cb5797c4c..7994dc568 100644 --- a/packages/megalodon/src/entities/preferences.ts +++ b/packages/megalodon/src/entities/preferences.ts @@ -1,9 +1,9 @@ namespace Entity { - export type Preferences = { - 'posting:default:visibility': 'public' | 'unlisted' | 'private' | 'direct' - 'posting:default:sensitive': boolean - 'posting:default:language': string | null - 'reading:expand:media': 'default' | 'show_all' | 'hide_all' - 'reading:expand:spoilers': boolean - } + export type Preferences = { + "posting:default:visibility": "public" | "unlisted" | "private" | "direct"; + "posting:default:sensitive": boolean; + "posting:default:language": string | null; + "reading:expand:media": "default" | "show_all" | "hide_all"; + "reading:expand:spoilers": boolean; + }; } diff --git a/packages/megalodon/src/entities/push_subscription.ts b/packages/megalodon/src/entities/push_subscription.ts index fe7464e8e..ad1146a24 100644 --- a/packages/megalodon/src/entities/push_subscription.ts +++ b/packages/megalodon/src/entities/push_subscription.ts @@ -1,16 +1,16 @@ namespace Entity { - export type Alerts = { - follow: boolean - favourite: boolean - mention: boolean - reblog: boolean - poll: boolean - } + export type Alerts = { + follow: boolean; + favourite: boolean; + mention: boolean; + reblog: boolean; + poll: boolean; + }; - export type PushSubscription = { - id: string - endpoint: string - server_key: string - alerts: Alerts - } + export type PushSubscription = { + id: string; + endpoint: string; + server_key: string; + alerts: Alerts; + }; } diff --git a/packages/megalodon/src/entities/reaction.ts b/packages/megalodon/src/entities/reaction.ts index ccdc2d26a..2ad4f9d3a 100644 --- a/packages/megalodon/src/entities/reaction.ts +++ b/packages/megalodon/src/entities/reaction.ts @@ -1,11 +1,11 @@ /// namespace Entity { - export type Reaction = { - count: number - me: boolean - name: string - url?: string - accounts?: Array - } + export type Reaction = { + count: number; + me: boolean; + name: string; + url?: string; + accounts?: Array; + }; } diff --git a/packages/megalodon/src/entities/relationship.ts b/packages/megalodon/src/entities/relationship.ts index 5f10b9c98..91802d5c8 100644 --- a/packages/megalodon/src/entities/relationship.ts +++ b/packages/megalodon/src/entities/relationship.ts @@ -1,17 +1,17 @@ namespace Entity { - export type Relationship = { - id: string - following: boolean - followed_by: boolean - delivery_following?: boolean - blocking: boolean - blocked_by: boolean - muting: boolean - muting_notifications: boolean - requested: boolean - domain_blocking: boolean - showing_reblogs: boolean - endorsed: boolean - notifying: boolean - } + export type Relationship = { + id: string; + following: boolean; + followed_by: boolean; + delivery_following?: boolean; + blocking: boolean; + blocked_by: boolean; + muting: boolean; + muting_notifications: boolean; + requested: boolean; + domain_blocking: boolean; + showing_reblogs: boolean; + endorsed: boolean; + notifying: boolean; + }; } diff --git a/packages/megalodon/src/entities/report.ts b/packages/megalodon/src/entities/report.ts index 28f029981..6862a5fab 100644 --- a/packages/megalodon/src/entities/report.ts +++ b/packages/megalodon/src/entities/report.ts @@ -1,9 +1,9 @@ namespace Entity { - export type Report = { - id: string - action_taken: string - comment: string - account_id: string - status_ids: Array - } + export type Report = { + id: string; + action_taken: string; + comment: string; + account_id: string; + status_ids: Array; + }; } diff --git a/packages/megalodon/src/entities/results.ts b/packages/megalodon/src/entities/results.ts index fe168de67..4448e5335 100644 --- a/packages/megalodon/src/entities/results.ts +++ b/packages/megalodon/src/entities/results.ts @@ -3,9 +3,9 @@ /// namespace Entity { - export type Results = { - accounts: Array - statuses: Array - hashtags: Array - } + export type Results = { + accounts: Array; + statuses: Array; + hashtags: Array; + }; } diff --git a/packages/megalodon/src/entities/scheduled_status.ts b/packages/megalodon/src/entities/scheduled_status.ts index fb6f63f10..78dfb8ed2 100644 --- a/packages/megalodon/src/entities/scheduled_status.ts +++ b/packages/megalodon/src/entities/scheduled_status.ts @@ -1,10 +1,10 @@ /// /// namespace Entity { - export type ScheduledStatus = { - id: string - scheduled_at: string - params: StatusParams - media_attachments: Array - } + export type ScheduledStatus = { + id: string; + scheduled_at: string; + params: StatusParams; + media_attachments: Array; + }; } diff --git a/packages/megalodon/src/entities/source.ts b/packages/megalodon/src/entities/source.ts index d87cf55d8..913b02fda 100644 --- a/packages/megalodon/src/entities/source.ts +++ b/packages/megalodon/src/entities/source.ts @@ -1,10 +1,10 @@ /// namespace Entity { - export type Source = { - privacy: string | null - sensitive: boolean | null - language: string | null - note: string - fields: Array - } + export type Source = { + privacy: string | null; + sensitive: boolean | null; + language: string | null; + note: string; + fields: Array; + }; } diff --git a/packages/megalodon/src/entities/stats.ts b/packages/megalodon/src/entities/stats.ts index 76f0bad34..6471df039 100644 --- a/packages/megalodon/src/entities/stats.ts +++ b/packages/megalodon/src/entities/stats.ts @@ -1,7 +1,7 @@ namespace Entity { - export type Stats = { - user_count: number - status_count: number - domain_count: number - } + export type Stats = { + user_count: number; + status_count: number; + domain_count: number; + }; } diff --git a/packages/megalodon/src/entities/status.ts b/packages/megalodon/src/entities/status.ts index 7fd72e20c..8c8148fd5 100644 --- a/packages/megalodon/src/entities/status.ts +++ b/packages/megalodon/src/entities/status.ts @@ -9,37 +9,37 @@ /// namespace Entity { - export type Status = { - id: string - uri: string - url: string - account: Account - in_reply_to_id: string | null - in_reply_to_account_id: string | null - reblog: Status | null - content: string - plain_content: string | null - created_at: string - emojis: Emoji[] - replies_count: number - reblogs_count: number - favourites_count: number - reblogged: boolean | null - favourited: boolean | null - muted: boolean | null - sensitive: boolean - spoiler_text: string - visibility: 'public' | 'unlisted' | 'private' | 'direct' - media_attachments: Array - mentions: Array - tags: Array - card: Card | null - poll: Poll | null - application: Application | null - language: string | null - pinned: boolean | null - emoji_reactions: Array - quote: Status | null - bookmarked: boolean - } + export type Status = { + id: string; + uri: string; + url: string; + account: Account; + in_reply_to_id: string | null; + in_reply_to_account_id: string | null; + reblog: Status | null; + content: string; + plain_content: string | null; + created_at: string; + emojis: Emoji[]; + replies_count: number; + reblogs_count: number; + favourites_count: number; + reblogged: boolean | null; + favourited: boolean | null; + muted: boolean | null; + sensitive: boolean; + spoiler_text: string; + visibility: "public" | "unlisted" | "private" | "direct"; + media_attachments: Array; + mentions: Array; + tags: Array; + card: Card | null; + poll: Poll | null; + application: Application | null; + language: string | null; + pinned: boolean | null; + emoji_reactions: Array; + quote: Status | null; + bookmarked: boolean; + }; } diff --git a/packages/megalodon/src/entities/status_edit.ts b/packages/megalodon/src/entities/status_edit.ts index 30bbee8e6..4040b4ff9 100644 --- a/packages/megalodon/src/entities/status_edit.ts +++ b/packages/megalodon/src/entities/status_edit.ts @@ -9,15 +9,15 @@ /// namespace Entity { - export type StatusEdit = { - account: Account - content: string - plain_content: string | null - created_at: string - emojis: Emoji[] - sensitive: boolean - spoiler_text: string - media_attachments: Array - poll: Poll | null - } + export type StatusEdit = { + account: Account; + content: string; + plain_content: string | null; + created_at: string; + emojis: Emoji[]; + sensitive: boolean; + spoiler_text: string; + media_attachments: Array; + poll: Poll | null; + }; } diff --git a/packages/megalodon/src/entities/status_params.ts b/packages/megalodon/src/entities/status_params.ts index 6de12423c..18908c01c 100644 --- a/packages/megalodon/src/entities/status_params.ts +++ b/packages/megalodon/src/entities/status_params.ts @@ -1,12 +1,12 @@ namespace Entity { - export type StatusParams = { - text: string - in_reply_to_id: string | null - media_ids: Array | null - sensitive: boolean | null - spoiler_text: string | null - visibility: 'public' | 'unlisted' | 'private' | 'direct' - scheduled_at: string | null - application_id: string - } + export type StatusParams = { + text: string; + in_reply_to_id: string | null; + media_ids: Array | null; + sensitive: boolean | null; + spoiler_text: string | null; + visibility: "public" | "unlisted" | "private" | "direct"; + scheduled_at: string | null; + application_id: string; + }; } diff --git a/packages/megalodon/src/entities/tag.ts b/packages/megalodon/src/entities/tag.ts index ff5b93381..ccc88aece 100644 --- a/packages/megalodon/src/entities/tag.ts +++ b/packages/megalodon/src/entities/tag.ts @@ -1,10 +1,10 @@ /// namespace Entity { - export type Tag = { - name: string - url: string - history: Array | null - following?: boolean - } + export type Tag = { + name: string; + url: string; + history: Array | null; + following?: boolean; + }; } diff --git a/packages/megalodon/src/entities/token.ts b/packages/megalodon/src/entities/token.ts index 6fa28e39b..1583edafb 100644 --- a/packages/megalodon/src/entities/token.ts +++ b/packages/megalodon/src/entities/token.ts @@ -1,8 +1,8 @@ namespace Entity { - export type Token = { - access_token: string - token_type: string - scope: string - created_at: number - } + export type Token = { + access_token: string; + token_type: string; + scope: string; + created_at: number; + }; } diff --git a/packages/megalodon/src/entities/urls.ts b/packages/megalodon/src/entities/urls.ts index 4a980d589..1ee9ed67c 100644 --- a/packages/megalodon/src/entities/urls.ts +++ b/packages/megalodon/src/entities/urls.ts @@ -1,5 +1,5 @@ namespace Entity { - export type URLs = { - streaming_api: string - } + export type URLs = { + streaming_api: string; + }; } diff --git a/packages/megalodon/src/misskey/api_client.ts b/packages/megalodon/src/misskey/api_client.ts index 1c198ecc9..22cac2a1c 100644 --- a/packages/megalodon/src/misskey/api_client.ts +++ b/packages/megalodon/src/misskey/api_client.ts @@ -1,645 +1,716 @@ -import axios, { AxiosResponse, AxiosRequestConfig } from 'axios' -import dayjs from 'dayjs' -import FormData from 'form-data' +import axios, { AxiosResponse, AxiosRequestConfig } from "axios"; +import dayjs from "dayjs"; +import FormData from "form-data"; -import { DEFAULT_UA } from '../default' -import proxyAgent, { ProxyConfig } from '../proxy_config' -import Response from '../response' -import MisskeyEntity from './entity' -import MegalodonEntity from '../entity' -import WebSocket from './web_socket' -import MisskeyNotificationType from './notification' -import NotificationType from '../notification' +import { DEFAULT_UA } from "../default"; +import proxyAgent, { ProxyConfig } from "../proxy_config"; +import Response from "../response"; +import MisskeyEntity from "./entity"; +import MegalodonEntity from "../entity"; +import WebSocket from "./web_socket"; +import MisskeyNotificationType from "./notification"; +import NotificationType from "../notification"; namespace MisskeyAPI { - export namespace Entity { - export type App = MisskeyEntity.App - export type Announcement = MisskeyEntity.Announcement - export type Blocking = MisskeyEntity.Blocking - export type Choice = MisskeyEntity.Choice - export type CreatedNote = MisskeyEntity.CreatedNote - export type Emoji = MisskeyEntity.Emoji - export type Favorite = MisskeyEntity.Favorite - export type Field = MisskeyEntity.Field - export type File = MisskeyEntity.File - export type Follower = MisskeyEntity.Follower - export type Following = MisskeyEntity.Following - export type FollowRequest = MisskeyEntity.FollowRequest - export type Hashtag = MisskeyEntity.Hashtag - export type List = MisskeyEntity.List - export type Meta = MisskeyEntity.Meta - export type Mute = MisskeyEntity.Mute - export type Note = MisskeyEntity.Note - export type Notification = MisskeyEntity.Notification - export type Poll = MisskeyEntity.Poll - export type Reaction = MisskeyEntity.Reaction - export type Relation = MisskeyEntity.Relation - export type User = MisskeyEntity.User - export type UserDetail = MisskeyEntity.UserDetail - export type UserDetailMe = MisskeyEntity.UserDetailMe - export type GetAll = MisskeyEntity.GetAll - export type UserKey = MisskeyEntity.UserKey - export type Session = MisskeyEntity.Session - export type Stats = MisskeyEntity.Stats - export type State = MisskeyEntity.State - export type APIEmoji = { emojis: Emoji[] } - } + export namespace Entity { + export type App = MisskeyEntity.App; + export type Announcement = MisskeyEntity.Announcement; + export type Blocking = MisskeyEntity.Blocking; + export type Choice = MisskeyEntity.Choice; + export type CreatedNote = MisskeyEntity.CreatedNote; + export type Emoji = MisskeyEntity.Emoji; + export type Favorite = MisskeyEntity.Favorite; + export type Field = MisskeyEntity.Field; + export type File = MisskeyEntity.File; + export type Follower = MisskeyEntity.Follower; + export type Following = MisskeyEntity.Following; + export type FollowRequest = MisskeyEntity.FollowRequest; + export type Hashtag = MisskeyEntity.Hashtag; + export type List = MisskeyEntity.List; + export type Meta = MisskeyEntity.Meta; + export type Mute = MisskeyEntity.Mute; + export type Note = MisskeyEntity.Note; + export type Notification = MisskeyEntity.Notification; + export type Poll = MisskeyEntity.Poll; + export type Reaction = MisskeyEntity.Reaction; + export type Relation = MisskeyEntity.Relation; + export type User = MisskeyEntity.User; + export type UserDetail = MisskeyEntity.UserDetail; + export type UserDetailMe = MisskeyEntity.UserDetailMe; + export type GetAll = MisskeyEntity.GetAll; + export type UserKey = MisskeyEntity.UserKey; + export type Session = MisskeyEntity.Session; + export type Stats = MisskeyEntity.Stats; + export type State = MisskeyEntity.State; + export type APIEmoji = { emojis: Emoji[] }; + } - export class Converter { - private baseUrl: string - private instanceHost: string - private plcUrl: string - private modelOfAcct = { - id: "1", - username: 'none', - acct: 'none', - display_name: 'none', - locked: true, - bot: true, - discoverable: false, - group: false, - created_at: '1971-01-01T00:00:00.000Z', - note: '', - url: 'plc', - avatar: 'plc', - avatar_static: 'plc', - header: 'plc', - header_static: 'plc', - followers_count: -1, - following_count: 0, - statuses_count: 0, - last_status_at: '1971-01-01T00:00:00.000Z', - noindex: true, - emojis: [], - fields: [], - moved: null - } + export class Converter { + private baseUrl: string; + private instanceHost: string; + private plcUrl: string; + private modelOfAcct = { + id: "1", + username: "none", + acct: "none", + display_name: "none", + locked: true, + bot: true, + discoverable: false, + group: false, + created_at: "1971-01-01T00:00:00.000Z", + note: "", + url: "plc", + avatar: "plc", + avatar_static: "plc", + header: "plc", + header_static: "plc", + followers_count: -1, + following_count: 0, + statuses_count: 0, + last_status_at: "1971-01-01T00:00:00.000Z", + noindex: true, + emojis: [], + fields: [], + moved: null, + }; - constructor(baseUrl: string) { - this.baseUrl = baseUrl; - this.instanceHost = baseUrl.substring(baseUrl.indexOf('//') + 2); - this.plcUrl = `${baseUrl}/static-assets/transparent.png`; - this.modelOfAcct.url = this.plcUrl; - this.modelOfAcct.avatar = this.plcUrl; - this.modelOfAcct.avatar_static = this.plcUrl; - this.modelOfAcct.header = this.plcUrl; - this.modelOfAcct.header_static = this.plcUrl; - } + constructor(baseUrl: string) { + this.baseUrl = baseUrl; + this.instanceHost = baseUrl.substring(baseUrl.indexOf("//") + 2); + this.plcUrl = `${baseUrl}/static-assets/transparent.png`; + this.modelOfAcct.url = this.plcUrl; + this.modelOfAcct.avatar = this.plcUrl; + this.modelOfAcct.avatar_static = this.plcUrl; + this.modelOfAcct.header = this.plcUrl; + this.modelOfAcct.header_static = this.plcUrl; + } + // FIXME: Properly render MFM instead of just escaping HTML characters. + escapeMFM = (text: string): string => + text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'") + .replace(/`/g, "`") + .replace(/\r?\n/g, "
"); + emoji = (e: Entity.Emoji): MegalodonEntity.Emoji => { + return { + shortcode: e.name, + static_url: e.url, + url: e.url, + visible_in_picker: true, + category: e.category, + }; + }; - // FIXME: Properly render MFM instead of just escaping HTML characters. - escapeMFM = (text: string): string => text - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(/`/g, '`') - .replace(/\r?\n/g, '
'); + field = (f: Entity.Field): MegalodonEntity.Field => ({ + name: f.name, + value: this.escapeMFM(f.value), + verified_at: null, + }); - emoji = (e: Entity.Emoji): MegalodonEntity.Emoji => { - return { - shortcode: e.name, - static_url: e.url, - url: e.url, - visible_in_picker: true, - category: e.category - } - } + user = (u: Entity.User): MegalodonEntity.Account => { + let acct = u.username; + let acctUrl = `https://${u.host || this.instanceHost}/@${u.username}`; + if (u.host) { + acct = `${u.username}@${u.host}`; + acctUrl = `https://${u.host}/@${u.username}`; + } + return { + id: u.id, + username: u.username, + acct: acct, + display_name: u.name || u.username, + locked: false, + created_at: new Date().toISOString(), + followers_count: 0, + following_count: 0, + statuses_count: 0, + note: "", + url: acctUrl, + avatar: u.avatarUrl, + avatar_static: u.avatarUrl, + header: this.plcUrl, + header_static: this.plcUrl, + emojis: u.emojis.map((e) => this.emoji(e)), + moved: null, + fields: [], + bot: false, + }; + }; - field = (f: Entity.Field): MegalodonEntity.Field => ({ - name: f.name, - value: this.escapeMFM(f.value), - verified_at: null - }) + userDetail = ( + u: Entity.UserDetail, + host: string, + ): MegalodonEntity.Account => { + let acct = u.username; + host = host.replace("https://", ""); + let acctUrl = `https://${host || u.host || this.instanceHost}/@${ + u.username + }`; + if (u.host) { + acct = `${u.username}@${u.host}`; + acctUrl = `https://${u.host}/@${u.username}`; + } + return { + id: u.id, + username: u.username, + acct: acct, + display_name: u.name || u.username, + locked: u.isLocked, + created_at: u.createdAt, + followers_count: u.followersCount, + following_count: u.followingCount, + statuses_count: u.notesCount, + note: u.description?.replace(/\n|\\n/g, "
") ?? "", + url: acctUrl, + avatar: u.avatarUrl, + avatar_static: u.avatarUrl, + header: u.bannerUrl ?? this.plcUrl, + header_static: u.bannerUrl ?? this.plcUrl, + emojis: u.emojis.map((e) => this.emoji(e)), + moved: null, + fields: u.fields.map((f) => this.field(f)), + bot: u.isBot, + }; + }; - user = (u: Entity.User): MegalodonEntity.Account => { - let acct = u.username - let acctUrl = `https://${u.host || this.instanceHost}/@${u.username}` - if (u.host) { - acct = `${u.username}@${u.host}` - acctUrl = `https://${u.host}/@${u.username}` - } - return { - id: u.id, - username: u.username, - acct: acct, - display_name: u.name || u.username, - locked: false, - created_at: new Date().toISOString(), - followers_count: 0, - following_count: 0, - statuses_count: 0, - note: '', - url: acctUrl, - avatar: u.avatarUrl, - avatar_static: u.avatarUrl, - header: this.plcUrl, - header_static: this.plcUrl, - emojis: u.emojis.map(e => this.emoji(e)), - moved: null, - fields: [], - bot: false - } - } + userPreferences = ( + u: MisskeyAPI.Entity.UserDetailMe, + v: "public" | "unlisted" | "private" | "direct", + ): MegalodonEntity.Preferences => { + return { + "reading:expand:media": "default", + "reading:expand:spoilers": false, + "posting:default:language": u.lang, + "posting:default:sensitive": u.alwaysMarkNsfw, + "posting:default:visibility": v, + }; + }; - userDetail = (u: Entity.UserDetail, host: string): MegalodonEntity.Account => { - let acct = u.username - host = host.replace('https://', '') - let acctUrl = `https://${host || u.host || this.instanceHost}/@${u.username}` - if (u.host) { - acct = `${u.username}@${u.host}` - acctUrl = `https://${u.host}/@${u.username}` - } - return { - id: u.id, - username: u.username, - acct: acct, - display_name: u.name || u.username, - locked: u.isLocked, - created_at: u.createdAt, - followers_count: u.followersCount, - following_count: u.followingCount, - statuses_count: u.notesCount, - note: u.description?.replace(/\n|\\n/g, '
') ?? '', - url: acctUrl, - avatar: u.avatarUrl, - avatar_static: u.avatarUrl, - header: u.bannerUrl ?? this.plcUrl, - header_static: u.bannerUrl ?? this.plcUrl, - emojis: u.emojis.map(e => this.emoji(e)), - moved: null, - fields: u.fields.map(f => this.field(f)), - bot: u.isBot, - } - } + visibility = ( + v: "public" | "home" | "followers" | "specified", + ): "public" | "unlisted" | "private" | "direct" => { + switch (v) { + case "public": + return v; + case "home": + return "unlisted"; + case "followers": + return "private"; + case "specified": + return "direct"; + } + }; - userPreferences = (u: MisskeyAPI.Entity.UserDetailMe, v: 'public' | 'unlisted' | 'private' | 'direct'): MegalodonEntity.Preferences => { - return { - "reading:expand:media": "default", - "reading:expand:spoilers": false, - "posting:default:language": u.lang, - "posting:default:sensitive": u.alwaysMarkNsfw, - "posting:default:visibility": v - } - } + encodeVisibility = ( + v: "public" | "unlisted" | "private" | "direct", + ): "public" | "home" | "followers" | "specified" => { + switch (v) { + case "public": + return v; + case "unlisted": + return "home"; + case "private": + return "followers"; + case "direct": + return "specified"; + } + }; - visibility = (v: 'public' | 'home' | 'followers' | 'specified'): 'public' | 'unlisted' | 'private' | 'direct' => { - switch (v) { - case 'public': - return v - case 'home': - return 'unlisted' - case 'followers': - return 'private' - case 'specified': - return 'direct' - } - } + fileType = ( + s: string, + ): "unknown" | "image" | "gifv" | "video" | "audio" => { + if (s === "image/gif") { + return "gifv"; + } + if (s.includes("image")) { + return "image"; + } + if (s.includes("video")) { + return "video"; + } + if (s.includes("audio")) { + return "audio"; + } + return "unknown"; + }; - encodeVisibility = (v: 'public' | 'unlisted' | 'private' | 'direct'): 'public' | 'home' | 'followers' | 'specified' => { - switch (v) { - case 'public': - return v - case 'unlisted': - return 'home' - case 'private': - return 'followers' - case 'direct': - return 'specified' - } - } + file = (f: Entity.File): MegalodonEntity.Attachment => { + return { + id: f.id, + type: this.fileType(f.type), + url: f.url, + remote_url: f.url, + preview_url: f.thumbnailUrl, + text_url: f.url, + meta: { + width: f.properties.width, + height: f.properties.height, + }, + description: f.comment, + blurhash: f.blurhash, + }; + }; - fileType = (s: string): 'unknown' | 'image' | 'gifv' | 'video' | 'audio' => { - if (s === 'image/gif') { - return 'gifv' - } - if (s.includes('image')) { - return 'image' - } - if (s.includes('video')) { - return 'video' - } - if (s.includes('audio')) { - return 'audio' - } - return 'unknown' - } + follower = (f: Entity.Follower): MegalodonEntity.Account => { + return this.user(f.follower); + }; - file = (f: Entity.File): MegalodonEntity.Attachment => { - return { - id: f.id, - type: this.fileType(f.type), - url: f.url, - remote_url: f.url, - preview_url: f.thumbnailUrl, - text_url: f.url, - meta: { - width: f.properties.width, - height: f.properties.height - }, - description: f.comment, - blurhash: f.blurhash - } - } + following = (f: Entity.Following): MegalodonEntity.Account => { + return this.user(f.followee); + }; - follower = (f: Entity.Follower): MegalodonEntity.Account => { - return this.user(f.follower) - } + relation = (r: Entity.Relation): MegalodonEntity.Relationship => { + return { + id: r.id, + following: r.isFollowing, + followed_by: r.isFollowed, + blocking: r.isBlocking, + blocked_by: r.isBlocked, + muting: r.isMuted, + muting_notifications: false, + requested: r.hasPendingFollowRequestFromYou, + domain_blocking: false, + showing_reblogs: true, + endorsed: false, + notifying: false, + }; + }; - following = (f: Entity.Following): MegalodonEntity.Account => { - return this.user(f.followee) - } + choice = (c: Entity.Choice): MegalodonEntity.PollOption => { + return { + title: c.text, + votes_count: c.votes, + }; + }; - relation = (r: Entity.Relation): MegalodonEntity.Relationship => { - return { - id: r.id, - following: r.isFollowing, - followed_by: r.isFollowed, - blocking: r.isBlocking, - blocked_by: r.isBlocked, - muting: r.isMuted, - muting_notifications: false, - requested: r.hasPendingFollowRequestFromYou, - domain_blocking: false, - showing_reblogs: true, - endorsed: false, - notifying: false - } - } + poll = (p: Entity.Poll, id: string): MegalodonEntity.Poll => { + const now = dayjs(); + const expire = dayjs(p.expiresAt); + const count = p.choices.reduce((sum, choice) => sum + choice.votes, 0); + return { + id: id, + expires_at: p.expiresAt, + expired: now.isAfter(expire), + multiple: p.multiple, + votes_count: count, + options: p.choices.map((c) => this.choice(c)), + voted: p.choices.some((c) => c.isVoted), + own_votes: p.choices + .filter((c) => c.isVoted) + .map((c) => p.choices.indexOf(c)), + }; + }; - choice = (c: Entity.Choice): MegalodonEntity.PollOption => { - return { - title: c.text, - votes_count: c.votes - } - } + note = (n: Entity.Note, host: string): MegalodonEntity.Status => { + host = host.replace("https://", ""); - poll = (p: Entity.Poll, id: string): MegalodonEntity.Poll => { - const now = dayjs() - const expire = dayjs(p.expiresAt) - const count = p.choices.reduce((sum, choice) => sum + choice.votes, 0) - return { - id: id, - expires_at: p.expiresAt, - expired: now.isAfter(expire), - multiple: p.multiple, - votes_count: count, - options: p.choices.map(c => this.choice(c)), - voted: p.choices.some(c => c.isVoted), - own_votes: p.choices.filter(c => c.isVoted).map(c => p.choices.indexOf(c)) - } - } + return { + id: n.id, + uri: n.uri ? n.uri : `https://${host}/notes/${n.id}`, + url: n.uri ? n.uri : `https://${host}/notes/${n.id}`, + account: this.user(n.user), + in_reply_to_id: n.replyId, + in_reply_to_account_id: n.reply?.userId ?? null, + reblog: n.renote ? this.note(n.renote, host) : null, + content: n.text ? this.escapeMFM(n.text) : "", + plain_content: n.text ? n.text : null, + created_at: n.createdAt, + emojis: n.emojis.map((e) => this.emoji(e)), + replies_count: n.repliesCount, + reblogs_count: n.renoteCount, + favourites_count: this.getTotalReactions(n.reactions), + reblogged: false, + favourited: !!n.myReaction, + muted: false, + sensitive: n.files ? n.files.some((f) => f.isSensitive) : false, + spoiler_text: n.cw ? n.cw : "", + visibility: this.visibility(n.visibility), + media_attachments: n.files ? n.files.map((f) => this.file(f)) : [], + mentions: [], + tags: [], + card: null, + poll: n.poll ? this.poll(n.poll, n.id) : null, + application: null, + language: null, + pinned: null, + emoji_reactions: this.mapReactions(n.reactions, n.myReaction), + bookmarked: false, + quote: n.renote && n.text ? this.note(n.renote, host) : null, + }; + }; - note = (n: Entity.Note, host: string): MegalodonEntity.Status => { - host = host.replace('https://', '') + mapReactions = ( + r: { [key: string]: number }, + myReaction?: string, + ): Array => { + return Object.keys(r).map((key) => { + if (myReaction && key === myReaction) { + return { + count: r[key], + me: true, + name: key, + }; + } + return { + count: r[key], + me: false, + name: key, + }; + }); + }; - return { - id: n.id, - uri: n.uri ? n.uri : `https://${host}/notes/${n.id}`, - url: n.uri ? n.uri : `https://${host}/notes/${n.id}`, - account: this.user(n.user), - in_reply_to_id: n.replyId, - in_reply_to_account_id: n.reply?.userId ?? null, - reblog: n.renote ? this.note(n.renote, host) : null, - content: n.text ? this.escapeMFM(n.text) : '', - plain_content: n.text ? n.text : null, - created_at: n.createdAt, - emojis: n.emojis.map(e => this.emoji(e)), - replies_count: n.repliesCount, - reblogs_count: n.renoteCount, - favourites_count: this.getTotalReactions(n.reactions), - reblogged: false, - favourited: !!n.myReaction, - muted: false, - sensitive: n.files ? n.files.some(f => f.isSensitive) : false, - spoiler_text: n.cw ? n.cw : '', - visibility: this.visibility(n.visibility), - media_attachments: n.files ? n.files.map(f => this.file(f)) : [], - mentions: [], - tags: [], - card: null, - poll: n.poll ? this.poll(n.poll, n.id) : null, - application: null, - language: null, - pinned: null, - emoji_reactions: this.mapReactions(n.reactions, n.myReaction), - bookmarked: false, - quote: n.renote && n.text ? this.note(n.renote, host) : null - } - } + getTotalReactions = (r: { [key: string]: number }): number => { + return Object.values(r).length > 0 + ? Object.values(r).reduce( + (previousValue, currentValue) => previousValue + currentValue, + ) + : 0; + }; - mapReactions = (r: { [key: string]: number }, myReaction?: string): Array => { - return Object.keys(r).map(key => { - if (myReaction && key === myReaction) { - return { - count: r[key], - me: true, - name: key - } - } - return { - count: r[key], - me: false, - name: key - } - }) - } + reactions = ( + r: Array, + ): Array => { + const result: Array = []; + for (const e of r) { + const i = result.findIndex((res) => res.name === e.type); + if (i >= 0) { + result[i].count++; + } else { + result.push({ + count: 1, + me: false, + name: e.type, + }); + } + } + return result; + }; - getTotalReactions = (r: { [key: string]: number }): number => { - return Object.values(r).length > 0 ? Object.values(r).reduce((previousValue, currentValue) => previousValue + currentValue) : 0 - } + noteToConversation = ( + n: Entity.Note, + host: string, + ): MegalodonEntity.Conversation => { + const accounts: Array = [this.user(n.user)]; + if (n.reply) { + accounts.push(this.user(n.reply.user)); + } + return { + id: n.id, + accounts: accounts, + last_status: this.note(n, host), + unread: false, + }; + }; - reactions = (r: Array): Array => { - const result: Array = [] - for (const e of r) { - const i = result.findIndex(res => res.name === e.type) - if (i >= 0) { - result[i].count++ - } else { - result.push({ - count: 1, - me: false, - name: e.type - }) - } - } - return result - } + list = (l: Entity.List): MegalodonEntity.List => ({ + id: l.id, + title: l.name, + }); - noteToConversation = (n: Entity.Note, host: string): MegalodonEntity.Conversation => { - const accounts: Array = [this.user(n.user)] - if (n.reply) { - accounts.push(this.user(n.reply.user)) - } - return { - id: n.id, - accounts: accounts, - last_status: this.note(n, host), - unread: false - } - } - - list = (l: Entity.List): MegalodonEntity.List => ({ - id: l.id, - title: l.name - }) - - encodeNotificationType = (e: MegalodonEntity.NotificationType): MisskeyEntity.NotificationType => { - switch (e) { - case NotificationType.Follow: - return MisskeyNotificationType.Follow - case NotificationType.Mention: - return MisskeyNotificationType.Reply - case NotificationType.Favourite: - case NotificationType.EmojiReaction: - return MisskeyNotificationType.Reaction - case NotificationType.Reblog: - return MisskeyNotificationType.Renote + encodeNotificationType = ( + e: MegalodonEntity.NotificationType, + ): MisskeyEntity.NotificationType => { + switch (e) { + case NotificationType.Follow: + return MisskeyNotificationType.Follow; + case NotificationType.Mention: + return MisskeyNotificationType.Reply; + case NotificationType.Favourite: + case NotificationType.EmojiReaction: + return MisskeyNotificationType.Reaction; + case NotificationType.Reblog: + return MisskeyNotificationType.Renote; case NotificationType.Poll: - return MisskeyNotificationType.PollEnded - case NotificationType.FollowRequest: - return MisskeyNotificationType.ReceiveFollowRequest - default: - return e - } - } + return MisskeyNotificationType.PollEnded; + case NotificationType.FollowRequest: + return MisskeyNotificationType.ReceiveFollowRequest; + default: + return e; + } + }; - decodeNotificationType = (e: MisskeyEntity.NotificationType): MegalodonEntity.NotificationType => { - switch (e) { - case MisskeyNotificationType.Follow: - return NotificationType.Follow - case MisskeyNotificationType.Mention: - case MisskeyNotificationType.Reply: - return NotificationType.Mention - case MisskeyNotificationType.Renote: - case MisskeyNotificationType.Quote: - return NotificationType.Reblog - case MisskeyNotificationType.Reaction: - return NotificationType.EmojiReaction + decodeNotificationType = ( + e: MisskeyEntity.NotificationType, + ): MegalodonEntity.NotificationType => { + switch (e) { + case MisskeyNotificationType.Follow: + return NotificationType.Follow; + case MisskeyNotificationType.Mention: + case MisskeyNotificationType.Reply: + return NotificationType.Mention; + case MisskeyNotificationType.Renote: + case MisskeyNotificationType.Quote: + return NotificationType.Reblog; + case MisskeyNotificationType.Reaction: + return NotificationType.EmojiReaction; case MisskeyNotificationType.PollEnded: - return NotificationType.Poll - case MisskeyNotificationType.ReceiveFollowRequest: - return NotificationType.FollowRequest - case MisskeyNotificationType.FollowRequestAccepted: - return NotificationType.Follow - default: - return e - } - } + return NotificationType.Poll; + case MisskeyNotificationType.ReceiveFollowRequest: + return NotificationType.FollowRequest; + case MisskeyNotificationType.FollowRequestAccepted: + return NotificationType.Follow; + default: + return e; + } + }; + announcement = (a: Entity.Announcement): MegalodonEntity.Announcement => ({ + id: a.id, + content: `

${this.escapeMFM(a.title)}

${this.escapeMFM(a.text)}`, + starts_at: null, + ends_at: null, + published: true, + all_day: false, + published_at: a.createdAt, + updated_at: a.updatedAt, + read: a.isRead, + mentions: [], + statuses: [], + tags: [], + emojis: [], + reactions: [], + }); - - announcement = (a: Entity.Announcement): MegalodonEntity.Announcement => ({ - id: a.id, - content: `

${this.escapeMFM(a.title)}

${this.escapeMFM(a.text)}`, - starts_at: null, - ends_at: null, - published: true, - all_day: false, - published_at: a.createdAt, - updated_at: a.updatedAt, - read: a.isRead, - mentions: [], - statuses: [], - tags: [], - emojis: [], - reactions: [], - }) - - notification = (n: Entity.Notification, host: string): MegalodonEntity.Notification => { - let notification = { - id: n.id, - account: n.user ? this.user(n.user) : this.modelOfAcct, - created_at: n.createdAt, - type: this.decodeNotificationType(n.type) - } - if (n.note) { - notification = Object.assign(notification, { - status: this.note(n.note, host) - }) + notification = ( + n: Entity.Notification, + host: string, + ): MegalodonEntity.Notification => { + let notification = { + id: n.id, + account: n.user ? this.user(n.user) : this.modelOfAcct, + created_at: n.createdAt, + type: this.decodeNotificationType(n.type), + }; + if (n.note) { + notification = Object.assign(notification, { + status: this.note(n.note, host), + }); if (notification.type === NotificationType.Poll) { notification = Object.assign(notification, { - account: this.note(n.note, host).account - }) + account: this.note(n.note, host).account, + }); } - } - if (n.reaction) { - notification = Object.assign(notification, { - emoji: n.reaction - }) - } - return notification - } + } + if (n.reaction) { + notification = Object.assign(notification, { + emoji: n.reaction, + }); + } + return notification; + }; - stats = (s: Entity.Stats): MegalodonEntity.Stats => { - return { - user_count: s.usersCount, - status_count: s.notesCount, - domain_count: s.instances - } - } + stats = (s: Entity.Stats): MegalodonEntity.Stats => { + return { + user_count: s.usersCount, + status_count: s.notesCount, + domain_count: s.instances, + }; + }; - meta = (m: Entity.Meta, s: Entity.Stats): MegalodonEntity.Instance => { - const wss = m.uri.replace(/^https:\/\//, 'wss://') - return { - uri: m.uri, - title: m.name, - description: m.description, - email: m.maintainerEmail, - version: m.version, - thumbnail: m.bannerUrl, - urls: { - streaming_api: `${wss}/streaming` - }, - stats: this.stats(s), - languages: m.langs, - contact_account: null, - max_toot_chars: m.maxNoteTextLength, - registrations: !m.disableRegistration - } - } + meta = (m: Entity.Meta, s: Entity.Stats): MegalodonEntity.Instance => { + const wss = m.uri.replace(/^https:\/\//, "wss://"); + return { + uri: m.uri, + title: m.name, + description: m.description, + email: m.maintainerEmail, + version: m.version, + thumbnail: m.bannerUrl, + urls: { + streaming_api: `${wss}/streaming`, + }, + stats: this.stats(s), + languages: m.langs, + contact_account: null, + max_toot_chars: m.maxNoteTextLength, + registrations: !m.disableRegistration, + }; + }; - hashtag = (h: Entity.Hashtag): MegalodonEntity.Tag => { - return { - name: h.tag, - url: h.tag, - history: null, - following: false - } - } - } + hashtag = (h: Entity.Hashtag): MegalodonEntity.Tag => { + return { + name: h.tag, + url: h.tag, + history: null, + following: false, + }; + }; + } - export const DEFAULT_SCOPE = [ - 'read:account', - 'write:account', - 'read:blocks', - 'write:blocks', - 'read:drive', - 'write:drive', - 'read:favorites', - 'write:favorites', - 'read:following', - 'write:following', - 'read:mutes', - 'write:mutes', - 'write:notes', - 'read:notifications', - 'write:notifications', - 'read:reactions', - 'write:reactions', - 'write:votes' - ] + export const DEFAULT_SCOPE = [ + "read:account", + "write:account", + "read:blocks", + "write:blocks", + "read:drive", + "write:drive", + "read:favorites", + "write:favorites", + "read:following", + "write:following", + "read:mutes", + "write:mutes", + "write:notes", + "read:notifications", + "write:notifications", + "read:reactions", + "write:reactions", + "write:votes", + ]; - /** - * Interface - */ - export interface Interface { - post(path: string, params?: any, headers?: { [key: string]: string }): Promise> - cancel(): void - socket(channel: 'user' | 'localTimeline' | 'hybridTimeline' | 'globalTimeline' | 'conversation' | 'list', listId?: string): WebSocket - } + /** + * Interface + */ + export interface Interface { + post( + path: string, + params?: any, + headers?: { [key: string]: string }, + ): Promise>; + cancel(): void; + socket( + channel: + | "user" + | "localTimeline" + | "hybridTimeline" + | "globalTimeline" + | "conversation" + | "list", + listId?: string, + ): WebSocket; + } - /** - * Misskey API client. - * - * Usign axios for request, you will handle promises. - */ - export class Client implements Interface { - private accessToken: string | null - private baseUrl: string - private userAgent: string - private abortController: AbortController - private proxyConfig: ProxyConfig | false = false - private converter: Converter + /** + * Misskey API client. + * + * Usign axios for request, you will handle promises. + */ + export class Client implements Interface { + private accessToken: string | null; + private baseUrl: string; + private userAgent: string; + private abortController: AbortController; + private proxyConfig: ProxyConfig | false = false; + private converter: Converter; - /** - * @param baseUrl hostname or base URL - * @param accessToken access token from OAuth2 authorization - * @param userAgent UserAgent is specified in header on request. - * @param proxyConfig Proxy setting, or set false if don't use proxy. - * @param converter Converter instance. - */ - constructor(baseUrl: string, accessToken: string | null, userAgent: string = DEFAULT_UA, proxyConfig: ProxyConfig | false = false, converter: Converter) { - this.accessToken = accessToken - this.baseUrl = baseUrl - this.userAgent = userAgent - this.proxyConfig = proxyConfig - this.abortController = new AbortController() - this.converter = converter - axios.defaults.signal = this.abortController.signal - } + /** + * @param baseUrl hostname or base URL + * @param accessToken access token from OAuth2 authorization + * @param userAgent UserAgent is specified in header on request. + * @param proxyConfig Proxy setting, or set false if don't use proxy. + * @param converter Converter instance. + */ + constructor( + baseUrl: string, + accessToken: string | null, + userAgent: string = DEFAULT_UA, + proxyConfig: ProxyConfig | false = false, + converter: Converter, + ) { + this.accessToken = accessToken; + this.baseUrl = baseUrl; + this.userAgent = userAgent; + this.proxyConfig = proxyConfig; + this.abortController = new AbortController(); + this.converter = converter; + axios.defaults.signal = this.abortController.signal; + } - /** - * POST request to mastodon REST API. - * @param path relative path from baseUrl - * @param params Form data - * @param headers Request header object - */ - public async post(path: string, params: any = {}, headers: { [key: string]: string } = {}): Promise> { - let options: AxiosRequestConfig = { - headers: headers, - maxContentLength: Infinity, - maxBodyLength: Infinity - } - if (this.proxyConfig) { - options = Object.assign(options, { - httpAgent: proxyAgent(this.proxyConfig), - httpsAgent: proxyAgent(this.proxyConfig) - }) - } - let bodyParams = params - if (this.accessToken) { - if (params instanceof FormData) { - bodyParams.append('i', this.accessToken) - } else { - bodyParams = Object.assign(params, { - i: this.accessToken - }) - } - } + /** + * POST request to mastodon REST API. + * @param path relative path from baseUrl + * @param params Form data + * @param headers Request header object + */ + public async post( + path: string, + params: any = {}, + headers: { [key: string]: string } = {}, + ): Promise> { + let options: AxiosRequestConfig = { + headers: headers, + maxContentLength: Infinity, + maxBodyLength: Infinity, + }; + if (this.proxyConfig) { + options = Object.assign(options, { + httpAgent: proxyAgent(this.proxyConfig), + httpsAgent: proxyAgent(this.proxyConfig), + }); + } + let bodyParams = params; + if (this.accessToken) { + if (params instanceof FormData) { + bodyParams.append("i", this.accessToken); + } else { + bodyParams = Object.assign(params, { + i: this.accessToken, + }); + } + } - return axios.post(this.baseUrl + path, bodyParams, options).then((resp: AxiosResponse) => { - const res: Response = { - data: resp.data, - status: resp.status, - statusText: resp.statusText, - headers: resp.headers - } - return res - }) - } + return axios + .post(this.baseUrl + path, bodyParams, options) + .then((resp: AxiosResponse) => { + const res: Response = { + data: resp.data, + status: resp.status, + statusText: resp.statusText, + headers: resp.headers, + }; + return res; + }); + } - /** - * Cancel all requests in this instance. - * @returns void - */ - public cancel(): void { - return this.abortController.abort() - } + /** + * Cancel all requests in this instance. + * @returns void + */ + public cancel(): void { + return this.abortController.abort(); + } - /** - * Get connection and receive websocket connection for Misskey API. - * - * @param channel Channel name is user, localTimeline, hybridTimeline, globalTimeline, conversation or list. - * @param listId This parameter is required only list channel. - */ - public socket( - channel: 'user' | 'localTimeline' | 'hybridTimeline' | 'globalTimeline' | 'conversation' | 'list', - listId?: string - ): WebSocket { - if (!this.accessToken) { - throw new Error('accessToken is required') - } - const url = `${this.baseUrl}/streaming` - const streaming = new WebSocket(url, channel, this.accessToken, listId, this.userAgent, this.proxyConfig, this.converter) - process.nextTick(() => { - streaming.start() - }) - return streaming - } - } + /** + * Get connection and receive websocket connection for Misskey API. + * + * @param channel Channel name is user, localTimeline, hybridTimeline, globalTimeline, conversation or list. + * @param listId This parameter is required only list channel. + */ + public socket( + channel: + | "user" + | "localTimeline" + | "hybridTimeline" + | "globalTimeline" + | "conversation" + | "list", + listId?: string, + ): WebSocket { + if (!this.accessToken) { + throw new Error("accessToken is required"); + } + const url = `${this.baseUrl}/streaming`; + const streaming = new WebSocket( + url, + channel, + this.accessToken, + listId, + this.userAgent, + this.proxyConfig, + this.converter, + ); + process.nextTick(() => { + streaming.start(); + }); + return streaming; + } + } } -export default MisskeyAPI +export default MisskeyAPI; diff --git a/packages/megalodon/src/misskey/entity.ts b/packages/megalodon/src/misskey/entity.ts index d0bfd15aa..72a80f9d9 100644 --- a/packages/megalodon/src/misskey/entity.ts +++ b/packages/megalodon/src/misskey/entity.ts @@ -25,4 +25,4 @@ /// /// -export default MisskeyEntity +export default MisskeyEntity; diff --git a/packages/megalodon/src/misskey/notification.ts b/packages/megalodon/src/misskey/notification.ts index e44b6159c..eb7c2d23d 100644 --- a/packages/megalodon/src/misskey/notification.ts +++ b/packages/megalodon/src/misskey/notification.ts @@ -1,16 +1,18 @@ -import MisskeyEntity from './entity' +import MisskeyEntity from "./entity"; namespace MisskeyNotificationType { - export const Follow: MisskeyEntity.NotificationType = 'follow' - export const Mention: MisskeyEntity.NotificationType = 'mention' - export const Reply: MisskeyEntity.NotificationType = 'reply' - export const Renote: MisskeyEntity.NotificationType = 'renote' - export const Quote: MisskeyEntity.NotificationType = 'quote' - export const Reaction: MisskeyEntity.NotificationType = 'favourite' - export const PollEnded: MisskeyEntity.NotificationType = 'pollEnded' - export const ReceiveFollowRequest: MisskeyEntity.NotificationType = 'receiveFollowRequest' - export const FollowRequestAccepted: MisskeyEntity.NotificationType = 'followRequestAccepted' - export const GroupInvited: MisskeyEntity.NotificationType = 'groupInvited' + export const Follow: MisskeyEntity.NotificationType = "follow"; + export const Mention: MisskeyEntity.NotificationType = "mention"; + export const Reply: MisskeyEntity.NotificationType = "reply"; + export const Renote: MisskeyEntity.NotificationType = "renote"; + export const Quote: MisskeyEntity.NotificationType = "quote"; + export const Reaction: MisskeyEntity.NotificationType = "favourite"; + export const PollEnded: MisskeyEntity.NotificationType = "pollEnded"; + export const ReceiveFollowRequest: MisskeyEntity.NotificationType = + "receiveFollowRequest"; + export const FollowRequestAccepted: MisskeyEntity.NotificationType = + "followRequestAccepted"; + export const GroupInvited: MisskeyEntity.NotificationType = "groupInvited"; } -export default MisskeyNotificationType +export default MisskeyNotificationType; diff --git a/packages/megalodon/src/misskey/web_socket.ts b/packages/megalodon/src/misskey/web_socket.ts index d3642864a..0cbfc2bfe 100644 --- a/packages/megalodon/src/misskey/web_socket.ts +++ b/packages/megalodon/src/misskey/web_socket.ts @@ -1,329 +1,365 @@ -import WS from 'ws' -import dayjs, { Dayjs } from 'dayjs' -import { v4 as uuid } from 'uuid' -import { EventEmitter } from 'events' -import { WebSocketInterface } from '../megalodon' -import proxyAgent, { ProxyConfig } from '../proxy_config' -import MisskeyAPI from './api_client' +import WS from "ws"; +import dayjs, { Dayjs } from "dayjs"; +import { v4 as uuid } from "uuid"; +import { EventEmitter } from "events"; +import { WebSocketInterface } from "../megalodon"; +import proxyAgent, { ProxyConfig } from "../proxy_config"; +import MisskeyAPI from "./api_client"; /** * WebSocket * Misskey is not support http streaming. It supports websocket instead of streaming. * So this class connect to Misskey server with WebSocket. */ -export default class WebSocket extends EventEmitter implements WebSocketInterface { - public url: string - public channel: 'user' | 'localTimeline' | 'hybridTimeline' | 'globalTimeline' | 'conversation' | 'list' - public parser: any - public headers: { [key: string]: string } - public proxyConfig: ProxyConfig | false = false - public listId: string | null = null - private _converter: MisskeyAPI.Converter - private _accessToken: string - private _reconnectInterval: number - private _reconnectMaxAttempts: number - private _reconnectCurrentAttempts: number - private _connectionClosed: boolean - private _client: WS | null = null - private _channelID: string - private _pongReceivedTimestamp: Dayjs - private _heartbeatInterval: number = 60000 - private _pongWaiting: boolean = false +export default class WebSocket + extends EventEmitter + implements WebSocketInterface +{ + public url: string; + public channel: + | "user" + | "localTimeline" + | "hybridTimeline" + | "globalTimeline" + | "conversation" + | "list"; + public parser: any; + public headers: { [key: string]: string }; + public proxyConfig: ProxyConfig | false = false; + public listId: string | null = null; + private _converter: MisskeyAPI.Converter; + private _accessToken: string; + private _reconnectInterval: number; + private _reconnectMaxAttempts: number; + private _reconnectCurrentAttempts: number; + private _connectionClosed: boolean; + private _client: WS | null = null; + private _channelID: string; + private _pongReceivedTimestamp: Dayjs; + private _heartbeatInterval = 60000; + private _pongWaiting = false; - /** - * @param url Full url of websocket: e.g. wss://misskey.io/streaming - * @param channel Channel name is user, localTimeline, hybridTimeline, globalTimeline, conversation or list. - * @param accessToken The access token. - * @param listId This parameter is required when you specify list as channel. - */ - constructor( - url: string, - channel: 'user' | 'localTimeline' | 'hybridTimeline' | 'globalTimeline' | 'conversation' | 'list', - accessToken: string, - listId: string | undefined, - userAgent: string, - proxyConfig: ProxyConfig | false = false, - converter: MisskeyAPI.Converter - ) { - super() - this.url = url - this.parser = new Parser() - this.channel = channel - this.headers = { - 'User-Agent': userAgent - } - if (listId === undefined) { - this.listId = null - } else { - this.listId = listId - } - this.proxyConfig = proxyConfig - this._accessToken = accessToken - this._reconnectInterval = 10000 - this._reconnectMaxAttempts = Infinity - this._reconnectCurrentAttempts = 0 - this._connectionClosed = false - this._channelID = uuid() - this._pongReceivedTimestamp = dayjs() - this._converter = converter - } + /** + * @param url Full url of websocket: e.g. wss://misskey.io/streaming + * @param channel Channel name is user, localTimeline, hybridTimeline, globalTimeline, conversation or list. + * @param accessToken The access token. + * @param listId This parameter is required when you specify list as channel. + */ + constructor( + url: string, + channel: + | "user" + | "localTimeline" + | "hybridTimeline" + | "globalTimeline" + | "conversation" + | "list", + accessToken: string, + listId: string | undefined, + userAgent: string, + proxyConfig: ProxyConfig | false = false, + converter: MisskeyAPI.Converter, + ) { + super(); + this.url = url; + this.parser = new Parser(); + this.channel = channel; + this.headers = { + "User-Agent": userAgent, + }; + if (listId === undefined) { + this.listId = null; + } else { + this.listId = listId; + } + this.proxyConfig = proxyConfig; + this._accessToken = accessToken; + this._reconnectInterval = 10000; + this._reconnectMaxAttempts = Infinity; + this._reconnectCurrentAttempts = 0; + this._connectionClosed = false; + this._channelID = uuid(); + this._pongReceivedTimestamp = dayjs(); + this._converter = converter; + } - /** - * Start websocket connection. - */ - public start() { - this._connectionClosed = false - this._resetRetryParams() - this._startWebSocketConnection() - } + /** + * Start websocket connection. + */ + public start() { + this._connectionClosed = false; + this._resetRetryParams(); + this._startWebSocketConnection(); + } - private baseUrlToHost(baseUrl: string): string { - return baseUrl.replace('https://', '') - } + private baseUrlToHost(baseUrl: string): string { + return baseUrl.replace("https://", ""); + } - /** - * Reset connection and start new websocket connection. - */ - private _startWebSocketConnection() { - this._resetConnection() - this._setupParser() - this._client = this._connect() - this._bindSocket(this._client) - } + /** + * Reset connection and start new websocket connection. + */ + private _startWebSocketConnection() { + this._resetConnection(); + this._setupParser(); + this._client = this._connect(); + this._bindSocket(this._client); + } - /** - * Stop current connection. - */ - public stop() { - this._connectionClosed = true - this._resetConnection() - this._resetRetryParams() - } + /** + * Stop current connection. + */ + public stop() { + this._connectionClosed = true; + this._resetConnection(); + this._resetRetryParams(); + } - /** - * Clean up current connection, and listeners. - */ - private _resetConnection() { - if (this._client) { - this._client.close(1000) - this._client.removeAllListeners() - this._client = null - } + /** + * Clean up current connection, and listeners. + */ + private _resetConnection() { + if (this._client) { + this._client.close(1000); + this._client.removeAllListeners(); + this._client = null; + } - if (this.parser) { - this.parser.removeAllListeners() - } - } + if (this.parser) { + this.parser.removeAllListeners(); + } + } - /** - * Resets the parameters used in reconnect. - */ - private _resetRetryParams() { - this._reconnectCurrentAttempts = 0 - } + /** + * Resets the parameters used in reconnect. + */ + private _resetRetryParams() { + this._reconnectCurrentAttempts = 0; + } - /** - * Connect to the endpoint. - */ - private _connect(): WS { - let options: WS.ClientOptions = { - headers: this.headers - } - if (this.proxyConfig) { - options = Object.assign(options, { - agent: proxyAgent(this.proxyConfig) - }) - } - const cli: WS = new WS(`${this.url}?i=${this._accessToken}`, options) - return cli - } + /** + * Connect to the endpoint. + */ + private _connect(): WS { + let options: WS.ClientOptions = { + headers: this.headers, + }; + if (this.proxyConfig) { + options = Object.assign(options, { + agent: proxyAgent(this.proxyConfig), + }); + } + const cli: WS = new WS(`${this.url}?i=${this._accessToken}`, options); + return cli; + } - /** - * Connect specified channels in websocket. - */ - private _channel() { - if (!this._client) { - return - } - switch (this.channel) { - case 'conversation': - this._client.send( - JSON.stringify({ - type: 'connect', - body: { - channel: 'main', - id: this._channelID - } - }) - ) - break - case 'user': - this._client.send( - JSON.stringify({ - type: 'connect', - body: { - channel: 'main', - id: this._channelID - } - }) - ) - this._client.send( - JSON.stringify({ - type: 'connect', - body: { - channel: 'homeTimeline', - id: this._channelID - } - }) - ) - break - case 'list': - this._client.send( - JSON.stringify({ - type: 'connect', - body: { - channel: 'userList', - id: this._channelID, - params: { - listId: this.listId - } - } - }) - ) - break - default: - this._client.send( - JSON.stringify({ - type: 'connect', - body: { - channel: this.channel, - id: this._channelID - } - }) - ) - break - } - } + /** + * Connect specified channels in websocket. + */ + private _channel() { + if (!this._client) { + return; + } + switch (this.channel) { + case "conversation": + this._client.send( + JSON.stringify({ + type: "connect", + body: { + channel: "main", + id: this._channelID, + }, + }), + ); + break; + case "user": + this._client.send( + JSON.stringify({ + type: "connect", + body: { + channel: "main", + id: this._channelID, + }, + }), + ); + this._client.send( + JSON.stringify({ + type: "connect", + body: { + channel: "homeTimeline", + id: this._channelID, + }, + }), + ); + break; + case "list": + this._client.send( + JSON.stringify({ + type: "connect", + body: { + channel: "userList", + id: this._channelID, + params: { + listId: this.listId, + }, + }, + }), + ); + break; + default: + this._client.send( + JSON.stringify({ + type: "connect", + body: { + channel: this.channel, + id: this._channelID, + }, + }), + ); + break; + } + } - /** - * Reconnects to the same endpoint. - */ + /** + * Reconnects to the same endpoint. + */ - private _reconnect() { - setTimeout(() => { - // Skip reconnect when client is connecting. - // https://github.com/websockets/ws/blob/7.2.1/lib/websocket.js#L365 - if (this._client && this._client.readyState === WS.CONNECTING) { - return - } + private _reconnect() { + setTimeout(() => { + // Skip reconnect when client is connecting. + // https://github.com/websockets/ws/blob/7.2.1/lib/websocket.js#L365 + if (this._client && this._client.readyState === WS.CONNECTING) { + return; + } - if (this._reconnectCurrentAttempts < this._reconnectMaxAttempts) { - this._reconnectCurrentAttempts++ - this._clearBinding() - if (this._client) { - // In reconnect, we want to close the connection immediately, - // because recoonect is necessary when some problems occur. - this._client.terminate() - } - // Call connect methods - console.log('Reconnecting') - this._client = this._connect() - this._bindSocket(this._client) - } - }, this._reconnectInterval) - } + if (this._reconnectCurrentAttempts < this._reconnectMaxAttempts) { + this._reconnectCurrentAttempts++; + this._clearBinding(); + if (this._client) { + // In reconnect, we want to close the connection immediately, + // because recoonect is necessary when some problems occur. + this._client.terminate(); + } + // Call connect methods + console.log("Reconnecting"); + this._client = this._connect(); + this._bindSocket(this._client); + } + }, this._reconnectInterval); + } - /** - * Clear binding event for websocket client. - */ - private _clearBinding() { - if (this._client) { - this._client.removeAllListeners('close') - this._client.removeAllListeners('pong') - this._client.removeAllListeners('open') - this._client.removeAllListeners('message') - this._client.removeAllListeners('error') - } - } + /** + * Clear binding event for websocket client. + */ + private _clearBinding() { + if (this._client) { + this._client.removeAllListeners("close"); + this._client.removeAllListeners("pong"); + this._client.removeAllListeners("open"); + this._client.removeAllListeners("message"); + this._client.removeAllListeners("error"); + } + } - /** - * Bind event for web socket client. - * @param client A WebSocket instance. - */ - private _bindSocket(client: WS) { - client.on('close', (code: number, _reason: Buffer) => { - if (code === 1000) { - this.emit('close', {}) - } else { - console.log(`Closed connection with ${code}`) - if (!this._connectionClosed) { - this._reconnect() - } - } - }) - client.on('pong', () => { - this._pongWaiting = false - this.emit('pong', {}) - this._pongReceivedTimestamp = dayjs() - // It is required to anonymous function since get this scope in checkAlive. - setTimeout(() => this._checkAlive(this._pongReceivedTimestamp), this._heartbeatInterval) - }) - client.on('open', () => { - this.emit('connect', {}) - this._channel() - // Call first ping event. - setTimeout(() => { - client.ping('') - }, 10000) - }) - client.on('message', (data: WS.Data, isBinary: boolean) => { - this.parser.parse(data, isBinary, this._channelID) - }) - client.on('error', (err: Error) => { - this.emit('error', err) - }) - } + /** + * Bind event for web socket client. + * @param client A WebSocket instance. + */ + private _bindSocket(client: WS) { + client.on("close", (code: number, _reason: Buffer) => { + if (code === 1000) { + this.emit("close", {}); + } else { + console.log(`Closed connection with ${code}`); + if (!this._connectionClosed) { + this._reconnect(); + } + } + }); + client.on("pong", () => { + this._pongWaiting = false; + this.emit("pong", {}); + this._pongReceivedTimestamp = dayjs(); + // It is required to anonymous function since get this scope in checkAlive. + setTimeout( + () => this._checkAlive(this._pongReceivedTimestamp), + this._heartbeatInterval, + ); + }); + client.on("open", () => { + this.emit("connect", {}); + this._channel(); + // Call first ping event. + setTimeout(() => { + client.ping(""); + }, 10000); + }); + client.on("message", (data: WS.Data, isBinary: boolean) => { + this.parser.parse(data, isBinary, this._channelID); + }); + client.on("error", (err: Error) => { + this.emit("error", err); + }); + } - /** - * Set up parser when receive message. - */ - private _setupParser() { - this.parser.on('update', (note: MisskeyAPI.Entity.Note) => { - this.emit('update', this._converter.note(note, this.baseUrlToHost(this.url))) - }) - this.parser.on('notification', (notification: MisskeyAPI.Entity.Notification) => { - this.emit('notification', this._converter.notification(notification, this.baseUrlToHost(this.url))) - }) - this.parser.on('conversation', (note: MisskeyAPI.Entity.Note) => { - this.emit('conversation', this._converter.noteToConversation(note, this.baseUrlToHost(this.url))) - }) - this.parser.on('error', (err: Error) => { - this.emit('parser-error', err) - }) - } + /** + * Set up parser when receive message. + */ + private _setupParser() { + this.parser.on("update", (note: MisskeyAPI.Entity.Note) => { + this.emit( + "update", + this._converter.note(note, this.baseUrlToHost(this.url)), + ); + }); + this.parser.on( + "notification", + (notification: MisskeyAPI.Entity.Notification) => { + this.emit( + "notification", + this._converter.notification( + notification, + this.baseUrlToHost(this.url), + ), + ); + }, + ); + this.parser.on("conversation", (note: MisskeyAPI.Entity.Note) => { + this.emit( + "conversation", + this._converter.noteToConversation(note, this.baseUrlToHost(this.url)), + ); + }); + this.parser.on("error", (err: Error) => { + this.emit("parser-error", err); + }); + } - /** - * Call ping and wait to pong. - */ - private _checkAlive(timestamp: Dayjs) { - const now: Dayjs = dayjs() - // Block multiple calling, if multiple pong event occur. - // It the duration is less than interval, through ping. - if (now.diff(timestamp) > this._heartbeatInterval - 1000 && !this._connectionClosed) { - // Skip ping when client is connecting. - // https://github.com/websockets/ws/blob/7.2.1/lib/websocket.js#L289 - if (this._client && this._client.readyState !== WS.CONNECTING) { - this._pongWaiting = true - this._client.ping('') - setTimeout(() => { - if (this._pongWaiting) { - this._pongWaiting = false - this._reconnect() - } - }, 10000) - } - } - } + /** + * Call ping and wait to pong. + */ + private _checkAlive(timestamp: Dayjs) { + const now: Dayjs = dayjs(); + // Block multiple calling, if multiple pong event occur. + // It the duration is less than interval, through ping. + if ( + now.diff(timestamp) > this._heartbeatInterval - 1000 && + !this._connectionClosed + ) { + // Skip ping when client is connecting. + // https://github.com/websockets/ws/blob/7.2.1/lib/websocket.js#L289 + if (this._client && this._client.readyState !== WS.CONNECTING) { + this._pongWaiting = true; + this._client.ping(""); + setTimeout(() => { + if (this._pongWaiting) { + this._pongWaiting = false; + this._reconnect(); + } + }, 10000); + } + } + } } /** @@ -331,84 +367,92 @@ export default class WebSocket extends EventEmitter implements WebSocketInterfac * This class provides parser for websocket message. */ export class Parser extends EventEmitter { - /** - * @param message Message body of websocket. - * @param channelID Parse only messages which has same channelID. - */ - public parse(data: WS.Data, isBinary: boolean, channelID: string) { - const message = isBinary ? data : data.toString() - if (typeof message !== 'string') { - this.emit('heartbeat', {}) - return - } + /** + * @param message Message body of websocket. + * @param channelID Parse only messages which has same channelID. + */ + public parse(data: WS.Data, isBinary: boolean, channelID: string) { + const message = isBinary ? data : data.toString(); + if (typeof message !== "string") { + this.emit("heartbeat", {}); + return; + } - if (message === '') { - this.emit('heartbeat', {}) - return - } + if (message === "") { + this.emit("heartbeat", {}); + return; + } - let obj: { - type: string - body: { - id: string - type: string - body: any - } - } - let body: { - id: string - type: string - body: any - } + let obj: { + type: string; + body: { + id: string; + type: string; + body: any; + }; + }; + let body: { + id: string; + type: string; + body: any; + }; - try { - obj = JSON.parse(message) - if (obj.type !== 'channel') { - return - } - if (!obj.body) { - return - } - body = obj.body - if (body.id !== channelID) { - return - } - } catch (err) { - this.emit('error', new Error(`Error parsing websocket reply: ${message}, error message: ${err}`)) - return - } + try { + obj = JSON.parse(message); + if (obj.type !== "channel") { + return; + } + if (!obj.body) { + return; + } + body = obj.body; + if (body.id !== channelID) { + return; + } + } catch (err) { + this.emit( + "error", + new Error( + `Error parsing websocket reply: ${message}, error message: ${err}`, + ), + ); + return; + } - switch (body.type) { - case 'note': - this.emit('update', body.body as MisskeyAPI.Entity.Note) - break - case 'notification': - this.emit('notification', body.body as MisskeyAPI.Entity.Notification) - break - case 'mention': { - const note = body.body as MisskeyAPI.Entity.Note - if (note.visibility === 'specified') { - this.emit('conversation', note) - } - break - } - // When renote and followed event, the same notification will be received. - case 'renote': - case 'followed': - case 'follow': - case 'unfollow': - case 'receiveFollowRequest': - case 'meUpdated': - case 'readAllNotifications': - case 'readAllUnreadSpecifiedNotes': - case 'readAllAntennas': - case 'readAllUnreadMentions': - case 'unreadNotification': - // Ignore these events - break - default: - this.emit('error', new Error(`Unknown event has received: ${JSON.stringify(body)}`)) - break - } - } + switch (body.type) { + case "note": + this.emit("update", body.body as MisskeyAPI.Entity.Note); + break; + case "notification": + this.emit("notification", body.body as MisskeyAPI.Entity.Notification); + break; + case "mention": { + const note = body.body as MisskeyAPI.Entity.Note; + if (note.visibility === "specified") { + this.emit("conversation", note); + } + break; + } + // When renote and followed event, the same notification will be received. + case "renote": + case "followed": + case "follow": + case "unfollow": + case "receiveFollowRequest": + case "meUpdated": + case "readAllNotifications": + case "readAllUnreadSpecifiedNotes": + case "readAllAntennas": + case "readAllUnreadMentions": + case "unreadNotification": + // Ignore these events + break; + default: + this.emit( + "error", + new Error(`Unknown event has received: ${JSON.stringify(body)}`), + ); + break; + } + } }