diff --git a/packages/backend/src/server/api/endpoints/i/known-as.ts b/packages/backend/src/server/api/endpoints/i/known-as.ts index 5e86e8b955..0d0c06180a 100644 --- a/packages/backend/src/server/api/endpoints/i/known-as.ts +++ b/packages/backend/src/server/api/endpoints/i/known-as.ts @@ -1,4 +1,4 @@ -import type { User, UserDetailedNotMeOnly } from "@/models/entities/user.js"; +import type { User } from "@/models/entities/user.js"; import { Users } from "@/models/index.js"; import { resolveUser } from "@/remote/resolve-user.js"; import acceptAllFollowRequests from "@/services/following/requests/accept-all.js"; @@ -6,10 +6,9 @@ import { publishToFollowers } from "@/services/i/update.js"; import { publishMainStream } from "@/services/stream.js"; import { DAY } from "@/const.js"; import { apiLogger } from "../../logger.js"; -import { UserProfiles } from "@/models/index.js"; -import config from "@/config/index.js"; import define from "../../define.js"; import { ApiError } from "../../error.js"; +import { parse } from "@/misc/acct.js"; export const meta = { tags: ["users"], @@ -38,49 +37,57 @@ export const meta = { code: "URI_NULL", id: "bf326f31-d430-4f97-9933-5d61e4d48a23", }, + alreadyMoved: { + message: "You have already moved your account.", + code: "ALREADY_MOVED", + id: "56f20ec9-fd06-4fa5-841b-edd6d7d4fa31", + }, + yourself: { + message: "You can't set yourself as your own alias.", + code: "FORBIDDEN_TO_SET_YOURSELF", + id: "25c90186-4ab0-49c8-9bba-a1fa6c202ba4", + }, }, } as const; export const paramDef = { type: "object", properties: { - alsoKnownAs: { type: "string" }, + alsoKnownAs: { + type: "array", + maxItems: 10, + uniqueItems: true, + items: { type: "string" }, + }, }, required: ["alsoKnownAs"], } as const; export default define(meta, paramDef, async (ps, user) => { if (!ps.alsoKnownAs) throw new ApiError(meta.errors.noSuchUser); + if (user.movedToUri) throw new ApiError(meta.errors.alreadyMoved); - let unfiltered: string = ps.alsoKnownAs; - const updates = {} as Partial; + const newAka = new Set(); - if (!unfiltered) { - updates.alsoKnownAs = null; - } else { - if (unfiltered.startsWith("acct:")) unfiltered = unfiltered.substring(5); - if (unfiltered.startsWith("@")) unfiltered = unfiltered.substring(1); - if (!unfiltered.includes("@")) throw new ApiError(meta.errors.notRemote); + for (const line of ps.alsoKnownAs) { + if (!line) throw new ApiError(meta.errors.noSuchUser); + const { username, host } = parse(line); - const userAddress: string[] = unfiltered.split("@"); - const knownAs = await resolveUser(userAddress[0], userAddress[1]).catch( - (e) => { - apiLogger.warn(`failed to resolve remote user: ${e}`); - throw new ApiError(meta.errors.noSuchUser); - }, - ); + const aka = await resolveUser(username, host).catch((e) => { + apiLogger.warn(`failed to resolve remote user: ${e}`); + throw new ApiError(meta.errors.noSuchUser); + }); - const toUrl: string | null = knownAs.uri; - if (!toUrl) { - throw new ApiError(meta.errors.uriNull); - } - if (updates.alsoKnownAs == null || updates.alsoKnownAs.length === 0) { - updates.alsoKnownAs = [toUrl]; - } else { - updates.alsoKnownAs.push(toUrl); - } + if (aka.id === user.id) throw new ApiError(meta.errors.yourself); + if (!aka.uri) throw new ApiError(meta.errors.uriNull); + + newAka.add(aka.uri); } + const updates = { + alsoKnownAs: newAka.size > 0 ? Array.from(newAka) : null, + } as Partial; + await Users.update(user.id, updates); const iObj = await Users.pack(user.id, user, { diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts index 3d947063ff..d972aaf1d9 100644 --- a/packages/backend/src/server/api/endpoints/i/move.ts +++ b/packages/backend/src/server/api/endpoints/i/move.ts @@ -10,9 +10,9 @@ import deleteFollowing from "@/services/following/delete.js"; import create from "@/services/following/create.js"; import { getUser } from "@/server/api/common/getters.js"; import { Followings, Users } from "@/models/index.js"; -import { UserProfiles } from "@/models/index.js"; import config from "@/config/index.js"; import { publishMainStream } from "@/services/stream.js"; +import { parse } from "@/misc/acct.js"; export const meta = { tags: ["users"], @@ -95,22 +95,13 @@ export default define(meta, paramDef, async (ps, user) => { if (user.isAdmin) throw new ApiError(meta.errors.adminForbidden); if (user.movedToUri) throw new ApiError(meta.errors.alreadyMoved); - let unfiltered: string = ps.moveToAccount; - if (!unfiltered) { + const { username, host } = parse(ps.moveToAccount); + if (!host) throw new ApiError(meta.errors.notRemote); + + const moveTo: User = await resolveUser(username, host).catch((e) => { + apiLogger.warn(`failed to resolve remote user: ${e}`); throw new ApiError(meta.errors.noSuchMoveTarget); - } - - if (unfiltered.startsWith("acct:")) unfiltered = unfiltered.substring(5); - if (unfiltered.startsWith("@")) unfiltered = unfiltered.substring(1); - if (!unfiltered.includes("@")) throw new ApiError(meta.errors.notRemote); - - const userAddress: string[] = unfiltered.split("@"); - const moveTo: User = await resolveUser(userAddress[0], userAddress[1]).catch( - (e) => { - apiLogger.warn(`failed to resolve remote user: ${e}`); - throw new ApiError(meta.errors.noSuchMoveTarget); - }, - ); + }); let fromUrl: string | null = user.uri; if (!fromUrl) { fromUrl = `${config.url}/users/${user.id}`; @@ -134,6 +125,7 @@ export default define(meta, paramDef, async (ps, user) => { if (!toUrl) toUrl = ""; updates.movedToUri = toUrl; + updates.alsoKnownAs = user.alsoKnownAs?.concat(toUrl) ?? [toUrl]; await Users.update(user.id, updates); const iObj = await Users.pack(user.id, user, { diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 49cac81fdb..bead8df0a4 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -54,7 +54,7 @@ export const paramDef = { anyOf: [ { properties: { - userId: { type: "string", format: "misskey:id" }, + userId: { type: "string" }, }, required: ["userId"], }, @@ -65,7 +65,6 @@ export const paramDef = { uniqueItems: true, items: { type: "string", - format: "misskey:id", }, }, }, @@ -95,21 +94,27 @@ export default define(meta, paramDef, async (ps, me) => { return []; } - const users = await Users.findBy( - isAdminOrModerator - ? { - id: In(ps.userIds), - } - : { - id: In(ps.userIds), - isSuspended: false, - }, - ); + const isUrl = ps.userIds[0].startsWith("http"); + let users: User[]; + if (isUrl) { + users = await Users.findBy( + isAdminOrModerator + ? { uri: In(ps.userIds) } + : { uri: In(ps.userIds), isSuspended: false }, + ); + } else { + users = await Users.findBy( + isAdminOrModerator + ? { id: In(ps.userIds) } + : { id: In(ps.userIds), isSuspended: false }, + ); + } // リクエストされた通りに並べ替え const _users: User[] = []; for (const id of ps.userIds) { - _users.push(users.find((x) => x.id === id)!); + const res = users.find((x) => (isUrl ? x.uri === id : x.id === id)); + if (res) _users.push(res); } return await Promise.all( @@ -129,7 +134,9 @@ export default define(meta, paramDef, async (ps, me) => { } else { const q: FindOptionsWhere = ps.userId != null - ? { id: ps.userId } + ? ps.userId.startsWith("http") + ? { uri: ps.userId } + : { id: ps.userId } : { usernameLower: ps.username!.toLowerCase(), host: IsNull() }; user = await Users.findOneBy(q); diff --git a/packages/client/src/pages/settings/migration.vue b/packages/client/src/pages/settings/migration.vue index 6d5b435e3b..df65e6e25c 100644 --- a/packages/client/src/pages/settings/migration.vue +++ b/packages/client/src/pages/settings/migration.vue @@ -2,14 +2,14 @@
+ {{ + i18n.ts.moveAccountDescription + }} - {{ i18n.ts.moveAccount }} @@ -18,19 +18,31 @@ - + {{ + i18n.ts.moveFromDescription + }} + - - + + {{ i18n.ts.add }} + {{ i18n.ts.save }} @@ -42,17 +54,40 @@ import FormSection from "@/components/form/section.vue"; import FormInput from "@/components/form/input.vue"; import FormButton from "@/components/MkButton.vue"; +import FormInfo from "@/components/MkInfo.vue"; import * as os from "@/os"; import { i18n } from "@/i18n"; import { definePageMetadata } from "@/scripts/page-metadata"; +import { $i } from "@/account"; +import { toString } from "calckey-js/built/acct"; let moveToAccount = $ref(""); -let accountAlias = $ref(""); +let accountAlias = $ref([""]); -async function save(account): Promise { - os.apiWithDialog("i/known-as", { - alsoKnownAs: account, +await init(); + +async function init() { + if ($i?.alsoKnownAs && $i.alsoKnownAs.length > 0) { + const aka = await os.api("users/show", { userIds: $i.alsoKnownAs }); + accountAlias = + aka && aka.length > 0 + ? aka.map((user) => `@${toString(user)}`) + : [""]; + } else { + accountAlias = [""]; + } +} + +async function save(): Promise { + const i = await os.apiWithDialog("i/known-as", { + alsoKnownAs: accountAlias.map((e) => e.trim()).filter((e) => e !== ""), }); + $i.alsoKnownAs = i.alsoKnownAs; + await init(); +} + +function add(): void { + accountAlias.push(""); } async function move(account): Promise {