diff --git a/packages/backend/src/server/api/mastodon/converters/announcement.ts b/packages/backend/src/server/api/mastodon/converters/announcement.ts
new file mode 100644
index 000000000..9b5445cef
--- /dev/null
+++ b/packages/backend/src/server/api/mastodon/converters/announcement.ts
@@ -0,0 +1,27 @@
+import { Announcement } from "@/models/entities/announcement.js";
+import { ILocalUser } from "@/models/entities/user.js";
+import { awaitAll } from "@/prelude/await-all";
+import { AnnouncementReads } from "@/models/index.js";
+import { MfmHelpers } from "@/server/api/mastodon/helpers/mfm.js";
+import mfm from "mfm-js";
+
+export class AnnouncementConverter {
+ public static encode(announcement: Announcement, isRead: boolean): MastodonEntity.Announcement {
+ return {
+ id: announcement.id,
+ content: `
${MfmHelpers.toHtml(mfm.parse(announcement.title), []) ?? 'Announcement'}
${MfmHelpers.toHtml(mfm.parse(announcement.text), []) ?? ''}`,
+ starts_at: null,
+ ends_at: null,
+ published: true,
+ all_day: false,
+ published_at: announcement.createdAt.toISOString(),
+ updated_at: announcement.updatedAt?.toISOString() ?? announcement.createdAt.toISOString(),
+ read: isRead,
+ mentions: [], //FIXME
+ statuses: [],
+ tags: [],
+ emojis: [], //FIXME
+ reactions: [],
+ };
+ }
+}
\ No newline at end of file
diff --git a/packages/backend/src/server/api/mastodon/endpoints/misc.ts b/packages/backend/src/server/api/mastodon/endpoints/misc.ts
index 5e157c0cd..0f47aa7b9 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/misc.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/misc.ts
@@ -1,8 +1,11 @@
import Router from "@koa/router";
import { getClient } from "@/server/api/mastodon/index.js";
-import { convertId, IdType } from "@/misc/convert-id.js";
-import { convertAnnouncementId } from "@/server/api/mastodon/converters.js";
import { MiscHelpers } from "@/server/api/mastodon/helpers/misc.js";
+import authenticate from "@/server/api/authenticate.js";
+import { argsToBools } from "@/server/api/mastodon/endpoints/timeline.js";
+import { Announcements } from "@/models/index.js";
+import { convertAnnouncementId } from "@/server/api/mastodon/converters.js";
+import { convertId, IdType } from "@/misc/convert-id.js";
export function setupEndpointsMisc(router: Router): void {
router.get("/v1/custom_emojis", async (ctx) => {
@@ -30,36 +33,49 @@ export function setupEndpointsMisc(router: Router): void {
});
router.get("/v1/announcements", async (ctx) => {
- const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
- const accessTokens = ctx.request.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
try {
- const data = await client.getInstanceAnnouncements();
- ctx.body = data.data.map((announcement) =>
- convertAnnouncementId(announcement),
- );
+ const auth = await authenticate(ctx.headers.authorization, null);
+ const user = auth[0] ?? null;
+
+ if (!user) {
+ ctx.status = 401;
+ return;
+ }
+
+ const args = argsToBools(ctx.query, ['with_dismissed']);
+ ctx.body = await MiscHelpers.getAnnouncements(user, args['with_dismissed'])
+ .then(p => p.map(x => convertAnnouncementId(x)));
} catch (e: any) {
- console.error(e);
- ctx.status = 401;
- ctx.body = e.response.data;
+ ctx.status = 500;
+ ctx.body = { error: e.message };
}
});
router.post<{ Params: { id: string } }>(
"/v1/announcements/:id/dismiss",
async (ctx) => {
- const BASE_URL = `${ctx.request.protocol}://${ctx.request.hostname}`;
- const accessTokens = ctx.request.headers.authorization;
- const client = getClient(BASE_URL, accessTokens);
try {
- const data = await client.dismissInstanceAnnouncement(
- convertId(ctx.params.id, IdType.IceshrimpId),
- );
- ctx.body = data.data;
+ const auth = await authenticate(ctx.headers.authorization, null);
+ const user = auth[0] ?? null;
+
+ if (!user) {
+ ctx.status = 401;
+ return;
+ }
+
+ const id = convertId(ctx.params.id, IdType.IceshrimpId);
+ const announcement = await Announcements.findOneBy({id: id});
+
+ if (!announcement) {
+ ctx.status = 404;
+ return;
+ }
+
+ await MiscHelpers.dismissAnnouncement(announcement, user);
+ ctx.body = {};
} catch (e: any) {
- console.error(e);
- ctx.status = 401;
- ctx.body = e.response.data;
+ ctx.status = 500;
+ ctx.body = { error: e.message };
}
},
);
diff --git a/packages/backend/src/server/api/mastodon/helpers/misc.ts b/packages/backend/src/server/api/mastodon/helpers/misc.ts
index 51666b68c..acefa9d4f 100644
--- a/packages/backend/src/server/api/mastodon/helpers/misc.ts
+++ b/packages/backend/src/server/api/mastodon/helpers/misc.ts
@@ -1,11 +1,15 @@
import config from "@/config/index.js";
import { FILE_TYPE_BROWSERSAFE, MAX_NOTE_TEXT_LENGTH } from "@/const.js";
import { fetchMeta } from "@/misc/fetch-meta.js";
-import { Instances, Notes, Users } from "@/models/index.js";
+import { AnnouncementReads, Announcements, Instances, Notes, Users } from "@/models/index.js";
import { IsNull } from "typeorm";
import { awaitAll } from "@/prelude/await-all.js";
import { UserConverter } from "@/server/api/mastodon/converters/user.js";
import { convertAccountId } from "@/server/api/mastodon/converters.js";
+import { Announcement } from "@/models/entities/announcement.js";
+import { ILocalUser } from "@/models/entities/user.js";
+import { AnnouncementConverter } from "@/server/api/mastodon/converters/announcement.js";
+import { genId } from "@/misc/gen-id.js";
export class MiscHelpers {
public static async getInstance(): Promise {
@@ -81,4 +85,42 @@ export class MiscHelpers {
return awaitAll(res);
}
+
+ public static async getAnnouncements(user: ILocalUser, includeRead: boolean = false): Promise {
+ if (includeRead) {
+ const [announcements, reads] = await Promise.all([
+ Announcements.createQueryBuilder("announcement")
+ .orderBy({"announcement.id": "DESC"})
+ .getMany(),
+ AnnouncementReads.findBy({userId: user.id})
+ .then(p => p.map(x => x.announcementId))
+ ]);
+
+ return announcements.map(p => AnnouncementConverter.encode(p, reads.includes(p.id)));
+ }
+
+ const sq = AnnouncementReads.createQueryBuilder("reads")
+ .select("reads.announcementId")
+ .where("reads.userId = :userId");
+
+ const query = Announcements.createQueryBuilder("announcement")
+ .where(`announcement.id NOT IN (${sq.getQuery()})`)
+ .orderBy({"announcement.id": "DESC"})
+ .setParameter("userId", user.id);
+
+ return query.getMany()
+ .then(p => p.map(x => AnnouncementConverter.encode(x, false)));
+ }
+
+ public static async dismissAnnouncement(announcement: Announcement, user: ILocalUser): Promise {
+ const exists = await AnnouncementReads.exist({where: {userId: user.id, announcementId: announcement.id}});
+ if (!exists) {
+ await AnnouncementReads.insert({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ announcementId: announcement.id
+ });
+ }
+ }
}
\ No newline at end of file