From 71783d8f063ee1216bf666d743fbd842e4a92da4 Mon Sep 17 00:00:00 2001 From: tamaina Date: Tue, 26 Aug 2025 21:51:17 +0900 Subject: [PATCH] iikanji --- packages/misskey-js/src/acct.ts | 50 +++++++++++-------- packages/misskey-js/test/acct.ts | 86 ++++++++++++++++++++++---------- 2 files changed, 88 insertions(+), 48 deletions(-) diff --git a/packages/misskey-js/src/acct.ts b/packages/misskey-js/src/acct.ts index 392c5ca947..74e0ff06f0 100644 --- a/packages/misskey-js/src/acct.ts +++ b/packages/misskey-js/src/acct.ts @@ -13,6 +13,30 @@ export function correctAcct(acct: Acct, localHostname?: string): Acct { return result; } +export function parse(acct: string): Acct { + let acctWithNoPrefix = acct; + + if (acct.startsWith('@')) { + acctWithNoPrefix = acct.substring(1); + } else if (acct.startsWith('acct:')) { + acctWithNoPrefix = acct.substring(5); + } + + const split = acctWithNoPrefix.split('@', 2); + + return { username: split[0], host: split[1] || null }; +} + +export function parseUrl(str: string): Acct { + const url = new URL(str); + const path = url.pathname.split('/').find((p) => p.startsWith('@') && p.length >= 2); + if (!path) throw new Error('This url is not acct like.'); + + const split = path.split('@', 3); // ['', 'username', 'other.example.com'] + + return { username: split[1], host: split[2] || url.hostname }; +} + /** * Parses a string and returns an Acct object. * @param acct String to parse @@ -23,30 +47,14 @@ export function correctAcct(acct: Acct, localHostname?: string): Acct { * @param localHostname If provided and matches the host found in acct, the returned `host` will be set to `null`. * @returns Acct object */ -export function parse(acct: string, localHostname?: string): Acct { - //#region url style +export function parseAcctOrUrl(acct: string, localHostname?: string): Acct { if (acct.startsWith('https://') || acct.startsWith('http://')) { - const url = new URL(acct); - const path = url.pathname.split('/').find((p) => p.startsWith('@') && p.length >= 2); - if (!path) throw new Error('This url is not acct like.'); - - const split = path.split('@', 3); // ['', 'username', 'other.example.com'] - - return correctAcct({ username: split[1], host: split[2] || url.hostname }, localHostname); + // url style + return correctAcct(parseUrl(acct), localHostname); } - //#endregion - //#region at-mark and acct: style - let acctWithNoPrefix = acct; - if (acct.startsWith('@')) { - acctWithNoPrefix = acct.substring(1); - } else if (acct.startsWith('acct:')) { - acctWithNoPrefix = acct.substring(5); - } - const split = acctWithNoPrefix.split('@', 2); - - return correctAcct({ username: split[0], host: split[1] || null }, localHostname); - //#endregion + // acct style + return correctAcct(parse(acct), localHostname); } export function toString(acct: Acct): string { diff --git a/packages/misskey-js/test/acct.ts b/packages/misskey-js/test/acct.ts index e0ebc0551b..fd59a82c9d 100644 --- a/packages/misskey-js/test/acct.ts +++ b/packages/misskey-js/test/acct.ts @@ -1,64 +1,96 @@ import { describe, it, expect } from 'vitest'; import * as acct from '../src/acct.js'; -describe('acct.parse', () => { +function testParseAcct(fn: (acct: string) => acct.Acct) { it('parses plain username', () => { - const res = acct.parse('alice'); + const res = fn('alice'); expect(res).toEqual({ username: 'alice', host: null }); }); it('parses at-mark style without host', () => { - const res = acct.parse('@alice'); + const res = fn('@alice'); expect(res).toEqual({ username: 'alice', host: null }); }); it('parses at-mark style with host', () => { - const res = acct.parse('@alice@example.com'); + const res = fn('@alice@example.com'); expect(res).toEqual({ username: 'alice', host: 'example.com' }); }); - it('nulls host for at-mark style when localHostname matches', () => { - const res = acct.parse('@alice@example.com', 'example.com'); - expect(res).toEqual({ username: 'alice', host: null }); - }); - it('parses acct: style', () => { - const res = acct.parse('acct:alice@example.com'); + const res = fn('acct:alice@example.com'); expect(res).toEqual({ username: 'alice', host: 'example.com' }); }); - it('nulls host for acct: style when localHostname matches', () => { - const res = acct.parse('acct:alice@example.com', 'example.com'); - expect(res).toEqual({ username: 'alice', host: null }); + it('parse Mr.http', () => { + const res = fn('http'); + expect(res).toEqual({ username: 'http', host: null }); }); +} +function testParseUrl(fn: (acct: string) => acct.Acct) { it('parses url style https with same host -> host kept when localHostname not provided', () => { - const res = acct.parse('https://example.com/@alice'); + const res = fn('https://example.com/@alice'); expect(res).toEqual({ username: 'alice', host: 'example.com' }); }); - it('parses url style http with same host and nulls host when localHostname matches', () => { - const res = acct.parse('http://example.com/@alice', 'example.com'); - expect(res).toEqual({ username: 'alice', host: null }); - }); - it('parses url style with remote host contained in path', () => { - const res = acct.parse('https://self.example.com/@alice@other.example.com'); + const res = fn('https://self.example.com/@alice@other.example.com'); expect(res).toEqual({ username: 'alice', host: 'other.example.com' }); }); - it('nulls host when localHostname matches the remote host in path', () => { - const res = acct.parse('https://self.example.com/@alice@other.example.com', 'other.example.com'); + it('throws on non-acct-like url path', () => { + expect(() => fn('https://example.com/users/alice')).toThrowError(); + }); +} + +describe('acct.parse', () => { + testParseAcct(acct.parse); +}); + +describe('acct.parseUrl', () => { + testParseUrl(acct.parseUrl); +}); + +describe('acct.parseAcctOrUrl', () => { + testParseAcct(acct.parseAcctOrUrl); + testParseUrl(acct.parseAcctOrUrl); + + it('parse url with localHostname', () => { + const res = acct.parseAcctOrUrl('https://example.com/@alice', 'example.com'); expect(res).toEqual({ username: 'alice', host: null }); }); - it('throws on non-acct-like url path', () => { - expect(() => acct.parse('https://example.com/users/alice')).toThrowError(); + it('parse @username with localHostname', () => { + const res = acct.parseAcctOrUrl('@alice', 'example.com'); + expect(res).toEqual({ username: 'alice', host: null }); + }); +}); + +describe('acct.correctAcct', () => { + it('returns host=null when acct.host is null', () => { + const input: acct.Acct = { username: 'alice', host: null }; + const out = acct.correctAcct(input); + expect(out).toEqual({ username: 'alice', host: null }); + expect(out).not.toBe(input); // immutability }); - it('parses correctly Mr.http', () => { - const res = acct.parse('http'); - expect(res).toEqual({ username: 'http', host: null }); + it('keeps host when localHostname not provided', () => { + const input: acct.Acct = { username: 'bob', host: 'example.com' }; + const out = acct.correctAcct(input); + expect(out).toEqual({ username: 'bob', host: 'example.com' }); + }); + + it('nulls host when it matches localHostname', () => { + const input: acct.Acct = { username: 'carol', host: 'example.com' }; + const out = acct.correctAcct(input, 'example.com'); + expect(out).toEqual({ username: 'carol', host: null }); + }); + + it('keeps host when it differs from localHostname', () => { + const input: acct.Acct = { username: 'dave', host: 'other.example.com' }; + const out = acct.correctAcct(input, 'example.com'); + expect(out).toEqual({ username: 'dave', host: 'other.example.com' }); }); });