From 0cf2e71b2e361b050b2d16224ce18a5db99abc64 Mon Sep 17 00:00:00 2001 From: fruye Date: Sun, 30 Apr 2023 19:34:52 +0000 Subject: [PATCH] Use numeric ids everywhere in mastodon API (#9970) Reviewed-on: https://codeberg.org/calckey/calckey/pulls/9970 Co-authored-by: fruye Co-committed-by: fruye --- packages/backend/src/server/api/index.ts | 5 +- .../mastodon/ApiMastodonCompatibleService.ts | 10 +- .../src/server/api/mastodon/converters.ts | 44 +++++ .../server/api/mastodon/endpoints/account.ts | 163 ++++-------------- .../server/api/mastodon/endpoints/filter.ts | 20 ++- .../api/mastodon/endpoints/notifications.ts | 17 +- .../server/api/mastodon/endpoints/search.ts | 21 ++- .../server/api/mastodon/endpoints/status.ts | 97 +++++++---- .../server/api/mastodon/endpoints/timeline.ts | 73 +++++--- 9 files changed, 238 insertions(+), 212 deletions(-) create mode 100644 packages/backend/src/server/api/mastodon/converters.ts diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 06b3ea4ef..3568a27b2 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -29,6 +29,7 @@ import { convertId, IdConvertType as IdType, } from "../../../native-utils/built/index.js"; +import { convertAttachment } from "./mastodon/converters.js"; // re-export native rust id conversion (function and enum) export { IdType, convertId }; @@ -93,7 +94,7 @@ mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => { return; } const data = await client.uploadMedia(multipartData); - ctx.body = data.data; + ctx.body = convertAttachment(data.data as Entity.Attachment); } catch (e: any) { console.error(e); ctx.status = 401; @@ -112,7 +113,7 @@ mastoFileRouter.post("/v2/media", upload.single("file"), async (ctx) => { return; } const data = await client.uploadMedia(multipartData); - ctx.body = data.data; + ctx.body = convertAttachment(data.data as Entity.Attachment); } catch (e: any) { console.error(e); ctx.status = 401; diff --git a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts index e8dfe5281..0c59b38f4 100644 --- a/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts +++ b/packages/backend/src/server/api/mastodon/ApiMastodonCompatibleService.ts @@ -8,6 +8,8 @@ import { apiTimelineMastodon } from "./endpoints/timeline.js"; import { apiNotificationsMastodon } from "./endpoints/notifications.js"; import { apiSearchMastodon } from "./endpoints/search.js"; import { getInstance } from "./endpoints/meta.js"; +import { convertAnnouncement, convertFilter } from "./converters.js"; +import { convertId, IdType } from "../index.js"; export function getClient( BASE_URL: string, @@ -68,7 +70,7 @@ export function apiMastodonCompatible(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getInstanceAnnouncements(); - ctx.body = data.data; + ctx.body = data.data.map(announcement => convertAnnouncement(announcement)); } catch (e: any) { console.error(e); ctx.status = 401; @@ -83,7 +85,9 @@ export function apiMastodonCompatible(router: Router): void { const accessTokens = ctx.request.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.dismissInstanceAnnouncement(ctx.params.id); + const data = await client.dismissInstanceAnnouncement( + convertId(ctx.params.id, IdType.CalckeyId), + ); ctx.body = data.data; } catch (e: any) { console.error(e); @@ -100,7 +104,7 @@ export function apiMastodonCompatible(router: Router): void { // displayed without being logged in try { const data = await client.getFilters(); - ctx.body = data.data; + ctx.body = data.data.map(filter => convertFilter(filter)); } catch (e: any) { console.error(e); ctx.status = 401; diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts new file mode 100644 index 000000000..d9a4f90ef --- /dev/null +++ b/packages/backend/src/server/api/mastodon/converters.ts @@ -0,0 +1,44 @@ +import { Entity } from "@calckey/megalodon"; +import { convertId, IdType } from "../index.js"; + +function simpleConvert(data: any) { + data.id = convertId(data.id, IdType.MastodonId); + return data; +} + +export function convertAccount(account: Entity.Account) { return simpleConvert(account); } +export function convertAnnouncement(announcement: Entity.Announcement) { return simpleConvert(announcement); } +export function convertAttachment(attachment: Entity.Attachment) { return simpleConvert(attachment); } +export function convertFilter(filter: Entity.Filter) { return simpleConvert(filter); } +export function convertList(list: Entity.List) { return simpleConvert(list); } + +export function convertNotification(notification: Entity.Notification) { + notification.account = convertAccount(notification.account); + notification.id = convertId(notification.id, IdType.MastodonId); + if (notification.status) + notification.status = convertStatus(notification.status); + return notification; +} + +export function convertPoll(poll: Entity.Poll) { return simpleConvert(poll); } +export function convertRelationship(relationship: Entity.Relationship) { return simpleConvert(relationship); } + +export function convertStatus(status: Entity.Status) { + status.account = convertAccount(status.account); + status.id = convertId(status.id, IdType.MastodonId); + if (status.in_reply_to_account_id) + status.in_reply_to_account_id = convertId(status.in_reply_to_account_id, IdType.MastodonId); + if (status.in_reply_to_id) + status.in_reply_to_id = convertId(status.in_reply_to_id, IdType.MastodonId); + status.media_attachments = status.media_attachments.map(attachment => convertAttachment(attachment)); + status.mentions = status.mentions.map(mention => ({ + ...mention, + id: convertId(mention.id, IdType.MastodonId), + })); + if (status.poll) + status.poll = convertPoll(status.poll); + if (status.reblog) + status.reblog = convertStatus(status.reblog); + + return status; +} diff --git a/packages/backend/src/server/api/mastodon/endpoints/account.ts b/packages/backend/src/server/api/mastodon/endpoints/account.ts index 749058193..2984c20e3 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/account.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/account.ts @@ -3,8 +3,9 @@ import { resolveUser } from "@/remote/resolve-user.js"; import Router from "@koa/router"; import { FindOptionsWhere, IsNull } from "typeorm"; import { getClient } from "../ApiMastodonCompatibleService.js"; -import { argsToBools, limitToInt } from "./timeline.js"; +import { argsToBools, convertTimelinesArgsId, limitToInt } from "./timeline.js"; import { convertId, IdType } from "../../index.js"; +import { convertAccount, convertList, convertRelationship, convertStatus } from "../converters.js"; const relationshipModel = { id: "", @@ -62,9 +63,7 @@ export function apiAccountMastodon(router: Router): void { const data = await client.updateCredentials( (ctx.request as any).body as any, ); - let resp = data.data; - resp.id = convertId(resp.id, IdType.MastodonId); - ctx.body = resp; + ctx.body = convertAccount(data.data); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -81,9 +80,7 @@ export function apiAccountMastodon(router: Router): void { (ctx.request.query as any).acct, "accounts", ); - let resp = data.data.accounts[0]; - resp.id = convertId(resp.id, IdType.MastodonId); - ctx.body = resp; + ctx.body = convertAccount(data.data.accounts[0]); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -115,11 +112,7 @@ export function apiAccountMastodon(router: Router): void { } const data = await client.getRelationships(reqIds); - let resp = data.data; - for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { - resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); - } - ctx.body = resp; + ctx.body = data.data.map(relationship => convertRelationship(relationship)); } catch (e: any) { console.error(e); let data = e.response.data; @@ -136,9 +129,7 @@ export function apiAccountMastodon(router: Router): void { try { const calcId = convertId(ctx.params.id, IdType.CalckeyId); const data = await client.getAccount(calcId); - let resp = data.data; - resp.id = convertId(resp.id, IdType.MastodonId); - ctx.body = resp; + ctx.body = convertAccount(data.data); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -155,27 +146,9 @@ export function apiAccountMastodon(router: Router): void { try { const data = await client.getAccountStatuses( convertId(ctx.params.id, IdType.CalckeyId), - argsToBools(limitToInt(ctx.query as any)), + convertTimelinesArgsId(argsToBools(limitToInt(ctx.query as any))), ); - let resp = data.data; - for (let statIdx = 0; statIdx < resp.length; statIdx++) { - resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId); - resp[statIdx].in_reply_to_account_id = resp[statIdx] - .in_reply_to_account_id - ? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId) - : null; - resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id - ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) - : null; - let mentions = resp[statIdx].mentions; - for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) { - resp[statIdx].mentions[mtnIdx].id = convertId( - mentions[mtnIdx].id, - IdType.MastodonId, - ); - } - } - ctx.body = resp; + ctx.body = data.data.map(status => convertStatus(status)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -193,13 +166,9 @@ export function apiAccountMastodon(router: Router): void { try { const data = await client.getAccountFollowers( convertId(ctx.params.id, IdType.CalckeyId), - limitToInt(ctx.query as any), + convertTimelinesArgsId(limitToInt(ctx.query as any)), ); - let resp = data.data; - for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { - resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); - } - ctx.body = resp; + ctx.body = data.data.map(account => convertAccount(account)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -217,13 +186,9 @@ export function apiAccountMastodon(router: Router): void { try { const data = await client.getAccountFollowing( convertId(ctx.params.id, IdType.CalckeyId), - limitToInt(ctx.query as any), + convertTimelinesArgsId(limitToInt(ctx.query as any)), ); - let resp = data.data; - for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { - resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); - } - ctx.body = resp; + ctx.body = data.data.map(account => convertAccount(account)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -239,8 +204,10 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getAccountLists(ctx.params.id); - ctx.body = data.data; + const data = await client.getAccountLists( + convertId(ctx.params.id, IdType.CalckeyId) + ); + ctx.body = data.data.map(list => convertList(list)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -259,9 +226,8 @@ export function apiAccountMastodon(router: Router): void { const data = await client.followAccount( convertId(ctx.params.id, IdType.CalckeyId), ); - let acct = data.data; + let acct = convertRelationship(data.data); acct.following = true; - acct.id = convertId(acct.id, IdType.MastodonId); ctx.body = acct; } catch (e: any) { console.error(e); @@ -281,8 +247,7 @@ export function apiAccountMastodon(router: Router): void { const data = await client.unfollowAccount( convertId(ctx.params.id, IdType.CalckeyId), ); - let acct = data.data; - acct.id = convertId(acct.id, IdType.MastodonId); + let acct = convertRelationship(data.data); acct.following = false; ctx.body = acct; } catch (e: any) { @@ -303,9 +268,7 @@ export function apiAccountMastodon(router: Router): void { const data = await client.blockAccount( convertId(ctx.params.id, IdType.CalckeyId), ); - let resp = data.data; - resp.id = convertId(resp.id, IdType.MastodonId); - ctx.body = resp; + ctx.body = convertRelationship(data.data); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -324,9 +287,7 @@ export function apiAccountMastodon(router: Router): void { const data = await client.unblockAccount( convertId(ctx.params.id, IdType.MastodonId), ); - let resp = data.data; - resp.id = convertId(resp.id, IdType.MastodonId); - ctx.body = resp; + ctx.body = convertRelationship(data.data); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -346,9 +307,7 @@ export function apiAccountMastodon(router: Router): void { convertId(ctx.params.id, IdType.CalckeyId), (ctx.request as any).body as any, ); - let resp = data.data; - resp.id = convertId(resp.id, IdType.MastodonId); - ctx.body = resp; + ctx.body = convertRelationship(data.data); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -367,9 +326,7 @@ export function apiAccountMastodon(router: Router): void { const data = await client.unmuteAccount( convertId(ctx.params.id, IdType.CalckeyId), ); - let resp = data.data; - resp.id = convertId(resp.id, IdType.MastodonId); - ctx.body = resp; + ctx.body = convertRelationship(data.data); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -384,27 +341,9 @@ export function apiAccountMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = (await client.getBookmarks( - limitToInt(ctx.query as any), - )) as any; - let resp = data.data; - for (let statIdx = 0; statIdx < resp.length; statIdx++) { - resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId); - resp[statIdx].in_reply_to_account_id = resp[statIdx] - .in_reply_to_account_id - ? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId) - : null; - resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id - ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) - : null; - let mentions = resp[statIdx].mentions; - for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) { - resp[statIdx].mentions[mtnIdx].id = convertId( - mentions[mtnIdx].id, - IdType.MastodonId, - ); - } - } - ctx.body = resp; + convertTimelinesArgsId(limitToInt(ctx.query as any)), + )); + ctx.body = data.data.map(status => convertStatus(status)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -417,26 +356,8 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getFavourites(limitToInt(ctx.query as any)); - let resp = data.data; - for (let statIdx = 0; statIdx < resp.length; statIdx++) { - resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId); - resp[statIdx].in_reply_to_account_id = resp[statIdx] - .in_reply_to_account_id - ? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId) - : null; - resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id - ? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId) - : null; - let mentions = resp[statIdx].mentions; - for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) { - resp[statIdx].mentions[mtnIdx].id = convertId( - mentions[mtnIdx].id, - IdType.MastodonId, - ); - } - } - ctx.body = resp; + const data = await client.getFavourites(convertTimelinesArgsId(limitToInt(ctx.query as any))); + ctx.body = data.data.map(status => convertStatus(status)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -449,12 +370,8 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getMutes(limitToInt(ctx.query as any)); - let resp = data.data; - for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { - resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); - } - ctx.body = resp; + const data = await client.getMutes(convertTimelinesArgsId(limitToInt(ctx.query as any))); + ctx.body = data.data.map(account => convertAccount(account)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -467,12 +384,8 @@ export function apiAccountMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getBlocks(limitToInt(ctx.query as any)); - let resp = data.data; - for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { - resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); - } - ctx.body = resp; + const data = await client.getBlocks(convertTimelinesArgsId(limitToInt(ctx.query as any))); + ctx.body = data.data.map(account => convertAccount(account)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -488,11 +401,7 @@ export function apiAccountMastodon(router: Router): void { const data = await client.getFollowRequests( ((ctx.query as any) || { limit: 20 }).limit, ); - let resp = data.data; - for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { - resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); - } - ctx.body = resp; + ctx.body = data.data.map(account => convertAccount(account)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -510,9 +419,7 @@ export function apiAccountMastodon(router: Router): void { const data = await client.acceptFollowRequest( convertId(ctx.params.id, IdType.CalckeyId), ); - let resp = data.data; - resp.id = convertId(resp.id, IdType.MastodonId); - ctx.body = resp; + ctx.body = convertRelationship(data.data); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -531,9 +438,7 @@ export function apiAccountMastodon(router: Router): void { const data = await client.rejectFollowRequest( convertId(ctx.params.id, IdType.CalckeyId), ); - let resp = data.data; - resp.id = convertId(resp.id, IdType.MastodonId); - ctx.body = resp; + ctx.body = convertRelationship(data.data); } catch (e: any) { console.error(e); console.error(e.response.data); diff --git a/packages/backend/src/server/api/mastodon/endpoints/filter.ts b/packages/backend/src/server/api/mastodon/endpoints/filter.ts index d21bc1d33..7343fc337 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/filter.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/filter.ts @@ -1,6 +1,8 @@ import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import Router from "@koa/router"; import { getClient } from "../ApiMastodonCompatibleService.js"; +import { IdType, convertId } from "../../index.js"; +import { convertFilter } from "../converters.js"; export function apiFilterMastodon(router: Router): void { router.get("/v1/filters", async (ctx) => { @@ -10,7 +12,7 @@ export function apiFilterMastodon(router: Router): void { const body: any = ctx.request.body; try { const data = await client.getFilters(); - ctx.body = data.data; + ctx.body = data.data.map(filter => convertFilter(filter)); } catch (e: any) { console.error(e); ctx.status = 401; @@ -24,8 +26,10 @@ export function apiFilterMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); const body: any = ctx.request.body; try { - const data = await client.getFilter(ctx.params.id); - ctx.body = data.data; + const data = await client.getFilter( + convertId(ctx.params.id, IdType.CalckeyId) + ); + ctx.body = convertFilter(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -40,7 +44,7 @@ export function apiFilterMastodon(router: Router): void { const body: any = ctx.request.body; try { const data = await client.createFilter(body.phrase, body.context, body); - ctx.body = data.data; + ctx.body = convertFilter(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -55,11 +59,11 @@ export function apiFilterMastodon(router: Router): void { const body: any = ctx.request.body; try { const data = await client.updateFilter( - ctx.params.id, + convertId(ctx.params.id, IdType.CalckeyId), body.phrase, body.context, ); - ctx.body = data.data; + ctx.body = convertFilter(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -73,7 +77,9 @@ export function apiFilterMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); const body: any = ctx.request.body; try { - const data = await client.deleteFilter(ctx.params.id); + const data = await client.deleteFilter( + convertId(ctx.params.id, IdType.CalckeyId) + ); ctx.body = data.data; } catch (e: any) { console.error(e); diff --git a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts index 8508f1d48..868377b78 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/notifications.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/notifications.ts @@ -1,8 +1,10 @@ import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import Router from "@koa/router"; import { koaBody } from "koa-body"; +import { convertId, IdType } from "../../index.js"; import { getClient } from "../ApiMastodonCompatibleService.js"; -import { toTextWithReaction } from "./timeline.js"; +import { convertTimelinesArgsId, toTextWithReaction } from "./timeline.js"; +import { convertNotification } from "../converters.js"; function toLimitToInt(q: any) { if (q.limit) if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10); return q; @@ -15,9 +17,10 @@ export function apiNotificationsMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); const body: any = ctx.request.body; try { - const data = await client.getNotifications(toLimitToInt(ctx.query)); + const data = await client.getNotifications(convertTimelinesArgsId(toLimitToInt(ctx.query))); const notfs = data.data; const ret = notfs.map((n) => { + n = convertNotification(n); if (n.type !== "follow" && n.type !== "follow_request") { if (n.type === "reaction") n.type = "favourite"; n.status = toTextWithReaction( @@ -43,8 +46,10 @@ export function apiNotificationsMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); const body: any = ctx.request.body; try { - const dataRaw = await client.getNotification(ctx.params.id); - const data = dataRaw.data; + const dataRaw = await client.getNotification( + convertId(ctx.params.id, IdType.CalckeyId) + ); + const data = convertNotification(dataRaw.data); if (data.type !== "follow" && data.type !== "follow_request") { if (data.type === "reaction") data.type = "favourite"; ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0]; @@ -79,7 +84,9 @@ export function apiNotificationsMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); const body: any = ctx.request.body; try { - const data = await client.dismissNotification(ctx.params.id); + const data = await client.dismissNotification( + convertId(ctx.params.id, IdType.CalckeyId) + ); ctx.body = data.data; } catch (e: any) { console.error(e); diff --git a/packages/backend/src/server/api/mastodon/endpoints/search.ts b/packages/backend/src/server/api/mastodon/endpoints/search.ts index e4990811a..98349cfd2 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/search.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/search.ts @@ -3,7 +3,8 @@ import Router from "@koa/router"; import { getClient } from "../ApiMastodonCompatibleService.js"; import axios from "axios"; import { Converter } from "@calckey/megalodon"; -import { limitToInt } from "./timeline.js"; +import { convertTimelinesArgsId, limitToInt } from "./timeline.js"; +import { convertAccount, convertStatus } from "../converters.js"; export function apiSearchMastodon(router: Router): void { router.get("/v1/search", async (ctx) => { @@ -12,7 +13,7 @@ export function apiSearchMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); const body: any = ctx.request.body; try { - const query: any = limitToInt(ctx.query); + const query: any = convertTimelinesArgsId(limitToInt(ctx.query)); const type = query.type || ""; const data = await client.search(query.q, type, query); ctx.body = data.data; @@ -27,18 +28,18 @@ export function apiSearchMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const query: any = limitToInt(ctx.query); + const query: any = convertTimelinesArgsId(limitToInt(ctx.query)); const type = query.type; if (type) { const data = await client.search(query.q, type, query); - ctx.body = data.data; + ctx.body = data.data.accounts.map(account => convertAccount(account)); } else { const acct = await client.search(query.q, "accounts", query); const stat = await client.search(query.q, "statuses", query); const tags = await client.search(query.q, "hashtags", query); ctx.body = { - accounts: acct.data.accounts, - statuses: stat.data.statuses, + accounts: acct.data.accounts.map(account => convertAccount(account)), + statuses: stat.data.statuses.map(status => convertStatus(status)), hashtags: tags.data.hashtags, }; } @@ -57,7 +58,7 @@ export function apiSearchMastodon(router: Router): void { ctx.request.hostname, accessTokens, ); - ctx.body = data; + ctx.body = data.map(status => convertStatus(status)); } catch (e: any) { console.error(e); ctx.status = 401; @@ -69,12 +70,16 @@ export function apiSearchMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; try { const query: any = ctx.query; - const data = await getFeaturedUser( + let data = await getFeaturedUser( BASE_URL, ctx.request.hostname, accessTokens, query.limit || 20, ); + data = data.map(suggestion => { + suggestion.account = convertAccount(suggestion.account); + return suggestion; + }); console.log(data); ctx.body = data; } catch (e: any) { diff --git a/packages/backend/src/server/api/mastodon/endpoints/status.ts b/packages/backend/src/server/api/mastodon/endpoints/status.ts index f7589569c..27b30d113 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/status.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/status.ts @@ -4,7 +4,9 @@ import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js"; import axios from "axios"; import querystring from "node:querystring"; import qs from "qs"; -import { limitToInt } from "./timeline.js"; +import { convertTimelinesArgsId, limitToInt } from "./timeline.js"; +import { convertId, IdType } from "../../index.js"; +import { convertAccount, convertAttachment, convertPoll, convertStatus } from "../converters.js"; function normalizeQuery(data: any) { const str = querystring.stringify(data); @@ -18,6 +20,8 @@ export function apiStatusMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { let body: any = ctx.request.body; + if (body.in_reply_to_id) + body.in_reply_to_id = convertId(body.in_reply_to_id, IdType.CalckeyId); if ( (!body.poll && body["poll[options][]"]) || (!body.media_ids && body["media_ids[]"]) @@ -54,7 +58,7 @@ export function apiStatusMastodon(router: Router): void { body.sensitive = typeof sensitive === "string" ? sensitive === "true" : sensitive; const data = await client.postStatus(text, body); - ctx.body = data.data; + ctx.body = convertStatus(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -66,8 +70,10 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getStatus(ctx.params.id); - ctx.body = data.data; + const data = await client.getStatus( + convertId(ctx.params.id, IdType.CalckeyId), + ); + ctx.body = convertStatus(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -79,7 +85,9 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.deleteStatus(ctx.params.id); + const data = await client.deleteStatus( + convertId(ctx.params.id, IdType.CalckeyId) + ); ctx.body = data.data; } catch (e: any) { console.error(e.response.data, request.params.id); @@ -100,10 +108,10 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const id = ctx.params.id; + const id = convertId(ctx.params.id, IdType.CalckeyId); const data = await client.getStatusContext( id, - limitToInt(ctx.query as any), + convertTimelinesArgsId(limitToInt(ctx.query as any)), ); const status = await client.getStatus(id); let reqInstance = axios.create({ @@ -126,6 +134,8 @@ export function apiStatusMastodon(router: Router): void { text, ), ); + data.data.ancestors = data.data.ancestors.map(status => convertStatus(status)); + data.data.descendants = data.data.descendants.map(status => convertStatus(status)); ctx.body = data.data; } catch (e: any) { console.error(e); @@ -141,8 +151,10 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getStatusRebloggedBy(ctx.params.id); - ctx.body = data.data; + const data = await client.getStatusRebloggedBy( + convertId(ctx.params.id, IdType.CalckeyId) + ); + ctx.body = data.data.map(account => convertAccount(account)); } catch (e: any) { console.error(e); ctx.status = 401; @@ -165,11 +177,11 @@ export function apiStatusMastodon(router: Router): void { const react = await getFirstReaction(BASE_URL, accessTokens); try { const a = (await client.createEmojiReaction( - ctx.params.id, + convertId(ctx.params.id, IdType.CalckeyId), react, )) as any; //const data = await client.favouriteStatus(ctx.params.id) as any; - ctx.body = a.data; + ctx.body = convertStatus(a.data); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -186,8 +198,11 @@ export function apiStatusMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); const react = await getFirstReaction(BASE_URL, accessTokens); try { - const data = await client.deleteEmojiReaction(ctx.params.id, react); - ctx.body = data.data; + const data = await client.deleteEmojiReaction( + convertId(ctx.params.id, IdType.CalckeyId), + react + ); + ctx.body = convertStatus(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -203,8 +218,10 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.reblogStatus(ctx.params.id); - ctx.body = data.data; + const data = await client.reblogStatus( + convertId(ctx.params.id, IdType.CalckeyId) + ); + ctx.body = convertStatus(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -220,8 +237,10 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.unreblogStatus(ctx.params.id); - ctx.body = data.data; + const data = await client.unreblogStatus( + convertId(ctx.params.id, IdType.CalckeyId) + ); + ctx.body = convertStatus(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -237,8 +256,10 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.bookmarkStatus(ctx.params.id); - ctx.body = data.data; + const data = await client.bookmarkStatus( + convertId(ctx.params.id, IdType.CalckeyId) + ); + ctx.body = convertStatus(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -254,8 +275,10 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = (await client.unbookmarkStatus(ctx.params.id)) as any; - ctx.body = data.data; + const data = await client.unbookmarkStatus( + convertId(ctx.params.id, IdType.CalckeyId) + ); + ctx.body = convertStatus(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -271,8 +294,10 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.pinStatus(ctx.params.id); - ctx.body = data.data; + const data = await client.pinStatus( + convertId(ctx.params.id, IdType.CalckeyId) + ); + ctx.body = convertStatus(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -288,8 +313,10 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.unpinStatus(ctx.params.id); - ctx.body = data.data; + const data = await client.unpinStatus( + convertId(ctx.params.id, IdType.CalckeyId) + ); + ctx.body = convertStatus(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -302,8 +329,10 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getMedia(ctx.params.id); - ctx.body = data.data; + const data = await client.getMedia( + convertId(ctx.params.id, IdType.CalckeyId) + ); + ctx.body = convertAttachment(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -316,10 +345,10 @@ export function apiStatusMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.updateMedia( - ctx.params.id, + convertId(ctx.params.id, IdType.CalckeyId), ctx.request.body as any, ); - ctx.body = data.data; + ctx.body = convertAttachment(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -331,8 +360,10 @@ export function apiStatusMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getPoll(ctx.params.id); - ctx.body = data.data; + const data = await client.getPoll( + convertId(ctx.params.id, IdType.CalckeyId) + ); + ctx.body = convertPoll(data.data); } catch (e: any) { console.error(e); ctx.status = 401; @@ -347,10 +378,10 @@ export function apiStatusMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.votePoll( - ctx.params.id, + convertId(ctx.params.id, IdType.CalckeyId), (ctx.request.body as any).choices, ); - ctx.body = data.data; + ctx.body = convertPoll(data.data); } catch (e: any) { console.error(e); ctx.status = 401; diff --git a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts index 57e5d9bb0..268c6a161 100644 --- a/packages/backend/src/server/api/mastodon/endpoints/timeline.ts +++ b/packages/backend/src/server/api/mastodon/endpoints/timeline.ts @@ -4,6 +4,8 @@ import { getClient } from "../ApiMastodonCompatibleService.js"; import { statusModel } from "./status.js"; import Autolinker from "autolinker"; import { ParsedUrlQuery } from "querystring"; +import { convertAccount, convertList, convertStatus } from "../converters.js"; +import { convertId, IdType } from "../../index.js"; export function limitToInt(q: ParsedUrlQuery) { let object: any = q; @@ -29,6 +31,16 @@ export function argsToBools(q: ParsedUrlQuery) { return q; } +export function convertTimelinesArgsId(q: ParsedUrlQuery) { + if (typeof q.min_id === "string") + q.min_id = convertId(q.min_id, IdType.CalckeyId); + if (typeof q.max_id === "string") + q.max_id = convertId(q.max_id, IdType.CalckeyId); + if (typeof q.since_id === "string") + q.since_id = convertId(q.since_id, IdType.CalckeyId); + return q; +} + export function toTextWithReaction(status: Entity.Status[], host: string) { return status.map((t) => { if (!t) return statusModel(null, null, [], "no content"); @@ -97,9 +109,10 @@ export function apiTimelineMastodon(router: Router): void { try { const query: any = ctx.query; const data = query.local - ? await client.getLocalTimeline(argsToBools(limitToInt(query))) - : await client.getPublicTimeline(argsToBools(limitToInt(query))); - ctx.body = toTextWithReaction(data.data, ctx.hostname); + ? await client.getLocalTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query)))) + : await client.getPublicTimeline(convertTimelinesArgsId(argsToBools(limitToInt(query)))); + let resp = data.data.map(status => convertStatus(status)); + ctx.body = toTextWithReaction(resp, ctx.hostname); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -116,9 +129,10 @@ export function apiTimelineMastodon(router: Router): void { try { const data = await client.getTagTimeline( ctx.params.hashtag, - argsToBools(limitToInt(ctx.query)), + convertTimelinesArgsId(argsToBools(limitToInt(ctx.query))), ); - ctx.body = toTextWithReaction(data.data, ctx.hostname); + let resp = data.data.map(status => convertStatus(status)); + ctx.body = toTextWithReaction(resp, ctx.hostname); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -132,8 +146,9 @@ export function apiTimelineMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getHomeTimeline(limitToInt(ctx.query)); - ctx.body = toTextWithReaction(data.data, ctx.hostname); + const data = await client.getHomeTimeline(convertTimelinesArgsId(limitToInt(ctx.query))); + let resp = data.data.map(status => convertStatus(status)); + ctx.body = toTextWithReaction(resp, ctx.hostname); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -149,10 +164,11 @@ export function apiTimelineMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getListTimeline( - ctx.params.listId, - limitToInt(ctx.query), + convertId(ctx.params.listId, IdType.CalckeyId), + convertTimelinesArgsId(limitToInt(ctx.query)), ); - ctx.body = toTextWithReaction(data.data, ctx.hostname); + let resp = data.data.map(status => convertStatus(status)); + ctx.body = toTextWithReaction(resp, ctx.hostname); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -166,7 +182,7 @@ export function apiTimelineMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getConversationTimeline(limitToInt(ctx.query)); + const data = await client.getConversationTimeline(convertTimelinesArgsId(limitToInt(ctx.query))); ctx.body = data.data; } catch (e: any) { console.error(e); @@ -181,7 +197,7 @@ export function apiTimelineMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getLists(); - ctx.body = data.data; + ctx.body = data.data.map(list => convertList(list)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -196,8 +212,10 @@ export function apiTimelineMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.getList(ctx.params.id); - ctx.body = data.data; + const data = await client.getList( + convertId(ctx.params.id, IdType.CalckeyId), + ); + ctx.body = convertList(data.data); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -212,7 +230,7 @@ export function apiTimelineMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.createList((ctx.request.body as any).title); - ctx.body = data.data; + ctx.body = convertList(data.data); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -227,8 +245,11 @@ export function apiTimelineMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.updateList(ctx.params.id, (ctx.request.body as any).title); - ctx.body = data.data; + const data = await client.updateList( + convertId(ctx.params.id, IdType.CalckeyId), + (ctx.request.body as any).title + ); + ctx.body = convertList(data.data); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -244,7 +265,9 @@ export function apiTimelineMastodon(router: Router): void { const accessTokens = ctx.headers.authorization; const client = getClient(BASE_URL, accessTokens); try { - const data = await client.deleteList(ctx.params.id); + const data = await client.deleteList( + convertId(ctx.params.id, IdType.CalckeyId), + ); ctx.body = data.data; } catch (e: any) { console.error(e); @@ -262,10 +285,10 @@ export function apiTimelineMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.getAccountsInList( - ctx.params.id, - ctx.query as any, + convertId(ctx.params.id, IdType.CalckeyId), + convertTimelinesArgsId(ctx.query as any), ); - ctx.body = data.data; + ctx.body = data.data.map(account => convertAccount(account)); } catch (e: any) { console.error(e); console.error(e.response.data); @@ -282,8 +305,8 @@ export function apiTimelineMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.addAccountsToList( - ctx.params.id, - (ctx.query as any).account_ids, + convertId(ctx.params.id, IdType.CalckeyId), + (ctx.query.account_ids as string[]).map(id => convertId(id, IdType.CalckeyId)), ); ctx.body = data.data; } catch (e: any) { @@ -302,8 +325,8 @@ export function apiTimelineMastodon(router: Router): void { const client = getClient(BASE_URL, accessTokens); try { const data = await client.deleteAccountsFromList( - ctx.params.id, - (ctx.query as any).account_ids, + convertId(ctx.params.id, IdType.CalckeyId), + (ctx.query.account_ids as string[]).map(id => convertId(id, IdType.CalckeyId)), ); ctx.body = data.data; } catch (e: any) {