diff --git a/locales/en-US.yml b/locales/en-US.yml index ec1bae34cd..09f1494ac7 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1202,7 +1202,7 @@ _mfm: inlineMath: "Math (Inline)" inlineMathDescription: "Display math formulas (KaTeX) in-line" blockMath: "Math (Block)" - blockMathDescription: "Display multi-line math formulas (KaTeX) in a block" + blockMathDescription: "Display math formulas (KaTeX) in a block" quote: "Quote" quoteDescription: "Displays content as a quote." emoji: "Custom Emoji" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 641aca9383..8ae43cdb9b 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1083,7 +1083,7 @@ _mfm: inlineMath: "数式(インライン)" inlineMathDescription: "数式(KaTeX)をインラインで表示します。" blockMath: "数式(ブロック)" - blockMathDescription: "複数行の数式(KaTeX)をブロックで表示します。" + blockMathDescription: "数式(KaTeX)をブロックで表示します。" quote: "引用" quoteDescription: "内容が引用であることを示せます。" emoji: "カスタム絵文字" @@ -1124,6 +1124,7 @@ _mfm: rotateDescription: "指定した角度で回転させます。" plain: "プレーン" plainDescription: "内側の構文を全て無効にします。" + position: 位置 _instanceTicker: none: "表示しない" remote: "リモートユーザーに表示" @@ -1132,7 +1133,7 @@ _serverDisconnectedBehavior: reload: "自動でリロード" dialog: "ダイアログで警告" quiet: "控えめに警告" - nothing: "何も起こらない" + nothing: "何もしない" _channel: create: "チャンネルを作成" edit: "チャンネルを編集" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index c652b52b7d..645f11f568 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1009,9 +1009,9 @@ _mfm: blockCode: "代码(块)" blockCodeDescription: "语法高亮显示整块程序代码。" inlineMath: "数学公式(内嵌)" - inlineMathDescription: "显示内嵌的KaTex公式。" + inlineMathDescription: "显示内嵌的KaTeX公式。" blockMath: "数学公式(块)" - blockMathDescription: "显示整块的多行KaTex数学公式。" + blockMathDescription: "显示整块的KaTeX数学公式。" quote: "引用" quoteDescription: "可以用来表示引用的内容。" emoji: "自定义表情符号" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index eb640b7dd4..c2dfd1ce02 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1012,9 +1012,9 @@ _mfm: blockCode: "程式碼(區塊)" blockCodeDescription: "在區塊中用高亮度顯示,例如複數行的程式碼語法。" inlineMath: "數學公式(內嵌)" - inlineMathDescription: "顯示內嵌的KaTex數學公式。" + inlineMathDescription: "顯示內嵌的KaTeX數學公式。" blockMath: "數學公式(方塊)" - blockMathDescription: "以區塊顯示複數行的KaTex數學式。" + blockMathDescription: "以區塊顯示KaTeX數學式。" quote: "引用" quoteDescription: "可以用來表示引用的内容。" emoji: "自訂表情符號" diff --git a/package.json b/package.json index 61c247ab2e..78e39e69f2 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ "@bull-board/ui": "^4.10.2", "@napi-rs/cli": "^2.15.0", "@tensorflow/tfjs": "^3.21.0", + "focus-trap": "^7.2.0", + "focus-trap-vue": "^4.0.1", "js-yaml": "4.1.0", "seedrandom": "^3.0.5" }, diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index 29ac726efc..d47e6f3007 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -10,7 +10,7 @@ import { renderPerson } from "@/remote/activitypub/renderer/person.js"; import renderEmoji from "@/remote/activitypub/renderer/emoji.js"; import { inbox as processInbox } from "@/queue/index.js"; import { isSelfHost, toPuny } from "@/misc/convert-host.js"; -import { Notes, Users, Emojis, NoteReactions } from "@/models/index.js"; +import { Notes, Users, Emojis, NoteReactions, FollowRequests } from "@/models/index.js"; import type { ILocalUser, User } from "@/models/entities/user.js"; import { renderLike } from "@/remote/activitypub/renderer/like.js"; import { getUserKeypair } from "@/misc/keypair-store.js"; @@ -330,7 +330,7 @@ router.get("/likes/:like", async (ctx) => { }); // follow -router.get("/follows/:follower/:followee", async (ctx) => { +router.get("/follows/:follower/:followee", async (ctx: Router.RouterContext) => { const verify = await checkFetch(ctx.req); if (verify !== 200) { ctx.status = verify; @@ -365,4 +365,47 @@ router.get("/follows/:follower/:followee", async (ctx) => { setResponseType(ctx); }); +// follow request +router.get("/follows/:followRequestId", async (ctx: Router.RouterContext) => { + const verify = await checkFetch(ctx.req); + if (verify !== 200) { + ctx.status = verify; + return; + } + + const followRequest = await FollowRequests.findOneBy({ + id: ctx.params.followRequestId, + }); + + if (followRequest == null) { + ctx.status = 404; + return; + } + + const [follower, followee] = await Promise.all([ + Users.findOneBy({ + id: followRequest.followerId, + host: IsNull(), + }), + Users.findOneBy({ + id: followRequest.followeeId, + host: Not(IsNull()), + }), + ]); + + if (follower == null || followee == null) { + ctx.status = 404; + return; + } + + const meta = await fetchMeta(); + if (meta.secureMode || meta.privateMode) { + ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); + } else { + ctx.set("Cache-Control", "public, max-age=180"); + } + ctx.body = renderActivity(renderFollow(follower, followee)); + setResponseType(ctx); +}); + export default router; diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 06b3ea4efa..3568a27b25 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 e8dfe52812..0c59b38f4a 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 0000000000..d9a4f90ef3 --- /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 7490581937..2984c20e3f 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 d21bc1d330..7343fc3373 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 8508f1d486..868377b783 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 e4990811ae..98349cfd27 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 f7589569c9..27b30d1136 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 57e5d9bb02..268c6a1612 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) { diff --git a/packages/backend/src/services/following/requests/create.ts b/packages/backend/src/services/following/requests/create.ts index 8b2e86ab5b..27f9144d0e 100644 --- a/packages/backend/src/services/following/requests/create.ts +++ b/packages/backend/src/services/following/requests/create.ts @@ -6,6 +6,7 @@ import type { User } from "@/models/entities/user.js"; import { Blockings, FollowRequests, Users } from "@/models/index.js"; import { genId } from "@/misc/gen-id.js"; import { createNotification } from "../../create-notification.js"; +import config from "@/config/index.js"; export default async function ( follower: { @@ -79,7 +80,7 @@ export default async function ( } if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) { - const content = renderActivity(renderFollow(follower, followee)); + const content = renderActivity(renderFollow(follower, followee, requestId ?? `${config.url}/follows/${followRequest.id}`)); deliver(follower, content, followee.inbox); } } diff --git a/packages/client/package.json b/packages/client/package.json index 49c175b15b..1735855037 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -32,7 +32,7 @@ "autosize": "5.0.2", "blurhash": "1.1.5", "broadcast-channel": "4.19.1", - "browser-image-resizer": "https://github.com/misskey-dev/browser-image-resizer.git", + "browser-image-resizer": "github:misskey-dev/browser-image-resizer", "calckey-js": "workspace:*", "chart.js": "4.1.1", "chartjs-adapter-date-fns": "2.0.1", diff --git a/packages/client/src/components/MkButton.vue b/packages/client/src/components/MkButton.vue index 5f1a5bdb7e..feac281d9e 100644 --- a/packages/client/src/components/MkButton.vue +++ b/packages/client/src/components/MkButton.vue @@ -195,8 +195,7 @@ function onMousedown(evt: MouseEvent): void { } &:focus-visible { - outline: solid 2px var(--focus); - outline-offset: 2px; + outline: auto; } &.inline { diff --git a/packages/client/src/components/MkCwButton.vue b/packages/client/src/components/MkCwButton.vue index 35af48874a..5e59853b66 100644 --- a/packages/client/src/components/MkCwButton.vue +++ b/packages/client/src/components/MkCwButton.vue @@ -1,5 +1,6 @@