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;
+ }
+ }
}