diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js index 7b66fdb6de..d3a197e5f0 100644 --- a/packages/backend-rs/index.js +++ b/packages/backend-rs/index.js @@ -310,8 +310,10 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { nyaify, AntennaSrcEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr, IdConvertType, convertId } = nativeBinding +const { stringToAcct, acctToString, nyaify, AntennaSrcEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr, IdConvertType, convertId } = nativeBinding +module.exports.stringToAcct = stringToAcct +module.exports.acctToString = acctToString module.exports.nyaify = nyaify module.exports.AntennaSrcEnum = AntennaSrcEnum module.exports.MutedNoteReasonEnum = MutedNoteReasonEnum diff --git a/packages/backend-rs/src/misc/acct.rs b/packages/backend-rs/src/misc/acct.rs new file mode 100644 index 0000000000..c24ff06dd4 --- /dev/null +++ b/packages/backend-rs/src/misc/acct.rs @@ -0,0 +1,73 @@ +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "napi", crate::export(object, use_nullable = true))] +pub struct Acct { + pub username: String, + pub host: Option, +} + +#[cfg_attr(feature = "napi", crate::export)] +pub fn string_to_acct(acct: &str) -> Acct { + let split: Vec<&str> = if let Some(stripped) = acct.strip_prefix('@') { + stripped + } else { + &acct + } + .split('@') + .collect(); + + Acct { + username: split[0].to_string(), + host: if split.len() == 1 { + None + } else { + Some(split[1].to_string()) + }, + } +} + +#[cfg_attr(feature = "napi", crate::export)] +pub fn acct_to_string(acct: &Acct) -> String { + match &acct.host { + Some(host) => format!("{}@{}", acct.username, host), + None => acct.username.clone(), + } +} + +#[cfg(test)] +mod unit_test { + use super::{acct_to_string, string_to_acct, Acct}; + + #[test] + fn test_acct_to_string() { + let remote_acct = Acct { + username: "firefish".to_string(), + host: Some("example.com".to_string()), + }; + let local_acct = Acct { + username: "MisakaMikoto".to_string(), + host: None, + }; + + assert_eq!(acct_to_string(&remote_acct), "firefish@example.com"); + assert_ne!(acct_to_string(&remote_acct), "mastodon@example.com"); + assert_eq!(acct_to_string(&local_acct), "MisakaMikoto"); + assert_ne!(acct_to_string(&local_acct), "ShiraiKuroko"); + } + + #[test] + fn test_string_to_acct() { + let remote_acct = Acct { + username: "firefish".to_string(), + host: Some("example.com".to_string()), + }; + let local_acct = Acct { + username: "MisakaMikoto".to_string(), + host: None, + }; + + assert_eq!(string_to_acct("@firefish@example.com"), remote_acct); + assert_eq!(string_to_acct("firefish@example.com"), remote_acct); + assert_eq!(string_to_acct("@MisakaMikoto"), local_acct); + assert_eq!(string_to_acct("MisakaMikoto"), local_acct); + } +} diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs index 6c5d7c4f2e..02cdbfb404 100644 --- a/packages/backend-rs/src/misc/mod.rs +++ b/packages/backend-rs/src/misc/mod.rs @@ -1 +1,2 @@ +pub mod acct; pub mod nyaify; diff --git a/packages/backend/src/misc/acct.ts b/packages/backend/src/misc/acct.ts deleted file mode 100644 index cb6808b4b4..0000000000 --- a/packages/backend/src/misc/acct.ts +++ /dev/null @@ -1,14 +0,0 @@ -export type Acct = { - username: string; - host: string | null; -}; - -export function parse(acct: string): Acct { - if (acct.startsWith("@")) acct = acct.slice(1); - const split = acct.split("@", 2); - return { username: split[0], host: split[1] || null }; -} - -export function toString(acct: Acct): string { - return acct.host == null ? acct.username : `${acct.username}@${acct.host}`; -} diff --git a/packages/backend/src/misc/check-hit-antenna.ts b/packages/backend/src/misc/check-hit-antenna.ts index b93cb459e8..a2090934b6 100644 --- a/packages/backend/src/misc/check-hit-antenna.ts +++ b/packages/backend/src/misc/check-hit-antenna.ts @@ -4,7 +4,7 @@ import type { User } from "@/models/entities/user.js"; import type { UserProfile } from "@/models/entities/user-profile.js"; import { Blockings, Followings, UserProfiles } from "@/models/index.js"; import { getFullApAccount } from "@/misc/convert-host.js"; -import * as Acct from "@/misc/acct.js"; +import { stringToAcct } from "backend-rs"; import { getWordHardMute } from "@/misc/check-word-mute.js"; import type { Packed } from "@/misc/schema.js"; import { Cache } from "@/misc/cache.js"; @@ -30,7 +30,7 @@ export async function checkHitAntenna( if (antenna.src === "users") { const accts = antenna.users.map((x) => { - const { username, host } = Acct.parse(x); + const { username, host } = stringToAcct(x); return getFullApAccount(username, host).toLowerCase(); }); if ( diff --git a/packages/backend/src/queue/processors/db/import-blocking.ts b/packages/backend/src/queue/processors/db/import-blocking.ts index 159ccbfd4a..bb1920d2a8 100644 --- a/packages/backend/src/queue/processors/db/import-blocking.ts +++ b/packages/backend/src/queue/processors/db/import-blocking.ts @@ -1,7 +1,7 @@ import type Bull from "bull"; import { queueLogger } from "../../logger.js"; -import * as Acct from "@/misc/acct.js"; +import { stringToAcct } from "backend-rs"; import { resolveUser } from "@/remote/resolve-user.js"; import { downloadTextFile } from "@/misc/download-text-file.js"; import { isSelfHost, toPuny } from "@/misc/convert-host.js"; @@ -42,7 +42,7 @@ export async function importBlocking( try { const acct = line.split(",")[0].trim(); - const { username, host } = Acct.parse(acct); + const { username, host } = stringToAcct(acct); let target = isSelfHost(host!) ? await Users.findOneBy({ diff --git a/packages/backend/src/queue/processors/db/import-following.ts b/packages/backend/src/queue/processors/db/import-following.ts index d2c2430fa3..f7aa5afedd 100644 --- a/packages/backend/src/queue/processors/db/import-following.ts +++ b/packages/backend/src/queue/processors/db/import-following.ts @@ -1,7 +1,7 @@ import { IsNull } from "typeorm"; import follow from "@/services/following/create.js"; -import * as Acct from "@/misc/acct.js"; +import { stringToAcct } from "backend-rs"; import { resolveUser } from "@/remote/resolve-user.js"; import { downloadTextFile } from "@/misc/download-text-file.js"; import { isSelfHost, toPuny } from "@/misc/convert-host.js"; @@ -40,7 +40,7 @@ export async function importFollowing( if (file.type.endsWith("json")) { for (const acct of JSON.parse(csv)) { try { - const { username, host } = Acct.parse(acct); + const { username, host } = stringToAcct(acct); let target = isSelfHost(host!) ? await Users.findOneBy({ @@ -78,7 +78,7 @@ export async function importFollowing( try { const acct = line.split(",")[0].trim(); - const { username, host } = Acct.parse(acct); + const { username, host } = stringToAcct(acct); let target = isSelfHost(host!) ? await Users.findOneBy({ diff --git a/packages/backend/src/queue/processors/db/import-muting.ts b/packages/backend/src/queue/processors/db/import-muting.ts index cd42a3281d..cdd8eab88d 100644 --- a/packages/backend/src/queue/processors/db/import-muting.ts +++ b/packages/backend/src/queue/processors/db/import-muting.ts @@ -1,14 +1,13 @@ import type Bull from "bull"; import { queueLogger } from "../../logger.js"; -import * as Acct from "@/misc/acct.js"; import { resolveUser } from "@/remote/resolve-user.js"; import { downloadTextFile } from "@/misc/download-text-file.js"; import { isSelfHost, toPuny } from "@/misc/convert-host.js"; import { Users, DriveFiles, Mutings } from "@/models/index.js"; import type { DbUserImportJobData } from "@/queue/types.js"; import type { User } from "@/models/entities/user.js"; -import { genId } from "backend-rs"; +import { genId, stringToAcct } from "backend-rs"; import { IsNull } from "typeorm"; import { inspect } from "node:util"; @@ -43,7 +42,7 @@ export async function importMuting( try { const acct = line.split(",")[0].trim(); - const { username, host } = Acct.parse(acct); + const { username, host } = stringToAcct(acct); let target = isSelfHost(host!) ? await Users.findOneBy({ diff --git a/packages/backend/src/queue/processors/db/import-user-lists.ts b/packages/backend/src/queue/processors/db/import-user-lists.ts index 1ea6aa84ba..9995b0d0a3 100644 --- a/packages/backend/src/queue/processors/db/import-user-lists.ts +++ b/packages/backend/src/queue/processors/db/import-user-lists.ts @@ -1,7 +1,6 @@ import type Bull from "bull"; import { queueLogger } from "../../logger.js"; -import * as Acct from "@/misc/acct.js"; import { resolveUser } from "@/remote/resolve-user.js"; import { pushUserToUserList } from "@/services/user-list/push.js"; import { downloadTextFile } from "@/misc/download-text-file.js"; @@ -12,7 +11,7 @@ import { UserLists, UserListJoinings, } from "@/models/index.js"; -import { genId } from "backend-rs"; +import { genId, stringToAcct } from "backend-rs"; import type { DbUserImportJobData } from "@/queue/types.js"; import { IsNull } from "typeorm"; import { inspect } from "node:util"; @@ -48,7 +47,7 @@ export async function importUserLists( try { const listName = line.split(",")[0].trim(); - const { username, host } = Acct.parse(line.split(",")[1].trim()); + const { username, host } = stringToAcct(line.split(",")[1].trim()); let list = await UserLists.findOneBy({ userId: user.id, 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 070d6d7f00..9eaeedb39f 100644 --- a/packages/backend/src/server/api/endpoints/i/known-as.ts +++ b/packages/backend/src/server/api/endpoints/i/known-as.ts @@ -8,7 +8,7 @@ import { DAY } from "@/const.js"; import { apiLogger } from "@/server/api/logger.js"; import define from "@/server/api/define.js"; import { ApiError } from "@/server/api/error.js"; -import { parse } from "@/misc/acct.js"; +import { stringToAcct } from "backend-rs"; import { inspect } from "node:util"; export const meta = { @@ -72,7 +72,7 @@ export default define(meta, paramDef, async (ps, user) => { for (const line of ps.alsoKnownAs) { if (!line) throw new ApiError(meta.errors.noSuchUser); - const { username, host } = parse(line); + const { username, host } = stringToAcct(line); const aka = await resolveUser(username, host).catch((e) => { apiLogger.warn(`failed to resolve remote user:\n${inspect(e)}`); diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts index 53379c76c3..4784d3ee20 100644 --- a/packages/backend/src/server/api/endpoints/i/move.ts +++ b/packages/backend/src/server/api/endpoints/i/move.ts @@ -12,7 +12,7 @@ import { getUser } from "@/server/api/common/getters.js"; import { Followings, Users } from "@/models/index.js"; import config from "@/config/index.js"; import { publishMainStream } from "@/services/stream.js"; -import { parse } from "@/misc/acct.js"; +import { stringToAcct } from "backend-rs"; import { inspect } from "node:util"; export const meta = { @@ -90,7 +90,7 @@ export default define(meta, paramDef, async (ps, user) => { if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchMoveTarget); if (user.movedToUri) throw new ApiError(meta.errors.alreadyMoved); - const { username, host } = parse(ps.moveToAccount); + const { username, host } = stringToAcct(ps.moveToAccount); if (!host) throw new ApiError(meta.errors.notRemote); const moveTo: User = await resolveUser(username, host).catch((e) => { diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index b327378700..6d6519e47b 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -1,7 +1,7 @@ import { IsNull } from "typeorm"; import { Users } from "@/models/index.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; -import * as Acct from "@/misc/acct.js"; +import { stringToAcct } from "backend-rs"; import type { User } from "@/models/entities/user.js"; import define from "@/server/api/define.js"; @@ -35,7 +35,7 @@ export default define(meta, paramDef, async (ps, me) => { const users = await Promise.all( meta.pinnedUsers - .map((acct) => Acct.parse(acct)) + .map((acct) => stringToAcct(acct)) .map((acct) => Users.findOneBy({ usernameLower: acct.username.toLowerCase(), diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index 28d62a3ac3..2a6dfdf674 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -19,7 +19,7 @@ import { Users } from "@/models/index.js"; import { fetchMeta } from "@/misc/fetch-meta.js"; import { genIdenticon } from "@/misc/gen-identicon.js"; import { createTemp } from "@/misc/create-temp.js"; -import * as Acct from "@/misc/acct.js"; +import { stringToAcct } from "backend-rs"; import { envOption } from "@/env.js"; import megalodon, { MegalodonInterface } from "megalodon"; import activityPub from "./activitypub.js"; @@ -108,7 +108,7 @@ router.use(nodeinfo.routes()); router.use(wellKnown.routes()); router.get("/avatar/@:acct", async (ctx) => { - const { username, host } = Acct.parse(ctx.params.acct); + const { username, host } = stringToAcct(ctx.params.acct); const user = await Users.findOne({ where: { usernameLower: username.toLowerCase(), diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index 676b9dc9fd..bb17cd279a 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -28,7 +28,7 @@ import { Emojis, GalleryPosts, } from "@/models/index.js"; -import * as Acct from "@/misc/acct.js"; +import { stringToAcct } from "backend-rs"; import { getNoteSummary } from "@/misc/get-note-summary.js"; import { queues } from "@/queue/queues.js"; import { genOpenapiSpec } from "../api/openapi/gen-spec.js"; @@ -330,7 +330,7 @@ const getFeed = async ( if (meta.privateMode) { return; } - const { username, host } = Acct.parse(acct); + const { username, host } = stringToAcct(acct); const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), host: host ?? IsNull(), @@ -461,7 +461,7 @@ const jsonFeed: Router.Middleware = async (ctx) => { const userPage: Router.Middleware = async (ctx, next) => { const userParam = ctx.params.user; const subParam = ctx.params.sub; - const { username, host } = Acct.parse(userParam); + const { username, host } = stringToAcct(userParam); const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), @@ -580,7 +580,7 @@ router.get("/posts/:note", async (ctx, next) => { // Page router.get("/@:user/pages/:page", async (ctx, next) => { - const { username, host } = Acct.parse(ctx.params.user); + const { username, host } = stringToAcct(ctx.params.user); const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), host: host ?? IsNull(), diff --git a/packages/backend/src/server/well-known.ts b/packages/backend/src/server/well-known.ts index 1dc0f3d0a5..5177c36908 100644 --- a/packages/backend/src/server/well-known.ts +++ b/packages/backend/src/server/well-known.ts @@ -1,7 +1,7 @@ import Router from "@koa/router"; import config from "@/config/index.js"; -import * as Acct from "@/misc/acct.js"; +import { type Acct, stringToAcct } from "backend-rs"; import { links } from "./nodeinfo.js"; import { escapeAttribute, escapeValue } from "@/prelude/xml.js"; import { Users } from "@/models/index.js"; @@ -110,7 +110,7 @@ router.get(webFingerPath, async (ctx) => { resource.startsWith(`${config.url.toLowerCase()}/users/`) ? fromId(resource.split("/").pop()!) : fromAcct( - Acct.parse( + stringToAcct( resource.startsWith(`${config.url.toLowerCase()}/@`) ? resource.split("/").pop()! : resource.startsWith("acct:") @@ -119,7 +119,7 @@ router.get(webFingerPath, async (ctx) => { ), ); - const fromAcct = (acct: Acct.Acct): FindOptionsWhere | number => + const fromAcct = (acct: Acct): FindOptionsWhere | number => !acct.host || acct.host === config.host.toLowerCase() ? { usernameLower: acct.username, diff --git a/packages/backend/src/services/send-email-notification.ts b/packages/backend/src/services/send-email-notification.ts index aa8fa35ffe..8eed0a4e69 100644 --- a/packages/backend/src/services/send-email-notification.ts +++ b/packages/backend/src/services/send-email-notification.ts @@ -2,7 +2,7 @@ import type { User } from "@/models/entities/user.js"; // import { sendEmail } from "./send-email.js"; // import { I18n } from "@/misc/i18n.js"; -// import * as Acct from "@/misc/acct.js"; +// import { acctToString } from "backend-rs"; // TODO //const locales = await import('../../../../locales/index.js'); @@ -15,7 +15,7 @@ async function follow(userId: User["id"], follower: User) { const locale = locales['en-US']; const i18n = new I18n(locale); // TODO: render user information html - sendEmail(userProfile.email, i18n.t('_email._follow.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`); + sendEmail(userProfile.email, i18n.t('_email._follow.title'), `${follower.name} (@${acctToString(follower)})`, `${follower.name} (@${acctToString(follower)})`); */ } @@ -26,7 +26,7 @@ async function receiveFollowRequest(userId: User["id"], follower: User) { const locale = locales['en-US']; const i18n = new I18n(locale); // TODO: render user information html - sendEmail(userProfile.email, i18n.t('_email._receiveFollowRequest.title'), `${follower.name} (@${Acct.toString(follower)})`, `${follower.name} (@${Acct.toString(follower)})`); + sendEmail(userProfile.email, i18n.t('_email._receiveFollowRequest.title'), `${follower.name} (@${acctToString(follower)})`, `${follower.name} (@${acctToString(follower)})`); */ }