From 3e9e1635e2da940a96e9ea72023cb11bf87fc555 Mon Sep 17 00:00:00 2001 From: fruye Date: Sun, 23 Apr 2023 22:31:56 +0000 Subject: [PATCH] Implement getting and dismissing server announcements (#4) to be fair, I didn't test it on a real mastodon server. I just hope it works. I also don't think it matters much anyway as this is a fork for calckey? idk please let me know if I need to test it on mastodon too. It's a bit silly implementation as mfm is just escaped to plaintext and the announcement title is glued to `content`, but overall I think it works fine :) Reviewed-on: https://codeberg.org/calckey/megalodon/pulls/4 Co-authored-by: fruye Co-committed-by: fruye --- megalodon/src/entities/announcement.ts | 34 +++++++++++++++ megalodon/src/entity.ts | 1 + megalodon/src/mastodon.ts | 20 +++++++++ megalodon/src/mastodon/api_client.ts | 12 ++++++ .../src/mastodon/entities/announcement.ts | 41 +++++++++++++++++++ megalodon/src/mastodon/entity.ts | 1 + megalodon/src/megalodon.ts | 16 ++++++++ megalodon/src/misskey.ts | 20 +++++++++ megalodon/src/misskey/api_client.ts | 29 +++++++++++++ .../src/misskey/entities/announcement.ts | 10 +++++ megalodon/src/misskey/entity.ts | 1 + megalodon/src/pleroma.ts | 20 +++++++++ megalodon/src/pleroma/api_client.ts | 2 + .../src/pleroma/entities/announcement.ts | 34 +++++++++++++++ megalodon/src/pleroma/entity.ts | 1 + 15 files changed, 242 insertions(+) create mode 100644 megalodon/src/entities/announcement.ts create mode 100644 megalodon/src/mastodon/entities/announcement.ts create mode 100644 megalodon/src/misskey/entities/announcement.ts create mode 100644 megalodon/src/pleroma/entities/announcement.ts diff --git a/megalodon/src/entities/announcement.ts b/megalodon/src/entities/announcement.ts new file mode 100644 index 0000000..00fa8a0 --- /dev/null +++ b/megalodon/src/entities/announcement.ts @@ -0,0 +1,34 @@ +/// +/// +/// + +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 AnnouncementAccount = { + id: string + username: string + url: string + acct: string + } + + export type AnnouncementStatus = { + id: string + url: string + } +} diff --git a/megalodon/src/entity.ts b/megalodon/src/entity.ts index b665354..39dd4b6 100644 --- a/megalodon/src/entity.ts +++ b/megalodon/src/entity.ts @@ -1,5 +1,6 @@ /// /// +/// /// /// /// diff --git a/megalodon/src/mastodon.ts b/megalodon/src/mastodon.ts index c00a51f..0ac8f80 100644 --- a/megalodon/src/mastodon.ts +++ b/megalodon/src/mastodon.ts @@ -2221,6 +2221,26 @@ export default class Mastodon implements MegalodonInterface { }) } + // ====================================== + // instance/announcements + // ====================================== + public async getInstanceAnnouncements(with_dismissed?: boolean | null): Promise>> { + let params = {} + if (with_dismissed) { + params = Object.assign(params, { + with_dismissed + }) + } + return this.client.get>('/api/v1/announcements', params).then(res => ({ + ...res, + data: res.data.map(t => MastodonAPI.Converter.announcement(t)) + })) + } + + public async dismissInstanceAnnouncement(id: string): Promise> { + return this.client.post<{}>(`/api/v1/announcements/${id}/dismiss`) + } + // ====================================== // Emoji reactions // ====================================== diff --git a/megalodon/src/mastodon/api_client.ts b/megalodon/src/mastodon/api_client.ts index 5e92596..291dcaf 100644 --- a/megalodon/src/mastodon/api_client.ts +++ b/megalodon/src/mastodon/api_client.ts @@ -444,6 +444,7 @@ namespace MastodonAPI { export namespace Entity { export type Account = MastodonEntity.Account export type Activity = MastodonEntity.Activity + export type Announcement = MastodonEntity.Announcement export type Application = MastodonEntity.Application export type AsyncAttachment = MegalodonEntity.AsyncAttachment export type Attachment = MastodonEntity.Attachment @@ -466,6 +467,7 @@ namespace MastodonAPI { export type Preferences = MastodonEntity.Preferences export type PushSubscription = MastodonEntity.PushSubscription export type Relationship = MastodonEntity.Relationship + export type Reaction = MastodonEntity.Reaction export type Report = MastodonEntity.Report export type Results = MastodonEntity.Results export type ScheduledStatus = MastodonEntity.ScheduledStatus @@ -523,6 +525,10 @@ namespace MastodonAPI { export const account = (a: Entity.Account): MegalodonEntity.Account => a export const activity = (a: Entity.Activity): MegalodonEntity.Activity => a + export const announcement = (a: Entity.Announcement): MegalodonEntity.Announcement => ({ + ...a, + reactions: a.reactions.map(r => reaction(r)) + }) export const application = (a: Entity.Application): MegalodonEntity.Application => a export const attachment = (a: Entity.Attachment): MegalodonEntity.Attachment => a export const async_attachment = (a: Entity.AsyncAttachment) => { @@ -586,6 +592,12 @@ namespace MastodonAPI { export const preferences = (p: Entity.Preferences): MegalodonEntity.Preferences => p export const push_subscription = (p: Entity.PushSubscription): MegalodonEntity.PushSubscription => p export const relationship = (r: Entity.Relationship): MegalodonEntity.Relationship => r + export const reaction = (r: Entity.Reaction): MegalodonEntity.Reaction => ({ + count: r.count, + me: r.me ?? false, + name: r.name, + url: r.url, + }) export const report = (r: Entity.Report): MegalodonEntity.Report => r export const results = (r: Entity.Results): MegalodonEntity.Results => ({ accounts: r.accounts.map(a => account(a)), diff --git a/megalodon/src/mastodon/entities/announcement.ts b/megalodon/src/mastodon/entities/announcement.ts new file mode 100644 index 0000000..d55b635 --- /dev/null +++ b/megalodon/src/mastodon/entities/announcement.ts @@ -0,0 +1,41 @@ +/// +/// + +namespace MastodonEntity { + 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 AnnouncementStatus = { + id: string + url: string + } + + export type Reaction = { + name: string + count: number + me?: boolean + url?: string + static_url?: string + } +} diff --git a/megalodon/src/mastodon/entity.ts b/megalodon/src/mastodon/entity.ts index ef8baf8..dc96c6e 100644 --- a/megalodon/src/mastodon/entity.ts +++ b/megalodon/src/mastodon/entity.ts @@ -1,5 +1,6 @@ /// /// +/// /// /// /// diff --git a/megalodon/src/megalodon.ts b/megalodon/src/megalodon.ts index 3a9a5f5..1096cc5 100644 --- a/megalodon/src/megalodon.ts +++ b/megalodon/src/megalodon.ts @@ -1256,6 +1256,22 @@ export interface MegalodonInterface { */ getInstanceCustomEmojis(): Promise>> + // ====================================== + // instance/announcements + // ====================================== + /** + * GET /api/v1/announcements + * + * @param with_dismissed Include announcements dismissed by the user. Defaults to false. + * @return Array of announcements. + */ + getInstanceAnnouncements(with_dismissed?: boolean | null): Promise>> + + /** + * POST /api/v1/announcements/:id/dismiss + */ + dismissInstanceAnnouncement(id: string): Promise> + // ====================================== // Emoji reactions // ====================================== diff --git a/megalodon/src/misskey.ts b/megalodon/src/misskey.ts index 3292375..dbe4c52 100644 --- a/megalodon/src/misskey.ts +++ b/megalodon/src/misskey.ts @@ -2107,6 +2107,26 @@ export default class Misskey implements MegalodonInterface { .then(res => ({ ...res, data: res.data.emojis.map(e => MisskeyAPI.Converter.emoji(e)) })) } + // ====================================== + // instance/announcements + // ====================================== + public async getInstanceAnnouncements(with_dismissed?: boolean | null): Promise>> { + let params = {} + if (with_dismissed) { + params = Object.assign(params, { + withUnreads: with_dismissed + }) + } + return this.client.post>('/api/announcements', params).then(res => ({ + ...res, + data: res.data.map(t => MisskeyAPI.Converter.announcement(t)) + })) + } + + public async dismissInstanceAnnouncement(id: string): Promise> { + return this.client.post<{}>('/api/i/read-announcement', { announcementId: id }) + } + // ====================================== // Emoji reactions // ====================================== diff --git a/megalodon/src/misskey/api_client.ts b/megalodon/src/misskey/api_client.ts index 99ca58b..ba27f49 100644 --- a/megalodon/src/misskey/api_client.ts +++ b/megalodon/src/misskey/api_client.ts @@ -14,6 +14,7 @@ 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 @@ -384,6 +385,34 @@ namespace MisskeyAPI { moved: null } + export const announcement = (a: Entity.Announcement): MegalodonEntity.Announcement => { + // Reused from Note converter. + const escapeHTML = (text: string) => text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/`/g, '`') + .replace(/\r?\n/g, '
'); + return { + id: a.id, + content: `

${escapeHTML(a.title)}

${escapeHTML(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: [], + }; + } + export const notification = (n: Entity.Notification, host: string): MegalodonEntity.Notification => { let notification = { id: n.id, diff --git a/megalodon/src/misskey/entities/announcement.ts b/megalodon/src/misskey/entities/announcement.ts new file mode 100644 index 0000000..95100cb --- /dev/null +++ b/megalodon/src/misskey/entities/announcement.ts @@ -0,0 +1,10 @@ +namespace MisskeyEntity { + export type Announcement = { + id: string + createdAt: string + updatedAt: string + text: string + title: string + isRead?: boolean + } +} diff --git a/megalodon/src/misskey/entity.ts b/megalodon/src/misskey/entity.ts index 3ce99ee..07a49b7 100644 --- a/megalodon/src/misskey/entity.ts +++ b/megalodon/src/misskey/entity.ts @@ -1,4 +1,5 @@ /// +/// /// /// /// diff --git a/megalodon/src/pleroma.ts b/megalodon/src/pleroma.ts index 080971d..030593c 100644 --- a/megalodon/src/pleroma.ts +++ b/megalodon/src/pleroma.ts @@ -2229,6 +2229,26 @@ export default class Pleroma implements MegalodonInterface { }) } + // ====================================== + // instance/announcements + // ====================================== + public async getInstanceAnnouncements(with_dismissed?: boolean | null): Promise>> { + let params = {} + if (with_dismissed) { + params = Object.assign(params, { + with_dismissed + }) + } + return this.client.get>('/api/v1/announcements', params).then(res => ({ + ...res, + data: res.data.map(t => PleromaAPI.Converter.announcement(t)) + })) + } + + public async dismissInstanceAnnouncement(id: string): Promise> { + return this.client.post<{}>(`/api/v1/announcements/${id}/dismiss`) + } + // ====================================== // Emoji reactions // ====================================== diff --git a/megalodon/src/pleroma/api_client.ts b/megalodon/src/pleroma/api_client.ts index 38f54b8..e965d3e 100644 --- a/megalodon/src/pleroma/api_client.ts +++ b/megalodon/src/pleroma/api_client.ts @@ -15,6 +15,7 @@ namespace PleromaAPI { export namespace Entity { export type Account = PleromaEntity.Account export type Activity = PleromaEntity.Activity + export type Announcement = PleromaEntity.Announcement export type Application = PleromaEntity.Application export type AsyncAttachment = PleromaEntity.AsyncAttachment export type Attachment = PleromaEntity.Attachment @@ -94,6 +95,7 @@ namespace PleromaAPI { export const account = (a: Entity.Account): MegalodonEntity.Account => a export const activity = (a: Entity.Activity): MegalodonEntity.Activity => a + export const announcement = (a: Entity.Announcement): MegalodonEntity.Announcement => a export const application = (a: Entity.Application): MegalodonEntity.Application => a export const attachment = (a: Entity.Attachment): MegalodonEntity.Attachment => a export const async_attachment = (a: Entity.AsyncAttachment) => { diff --git a/megalodon/src/pleroma/entities/announcement.ts b/megalodon/src/pleroma/entities/announcement.ts new file mode 100644 index 0000000..72417af --- /dev/null +++ b/megalodon/src/pleroma/entities/announcement.ts @@ -0,0 +1,34 @@ +/// +/// +/// + +namespace PleromaEntity { + 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 AnnouncementStatus = { + id: string + url: string + } +} diff --git a/megalodon/src/pleroma/entity.ts b/megalodon/src/pleroma/entity.ts index a2567b3..f2d27da 100644 --- a/megalodon/src/pleroma/entity.ts +++ b/megalodon/src/pleroma/entity.ts @@ -1,5 +1,6 @@ /// /// +/// /// /// ///