diff --git a/CHANGELOG.md b/CHANGELOG.md index c0eaf721c..b26a4a02e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,11 @@ - フォルダーやファイルに対しても開発者モード使用時、IDをコピーできるように - 引用対象を「もっと見る」で展開した場合、「閉じる」で畳めるように - プロフィールURLをコピーできるボタンを追加 #11190 +- `CURRENT_URL`で現在表示中のURLを取得できるように(AiScript) +- ユーザーのContextMenuに「アンテナに追加」ボタンを追加 +- フォローやお気に入り登録をしていないチャンネルを開く時は概要ページを開くように +- 画面ビューワをタップした場合、マウスクリックと同様に画像ビューワを閉じるように +- オフライン時の画面にリロードボタンを追加 - Fix: サーバーメトリクスが90度傾いている - Fix: 非ログイン時にクレデンシャルが必要なページに行くとエラーが出る問題を修正 - Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index 42dddebbf..7555984b2 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -52,6 +52,7 @@ export interface Locale { "deleteAndEdit": string; "deleteAndEditConfirm": string; "addToList": string; + "addToAntenna": string; "sendMessage": string; "copyRSS": string; "copyUsername": string; @@ -2157,4 +2158,4 @@ export interface Locale { declare const locales: { [lang: string]: Locale; }; -export = locales; +export default locales; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 16014da51..82efc8a46 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -49,6 +49,7 @@ delete: "削除" deleteAndEdit: "削除して編集" deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、Renote、返信も全て削除されます。" addToList: "リストに追加" +addToAntenna: "アンテナに追加" sendMessage: "メッセージを送信" copyRSS: "RSSをコピー" copyUsername: "ユーザー名をコピー" diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 2a2375725..b04234fd1 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -31,7 +31,7 @@ function greet() { console.log(themeColor(' | |_|___ ___| |_ ___ _ _ ')); console.log(themeColor(' | | | | |_ -|_ -| \'_| -_| | |')); console.log(themeColor(' |_|_|_|_|___|___|_,_|___|_ |')); - console.log(' ' + chalk.gray(v) + themeColor(' |___|\n'.substr(v.length))); + console.log(' ' + chalk.gray(v) + themeColor(' |___|\n'.substring(v.length))); //#endregion console.log(' Misskey is an open-source decentralized microblogging platform.'); diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index a0afaaf88..9e8d17442 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -103,7 +103,7 @@ export class FetchInstanceMetadataService { if (name) updates.name = name; if (description) updates.description = description; - if (icon || favicon) updates.iconUrl = icon ?? favicon; + if (icon || favicon) updates.iconUrl = (icon && !icon.includes('data:image/png;base64')) ? icon : favicon; if (favicon) updates.faviconUrl = favicon; if (themeColor) updates.themeColor = themeColor; diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index fb36ca3b6..4f3d66dd5 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -570,12 +570,14 @@ export class NoteCreateService implements OnApplicationShutdown { if (data.reply) { // 通知 if (data.reply.userHost === null) { - const threadMuted = await this.noteThreadMutingsRepository.findOneBy({ - userId: data.reply.userId, - threadId: data.reply.threadId ?? data.reply.id, + const isThreadMuted = await this.noteThreadMutingsRepository.exist({ + where: { + userId: data.reply.userId, + threadId: data.reply.threadId ?? data.reply.id, + } }); - if (!threadMuted) { + if (!isThreadMuted) { nm.push(data.reply.userId, 'reply'); this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj); @@ -712,12 +714,14 @@ export class NoteCreateService implements OnApplicationShutdown { @bindThis private async createMentionedEvents(mentionedUsers: MinimumUser[], note: Note, nm: NotificationManager) { for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) { - const threadMuted = await this.noteThreadMutingsRepository.findOneBy({ - userId: u.id, - threadId: note.threadId ?? note.id, + const isThreadMuted = await this.noteThreadMutingsRepository.exist({ + where: { + userId: u.id, + threadId: note.threadId ?? note.id, + }, }); - if (threadMuted) { + if (isThreadMuted) { continue; } diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts index b84591e26..52e9bd369 100644 --- a/packages/backend/src/core/NoteReadService.ts +++ b/packages/backend/src/core/NoteReadService.ts @@ -43,11 +43,13 @@ export class NoteReadService implements OnApplicationShutdown { //#endregion // スレッドミュート - const threadMute = await this.noteThreadMutingsRepository.findOneBy({ - userId: userId, - threadId: note.threadId ?? note.id, + const isThreadMuted = await this.noteThreadMutingsRepository.exist({ + where: { + userId: userId, + threadId: note.threadId ?? note.id, + }, }); - if (threadMute) return; + if (isThreadMuted) return; const unread = { id: this.idService.genId(), @@ -62,9 +64,9 @@ export class NoteReadService implements OnApplicationShutdown { // 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => { - const exist = await this.noteUnreadsRepository.findOneBy({ id: unread.id }); + const exist = await this.noteUnreadsRepository.exist({ where: { id: unread.id } }); - if (exist == null) return; + if (!exist) return; if (params.isMentioned) { this.globalEventService.publishMainStream(userId, 'unreadMention', note.id); diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index e6b17a874..1e44406c1 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -71,12 +71,12 @@ export class SignupService { const secret = generateUserToken(); // Check username duplication - if (await this.usersRepository.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) { + if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) { throw new Error('DUPLICATED_USERNAME'); } // Check deleted username duplication - if (await this.usedUsernamesRepository.findOneBy({ username: username.toLowerCase() })) { + if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) { throw new Error('USED_USERNAME'); } diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 7d90bc2c0..4d7bfeaf2 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -122,22 +122,26 @@ export class UserFollowingService implements OnModuleInit { let autoAccept = false; // 鍵アカウントであっても、既にフォローされていた場合はスルー - const following = await this.followingsRepository.findOneBy({ - followerId: follower.id, - followeeId: followee.id, + const isFollowing = await this.followingsRepository.exist({ + where: { + followerId: follower.id, + followeeId: followee.id, + }, }); - if (following) { + if (isFollowing) { autoAccept = true; } // フォローしているユーザーは自動承認オプション if (!autoAccept && (this.userEntityService.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) { - const followed = await this.followingsRepository.findOneBy({ - followerId: followee.id, - followeeId: follower.id, + const isFollowed = await this.followingsRepository.exist({ + where: { + followerId: followee.id, + followeeId: follower.id, + }, }); - if (followed) autoAccept = true; + if (isFollowed) autoAccept = true; } // Automatically accept if the follower is an account who has moved and the locked followee had accepted the old account. @@ -206,12 +210,14 @@ export class UserFollowingService implements OnModuleInit { this.cacheService.userFollowingsCache.refresh(follower.id); - const req = await this.followRequestsRepository.findOneBy({ - followeeId: followee.id, - followerId: follower.id, + const requestExist = await this.followRequestsRepository.exist({ + where: { + followeeId: followee.id, + followerId: follower.id, + }, }); - if (req) { + if (requestExist) { await this.followRequestsRepository.delete({ followeeId: followee.id, followerId: follower.id, @@ -505,12 +511,14 @@ export class UserFollowingService implements OnModuleInit { } } - const request = await this.followRequestsRepository.findOneBy({ - followeeId: followee.id, - followerId: follower.id, + const requestExist = await this.followRequestsRepository.exist({ + where: { + followeeId: followee.id, + followerId: follower.id, + }, }); - if (request == null) { + if (!requestExist) { throw new IdentifiableError('17447091-ce07-46dd-b331-c1fd4f15b1e7', 'request not found'); } diff --git a/packages/backend/src/core/activitypub/ApAudienceService.ts b/packages/backend/src/core/activitypub/ApAudienceService.ts index 0eab7fa33..a4ab5eae2 100644 --- a/packages/backend/src/core/activitypub/ApAudienceService.ts +++ b/packages/backend/src/core/activitypub/ApAudienceService.ts @@ -16,6 +16,8 @@ type AudienceInfo = { visibleUsers: User[], }; +type GroupedAudience = Record<'public' | 'followers' | 'other', string[]>; + @Injectable() export class ApAudienceService { constructor( @@ -67,11 +69,11 @@ export class ApAudienceService { } @bindThis - private groupingAudience(ids: string[], actor: RemoteUser) { - const groups = { - public: [] as string[], - followers: [] as string[], - other: [] as string[], + private groupingAudience(ids: string[], actor: RemoteUser): GroupedAudience { + const groups: GroupedAudience = { + public: [], + followers: [], + other: [], }; for (const id of ids) { @@ -90,7 +92,7 @@ export class ApAudienceService { } @bindThis - private isPublic(id: string) { + private isPublic(id: string): boolean { return [ 'https://www.w3.org/ns/activitystreams#Public', 'as#Public', @@ -99,9 +101,7 @@ export class ApAudienceService { } @bindThis - private isFollowers(id: string, actor: RemoteUser) { - return ( - id === (actor.followersUri ?? `${actor.uri}/followers`) - ); + private isFollowers(id: string, actor: RemoteUser): boolean { + return id === (actor.followersUri ?? `${actor.uri}/followers`); } } diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 20283a163..d5a530c90 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -99,13 +99,15 @@ export class ApDbResolverService implements OnApplicationShutdown { if (parsed.local) { if (parsed.type !== 'users') return null; - return await this.cacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({ - id: parsed.id, - }).then(x => x ?? undefined)) as LocalUser | undefined ?? null; + return await this.cacheService.userByIdCache.fetchMaybe( + parsed.id, + () => this.usersRepository.findOneBy({ id: parsed.id }).then(x => x ?? undefined), + ) as LocalUser | undefined ?? null; } else { - return await this.cacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({ - uri: parsed.uri, - })) as RemoteUser | null; + return await this.cacheService.uriPersonCache.fetch( + parsed.uri, + () => this.usersRepository.findOneBy({ uri: parsed.uri }), + ) as RemoteUser | null; } } @@ -145,9 +147,11 @@ export class ApDbResolverService implements OnApplicationShutdown { } | null> { const user = await this.apPersonService.resolvePerson(uri) as RemoteUser; - if (user == null) return null; - - const key = await this.publicKeyByUserIdCache.fetch(user.id, () => this.userPublickeysRepository.findOneBy({ userId: user.id }), v => v != null); + const key = await this.publicKeyByUserIdCache.fetch( + user.id, + () => this.userPublickeysRepository.findOneBy({ userId: user.id }), + v => v != null, + ); return { user, diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts index 82c2c9f71..09461973d 100644 --- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts +++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts @@ -29,6 +29,121 @@ const isFollowers = (recipe: IRecipe): recipe is IFollowersRecipe => const isDirect = (recipe: IRecipe): recipe is IDirectRecipe => recipe.type === 'Direct'; +class DeliverManager { + private actor: ThinUser; + private activity: IActivity | null; + private recipes: IRecipe[] = []; + + /** + * Constructor + * @param userEntityService + * @param followingsRepository + * @param queueService + * @param actor Actor + * @param activity Activity to deliver + */ + constructor( + private userEntityService: UserEntityService, + private followingsRepository: FollowingsRepository, + private queueService: QueueService, + + actor: { id: User['id']; host: null; }, + activity: IActivity | null, + ) { + // 型で弾いてはいるが一応ローカルユーザーかチェック + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (actor.host != null) throw new Error('actor.host must be null'); + + // パフォーマンス向上のためキューに突っ込むのはidのみに絞る + this.actor = { + id: actor.id, + }; + this.activity = activity; + } + + /** + * Add recipe for followers deliver + */ + @bindThis + public addFollowersRecipe(): void { + const deliver: IFollowersRecipe = { + type: 'Followers', + }; + + this.addRecipe(deliver); + } + + /** + * Add recipe for direct deliver + * @param to To + */ + @bindThis + public addDirectRecipe(to: RemoteUser): void { + const recipe: IDirectRecipe = { + type: 'Direct', + to, + }; + + this.addRecipe(recipe); + } + + /** + * Add recipe + * @param recipe Recipe + */ + @bindThis + public addRecipe(recipe: IRecipe): void { + this.recipes.push(recipe); + } + + /** + * Execute delivers + */ + @bindThis + public async execute(): Promise { + // The value flags whether it is shared or not. + // key: inbox URL, value: whether it is sharedInbox + const inboxes = new Map(); + + // build inbox list + // Process follower recipes first to avoid duplication when processing direct recipes later. + if (this.recipes.some(r => isFollowers(r))) { + // followers deliver + // TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう + // ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう? + const followers = await this.followingsRepository.find({ + where: { + followeeId: this.actor.id, + followerHost: Not(IsNull()), + }, + select: { + followerSharedInbox: true, + followerInbox: true, + }, + }); + + for (const following of followers) { + const inbox = following.followerSharedInbox ?? following.followerInbox; + if (inbox === null) throw new Error('inbox is null'); + inboxes.set(inbox, following.followerSharedInbox != null); + } + } + + for (const recipe of this.recipes.filter(isDirect)) { + // check that shared inbox has not been added yet + if (recipe.to.sharedInbox !== null && inboxes.has(recipe.to.sharedInbox)) continue; + + // check that they actually have an inbox + if (recipe.to.inbox === null) continue; + + inboxes.set(recipe.to.inbox, false); + } + + // deliver + this.queueService.deliverMany(this.actor, this.activity, inboxes); + } +} + @Injectable() export class ApDeliverManagerService { constructor( @@ -52,7 +167,7 @@ export class ApDeliverManagerService { * @param activity Activity */ @bindThis - public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: IActivity) { + public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: IActivity): Promise { const manager = new DeliverManager( this.userEntityService, this.followingsRepository, @@ -71,7 +186,7 @@ export class ApDeliverManagerService { * @param to Target user */ @bindThis - public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: IActivity, to: RemoteUser) { + public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: IActivity, to: RemoteUser): Promise { const manager = new DeliverManager( this.userEntityService, this.followingsRepository, @@ -84,7 +199,7 @@ export class ApDeliverManagerService { } @bindThis - public createDeliverManager(actor: { id: User['id']; host: null; }, activity: IActivity | null) { + public createDeliverManager(actor: { id: User['id']; host: null; }, activity: IActivity | null): DeliverManager { return new DeliverManager( this.userEntityService, this.followingsRepository, @@ -95,123 +210,3 @@ export class ApDeliverManagerService { ); } } - -class DeliverManager { - private actor: ThinUser; - private activity: IActivity | null; - private recipes: IRecipe[] = []; - - /** - * Constructor - * @param userEntityService - * @param followingsRepository - * @param queueService - * @param actor Actor - * @param activity Activity to deliver - */ - constructor( - private userEntityService: UserEntityService, - private followingsRepository: FollowingsRepository, - private queueService: QueueService, - - actor: { id: User['id']; host: null; }, - activity: IActivity | null, - ) { - // 型で弾いてはいるが一応ローカルユーザーかチェック - if (actor.host != null) throw new Error('actor.host must be null'); - - // パフォーマンス向上のためキューに突っ込むのはidのみに絞る - this.actor = { - id: actor.id, - }; - this.activity = activity; - } - - /** - * Add recipe for followers deliver - */ - @bindThis - public addFollowersRecipe() { - const deliver = { - type: 'Followers', - } as IFollowersRecipe; - - this.addRecipe(deliver); - } - - /** - * Add recipe for direct deliver - * @param to To - */ - @bindThis - public addDirectRecipe(to: RemoteUser) { - const recipe = { - type: 'Direct', - to, - } as IDirectRecipe; - - this.addRecipe(recipe); - } - - /** - * Add recipe - * @param recipe Recipe - */ - @bindThis - public addRecipe(recipe: IRecipe) { - this.recipes.push(recipe); - } - - /** - * Execute delivers - */ - @bindThis - public async execute() { - // The value flags whether it is shared or not. - // key: inbox URL, value: whether it is sharedInbox - const inboxes = new Map(); - - /* - build inbox list - - Process follower recipes first to avoid duplication when processing - direct recipes later. - */ - if (this.recipes.some(r => isFollowers(r))) { - // followers deliver - // TODO: SELECT DISTINCT ON ("followerSharedInbox") "followerSharedInbox" みたいな問い合わせにすればよりパフォーマンス向上できそう - // ただ、sharedInboxがnullなリモートユーザーも稀におり、その対応ができなさそう? - const followers = await this.followingsRepository.find({ - where: { - followeeId: this.actor.id, - followerHost: Not(IsNull()), - }, - select: { - followerSharedInbox: true, - followerInbox: true, - }, - }) as { - followerSharedInbox: string | null; - followerInbox: string; - }[]; - - for (const following of followers) { - const inbox = following.followerSharedInbox ?? following.followerInbox; - inboxes.set(inbox, following.followerSharedInbox != null); - } - } - - this.recipes.filter((recipe): recipe is IDirectRecipe => - // followers recipes have already been processed - isDirect(recipe) - // check that shared inbox has not been added yet - && !(recipe.to.sharedInbox && inboxes.has(recipe.to.sharedInbox)) - // check that they actually have an inbox - && recipe.to.inbox != null, - ) - .forEach(recipe => inboxes.set(recipe.to.inbox!, false)); - - // deliver - this.queueService.deliverMany(this.actor, this.activity, inboxes); - } -} diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index fdc6350ba..8a301d143 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -21,7 +21,7 @@ import { CacheService } from '@/core/CacheService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { QueueService } from '@/core/QueueService.js'; -import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository, } from '@/models/index.js'; +import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js'; import { bindThis } from '@/decorators.js'; import type { RemoteUser } from '@/models/entities/User.js'; import { getApHrefNullable, getApId, getApIds, getApType, getOneApHrefNullable, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isOrderedCollectionPage, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; @@ -115,7 +115,7 @@ export class ApInboxService { if (actor.uri) { if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { setImmediate(() => { - this.apPersonService.updatePerson(actor.uri!); + this.apPersonService.updatePerson(actor.uri); }); } } @@ -237,7 +237,7 @@ export class ApInboxService { @bindThis private async add(actor: RemoteUser, activity: IAdd): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { + if (actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -281,7 +281,7 @@ export class ApInboxService { const unlock = await this.appLockService.getApLock(uri); try { - // 既に同じURIを持つものが登録されていないかチェック + // 既に同じURIを持つものが登録されていないかチェック const exist = await this.apNoteService.fetchNote(uri); if (exist) { return; @@ -300,7 +300,7 @@ export class ApInboxService { return; } - this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode ?? err}`); + this.logger.warn(`Error in announce target ${targetUri} - ${err.statusCode}`); } throw err; } @@ -417,7 +417,7 @@ export class ApInboxService { @bindThis private async delete(actor: RemoteUser, activity: IDelete): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { + if (actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -428,7 +428,7 @@ export class ApInboxService { // typeが不明だけど、どうせ消えてるのでremote resolveしない formerType = undefined; } else { - const object = activity.object as IObject; + const object = activity.object; if (isTombstone(object)) { formerType = toSingle(object.formerType); } else { @@ -511,7 +511,10 @@ export class ApInboxService { // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する const uris = getApIds(activity.object); - const userIds = uris.filter(uri => uri.startsWith(this.config.url + '/users/')).map(uri => uri.split('/').pop()!); + const userIds = uris + .filter(uri => uri.startsWith(this.config.url + '/users/')) + .map(uri => uri.split('/').at(-1)) + .filter((userId): userId is string => userId !== undefined); const users = await this.usersRepository.findBy({ id: In(userIds), }); @@ -574,7 +577,7 @@ export class ApInboxService { @bindThis private async remove(actor: RemoteUser, activity: IRemove): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { + if (actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -594,7 +597,7 @@ export class ApInboxService { @bindThis private async undo(actor: RemoteUser, activity: IUndo): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { + if (actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -626,12 +629,14 @@ export class ApInboxService { return 'skip: follower not found'; } - const following = await this.followingsRepository.findOneBy({ - followerId: follower.id, - followeeId: actor.id, + const isFollowing = await this.followingsRepository.exist({ + where: { + followerId: follower.id, + followeeId: actor.id, + }, }); - if (following) { + if (isFollowing) { await this.userFollowingService.unfollow(follower, actor); return 'ok: unfollowed'; } @@ -681,22 +686,26 @@ export class ApInboxService { return 'skip: フォロー解除しようとしているユーザーはローカルユーザーではありません'; } - const req = await this.followRequestsRepository.findOneBy({ - followerId: actor.id, - followeeId: followee.id, + const requestExist = await this.followRequestsRepository.exist({ + where: { + followerId: actor.id, + followeeId: followee.id, + }, }); - const following = await this.followingsRepository.findOneBy({ - followerId: actor.id, - followeeId: followee.id, + const isFollowing = await this.followingsRepository.exist({ + where: { + followerId: actor.id, + followeeId: followee.id, + }, }); - if (req) { + if (requestExist) { await this.userFollowingService.cancelFollowRequest(followee, actor); return 'ok: follow request canceled'; } - if (following) { + if (isFollowing) { await this.userFollowingService.unfollow(actor, followee); return 'ok: unfollowed'; } @@ -721,7 +730,7 @@ export class ApInboxService { @bindThis private async update(actor: RemoteUser, activity: IUpdate): Promise { - if ('actor' in activity && actor.uri !== activity.actor) { + if (actor.uri !== activity.actor) { return 'skip: invalid actor'; } @@ -735,7 +744,7 @@ export class ApInboxService { }); if (isActor(object)) { - await this.apPersonService.updatePerson(actor.uri!, resolver, object); + await this.apPersonService.updatePerson(actor.uri, resolver, object); return 'ok: Person updated'; } else if (getApType(object) === 'Question') { await this.apQuestionService.updateQuestion(object, resolver).catch(err => console.error(err)); diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts index 411cf17d1..d7269eca9 100644 --- a/packages/backend/src/core/activitypub/ApMfmService.ts +++ b/packages/backend/src/core/activitypub/ApMfmService.ts @@ -4,9 +4,9 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { MfmService } from '@/core/MfmService.js'; import type { Note } from '@/models/entities/Note.js'; +import { bindThis } from '@/decorators.js'; import { extractApHashtagObjects } from './models/tag.js'; import type { IObject } from './type.js'; -import { bindThis } from '@/decorators.js'; @Injectable() export class ApMfmService { @@ -19,14 +19,13 @@ export class ApMfmService { } @bindThis - public htmlToMfm(html: string, tag?: IObject | IObject[]) { - const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null); - + public htmlToMfm(html: string, tag?: IObject | IObject[]): string { + const hashtagNames = extractApHashtagObjects(tag).map(x => x.name); return this.mfmService.fromHtml(html, hashtagNames); } @bindThis - public getNoteHtml(note: Note) { + public getNoteHtml(note: Note): string | null { if (!note.text) return ''; return this.mfmService.toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); } diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index d8b95ca4d..95e3a936d 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -1,6 +1,6 @@ import { createPublicKey } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; -import { In, IsNull } from 'typeorm'; +import { In } from 'typeorm'; import { v4 as uuid } from 'uuid'; import * as mfm from 'mfm-js'; import { DI } from '@/di-symbols.js'; @@ -26,7 +26,6 @@ import { isNotNull } from '@/misc/is-not-null.js'; import { LdSignatureService } from './LdSignatureService.js'; import { ApMfmService } from './ApMfmService.js'; import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js'; -import type { IIdentifier } from './models/identifier.js'; @Injectable() export class ApRendererService { @@ -63,7 +62,7 @@ export class ApRendererService { } @bindThis - public renderAccept(object: any, user: { id: User['id']; host: null }): IAccept { + public renderAccept(object: string | IObject, user: { id: User['id']; host: null }): IAccept { return { type: 'Accept', actor: this.userEntityService.genLocalUserUri(user.id), @@ -72,7 +71,7 @@ export class ApRendererService { } @bindThis - public renderAdd(user: LocalUser, target: any, object: any): IAdd { + public renderAdd(user: LocalUser, target: string | IObject | undefined, object: string | IObject): IAdd { return { type: 'Add', actor: this.userEntityService.genLocalUserUri(user.id), @@ -82,7 +81,7 @@ export class ApRendererService { } @bindThis - public renderAnnounce(object: any, note: Note): IAnnounce { + public renderAnnounce(object: string | IObject, note: Note): IAnnounce { const attributedTo = this.userEntityService.genLocalUserUri(note.userId); let to: string[] = []; @@ -133,13 +132,13 @@ export class ApRendererService { @bindThis public renderCreate(object: IObject, note: Note): ICreate { - const activity = { + const activity: ICreate = { id: `${this.config.url}/notes/${note.id}/activity`, actor: this.userEntityService.genLocalUserUri(note.userId), type: 'Create', published: note.createdAt.toISOString(), object, - } as ICreate; + }; if (object.to) activity.to = object.to; if (object.cc) activity.cc = object.cc; @@ -209,7 +208,7 @@ export class ApRendererService { * @param id Follower|Followee ID */ @bindThis - public async renderFollowUser(id: User['id']) { + public async renderFollowUser(id: User['id']): Promise { const user = await this.usersRepository.findOneByOrFail({ id: id }) as PartialLocalUser | PartialRemoteUser; return this.userEntityService.getUserUri(user); } @@ -223,8 +222,8 @@ export class ApRendererService { return { id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`, type: 'Follow', - actor: this.userEntityService.getUserUri(follower)!, - object: this.userEntityService.getUserUri(followee)!, + actor: this.userEntityService.getUserUri(follower), + object: this.userEntityService.getUserUri(followee), }; } @@ -264,14 +263,14 @@ export class ApRendererService { public async renderLike(noteReaction: NoteReaction, note: { uri: string | null }): Promise { const reaction = noteReaction.reaction; - const object = { + const object: ILike = { type: 'Like', id: `${this.config.url}/likes/${noteReaction.id}`, actor: `${this.config.url}/users/${noteReaction.userId}`, object: note.uri ? note.uri : `${this.config.url}/notes/${noteReaction.noteId}`, content: reaction, _misskey_reaction: reaction, - } as ILike; + }; if (reaction.startsWith(':')) { const name = reaction.replaceAll(':', ''); @@ -287,7 +286,7 @@ export class ApRendererService { public renderMention(mention: PartialLocalUser | PartialRemoteUser): IApMention { return { type: 'Mention', - href: this.userEntityService.getUserUri(mention)!, + href: this.userEntityService.getUserUri(mention), name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as LocalUser).username}`, }; } @@ -297,8 +296,8 @@ export class ApRendererService { src: PartialLocalUser | PartialRemoteUser, dst: PartialLocalUser | PartialRemoteUser, ): IMove { - const actor = this.userEntityService.getUserUri(src)!; - const target = this.userEntityService.getUserUri(dst)!; + const actor = this.userEntityService.getUserUri(src); + const target = this.userEntityService.getUserUri(dst); return { id: `${this.config.url}/moves/${src.id}/${dst.id}`, actor, @@ -310,10 +309,10 @@ export class ApRendererService { @bindThis public async renderNote(note: Note, dive = true): Promise { - const getPromisedFiles = async (ids: string[]) => { - if (!ids || ids.length === 0) return []; + const getPromisedFiles = async (ids: string[]): Promise => { + if (ids.length === 0) return []; const items = await this.driveFilesRepository.findBy({ id: In(ids) }); - return ids.map(id => items.find(item => item.id === id)).filter(item => item != null) as DriveFile[]; + return ids.map(id => items.find(item => item.id === id)).filter((item): item is DriveFile => item != null); }; let inReplyTo; @@ -323,9 +322,9 @@ export class ApRendererService { inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId }); if (inReplyToNote != null) { - const inReplyToUser = await this.usersRepository.findOneBy({ id: inReplyToNote.userId }); + const inReplyToUserExist = await this.usersRepository.exist({ where: { id: inReplyToNote.userId } }); - if (inReplyToUser != null) { + if (inReplyToUserExist) { if (inReplyToNote.uri) { inReplyTo = inReplyToNote.uri; } else { @@ -375,7 +374,7 @@ export class ApRendererService { id: In(note.mentions), }) : []; - const hashtagTags = (note.tags ?? []).map(tag => this.renderHashtag(tag)); + const hashtagTags = note.tags.map(tag => this.renderHashtag(tag)); const mentionTags = mentionedUsers.map(u => this.renderMention(u as LocalUser | RemoteUser)); const files = await getPromisedFiles(note.fileIds); @@ -451,37 +450,26 @@ export class ApRendererService { @bindThis public async renderPerson(user: LocalUser) { const id = this.userEntityService.genLocalUserUri(user.id); - const isSystem = !!user.username.match(/\./); + const isSystem = user.username.includes('.'); const [avatar, banner, profile] = await Promise.all([ - user.avatarId ? this.driveFilesRepository.findOneBy({ id: user.avatarId }) : Promise.resolve(undefined), - user.bannerId ? this.driveFilesRepository.findOneBy({ id: user.bannerId }) : Promise.resolve(undefined), + user.avatarId ? this.driveFilesRepository.findOneBy({ id: user.avatarId }) : undefined, + user.bannerId ? this.driveFilesRepository.findOneBy({ id: user.bannerId }) : undefined, this.userProfilesRepository.findOneByOrFail({ userId: user.id }), ]); - const attachment: { + const attachment = profile.fields.map(field => ({ type: 'PropertyValue', - name: string, - value: string, - identifier?: IIdentifier, - }[] = []; - - if (profile.fields) { - for (const field of profile.fields) { - attachment.push({ - type: 'PropertyValue', - name: field.name, - value: (field.value != null && field.value.match(/^https?:/)) - ? `${new URL(field.value).href}` - : field.value, - }); - } - } + name: field.name, + value: /^https?:/.test(field.value) + ? `${new URL(field.value).href}` + : field.value, + })); const emojis = await this.getEmojis(user.emojis); const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji)); - const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag)); + const hashtagTags = user.tags.map(tag => this.renderHashtag(tag)); const tag = [ ...apemojis, @@ -490,7 +478,7 @@ export class ApRendererService { const keypair = await this.userKeypairService.getUserKeypair(user.id); - const person = { + const person: any = { type: isSystem ? 'Application' : user.isBot ? 'Service' : 'Person', id, inbox: `${id}/inbox`, @@ -508,11 +496,11 @@ export class ApRendererService { image: banner ? this.renderImage(banner) : null, tag, manuallyApprovesFollowers: user.isLocked, - discoverable: !!user.isExplorable, + discoverable: user.isExplorable, publicKey: this.renderKey(user, keypair, '#main-key'), isCat: user.isCat, attachment: attachment.length ? attachment : undefined, - } as any; + }; if (user.movedToUri) { person.movedTo = user.movedToUri; @@ -552,7 +540,7 @@ export class ApRendererService { } @bindThis - public renderReject(object: any, user: { id: User['id'] }): IReject { + public renderReject(object: string | IObject, user: { id: User['id'] }): IReject { return { type: 'Reject', actor: this.userEntityService.genLocalUserUri(user.id), @@ -561,7 +549,7 @@ export class ApRendererService { } @bindThis - public renderRemove(user: { id: User['id'] }, target: any, object: any): IRemove { + public renderRemove(user: { id: User['id'] }, target: string | IObject | undefined, object: string | IObject): IRemove { return { type: 'Remove', actor: this.userEntityService.genLocalUserUri(user.id), @@ -579,8 +567,8 @@ export class ApRendererService { } @bindThis - public renderUndo(object: any, user: { id: User['id'] }): IUndo { - const id = typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined; + public renderUndo(object: string | IObject, user: { id: User['id'] }): IUndo { + const id = typeof object !== 'string' && typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined; return { type: 'Undo', @@ -592,7 +580,7 @@ export class ApRendererService { } @bindThis - public renderUpdate(object: any, user: { id: User['id'] }): IUpdate { + public renderUpdate(object: string | IObject, user: { id: User['id'] }): IUpdate { return { id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`, actor: this.userEntityService.genLocalUserUri(user.id), @@ -658,7 +646,7 @@ export class ApRendererService { vcard: 'http://www.w3.org/2006/vcard/ns#', }, ], - }, x as T & { id: string; }); + }, x as T & { id: string }); } @bindThis @@ -683,13 +671,13 @@ export class ApRendererService { */ @bindThis public renderOrderedCollectionPage(id: string, totalItems: any, orderedItems: any, partOf: string, prev?: string, next?: string) { - const page = { + const page: any = { id, partOf, type: 'OrderedCollectionPage', totalItems, orderedItems, - } as any; + }; if (prev) page.prev = prev; if (next) page.next = next; @@ -706,7 +694,7 @@ export class ApRendererService { * @param orderedItems attached objects (optional) */ @bindThis - public renderOrderedCollection(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: IObject[]) { + public renderOrderedCollection(id: string, totalItems: number, first?: string, last?: string, orderedItems?: IObject[]) { const page: any = { id, type: 'OrderedCollection', @@ -722,7 +710,7 @@ export class ApRendererService { @bindThis private async getEmojis(names: string[]): Promise { - if (names == null || names.length === 0) return []; + if (names.length === 0) return []; const allEmojis = await this.customEmojiService.localEmojisCache.fetch(); const emojis = names.map(name => allEmojis.get(name)).filter(isNotNull); diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index 5005612ab..44676cac2 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -140,7 +140,7 @@ export class ApRequestService { } @bindThis - public async signedPost(user: { id: User['id'] }, url: string, object: any) { + public async signedPost(user: { id: User['id'] }, url: string, object: unknown): Promise { const body = JSON.stringify(object); const keypair = await this.userKeypairService.getUserKeypair(user.id); @@ -169,7 +169,7 @@ export class ApRequestService { * @param url URL to fetch */ @bindThis - public async signedGet(url: string, user: { id: User['id'] }) { + public async signedGet(url: string, user: { id: User['id'] }): Promise { const keypair = await this.userKeypairService.getUserKeypair(user.id); const req = ApRequestCreator.createSignedGet({ diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts index 62dcad68d..3dfdee242 100644 --- a/packages/backend/src/core/activitypub/ApResolverService.ts +++ b/packages/backend/src/core/activitypub/ApResolverService.ts @@ -73,10 +73,6 @@ export class Resolver { @bindThis public async resolve(value: string | IObject): Promise { - if (value == null) { - throw new Error('resolvee is null (or undefined)'); - } - if (typeof value !== 'string') { return value; } @@ -116,11 +112,11 @@ export class Resolver { ? await this.apRequestService.signedGet(value, this.user) as IObject : await this.httpRequestService.getJson(value, 'application/activity+json, application/ld+json')) as IObject; - if (object == null || ( + if ( Array.isArray(object['@context']) ? !(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') : object['@context'] !== 'https://www.w3.org/ns/activitystreams' - )) { + ) { throw new Error('invalid response'); } diff --git a/packages/backend/src/core/activitypub/LdSignatureService.ts b/packages/backend/src/core/activitypub/LdSignatureService.ts index 20fe2a0a7..af7e24322 100644 --- a/packages/backend/src/core/activitypub/LdSignatureService.ts +++ b/packages/backend/src/core/activitypub/LdSignatureService.ts @@ -3,6 +3,8 @@ import { Injectable } from '@nestjs/common'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { bindThis } from '@/decorators.js'; import { CONTEXTS } from './misc/contexts.js'; +import type { JsonLdDocument } from 'jsonld'; +import type { JsonLd, RemoteDocument } from 'jsonld/jsonld-spec.js'; // RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017 @@ -18,22 +20,21 @@ class LdSignature { @bindThis public async signRsaSignature2017(data: any, privateKey: string, creator: string, domain?: string, created?: Date): Promise { - const options = { - type: 'RsaSignature2017', - creator, - domain, - nonce: crypto.randomBytes(16).toString('hex'), - created: (created ?? new Date()).toISOString(), - } as { + const options: { type: string; creator: string; domain?: string; nonce: string; created: string; + } = { + type: 'RsaSignature2017', + creator, + nonce: crypto.randomBytes(16).toString('hex'), + created: (created ?? new Date()).toISOString(), }; - if (!domain) { - delete options.domain; + if (domain) { + options.domain = domain; } const toBeSigned = await this.createVerifyData(data, options); @@ -62,7 +63,7 @@ class LdSignature { } @bindThis - public async createVerifyData(data: any, options: any) { + public async createVerifyData(data: any, options: any): Promise { const transformedOptions = { ...options, '@context': 'https://w3id.org/identity/v1', @@ -82,7 +83,7 @@ class LdSignature { } @bindThis - public async normalize(data: any) { + public async normalize(data: JsonLdDocument): Promise { const customLoader = this.getLoader(); // XXX: Importing jsonld dynamically since Jest frequently fails to import it statically // https://github.com/misskey-dev/misskey/pull/9894#discussion_r1103753595 @@ -93,14 +94,14 @@ class LdSignature { @bindThis private getLoader() { - return async (url: string): Promise => { - if (!url.match('^https?\:\/\/')) throw new Error(`Invalid URL ${url}`); + return async (url: string): Promise => { + if (!/^https?:\/\//.test(url)) throw new Error(`Invalid URL ${url}`); if (this.preLoad) { if (url in CONTEXTS) { if (this.debug) console.debug(`HIT: ${url}`); return { - contextUrl: null, + contextUrl: undefined, document: CONTEXTS[url], documentUrl: url, }; @@ -110,7 +111,7 @@ class LdSignature { if (this.debug) console.debug(`MISS: ${url}`); const document = await this.fetchDocument(url); return { - contextUrl: null, + contextUrl: undefined, document: document, documentUrl: url, }; @@ -118,13 +119,17 @@ class LdSignature { } @bindThis - private async fetchDocument(url: string) { - const json = await this.httpRequestService.send(url, { - headers: { - Accept: 'application/ld+json, application/json', + private async fetchDocument(url: string): Promise { + const json = await this.httpRequestService.send( + url, + { + headers: { + Accept: 'application/ld+json, application/json', + }, + timeout: this.loderTimeout, }, - timeout: this.loderTimeout, - }, { throwErrorWhenResponseNotOk: false }).then(res => { + { throwErrorWhenResponseNotOk: false }, + ).then(res => { if (!res.ok) { throw new Error(`${res.status} ${res.statusText}`); } else { @@ -132,7 +137,7 @@ class LdSignature { } }); - return json; + return json as JsonLd; } @bindThis diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts index aee0d3629..2bcd3811b 100644 --- a/packages/backend/src/core/activitypub/misc/contexts.ts +++ b/packages/backend/src/core/activitypub/misc/contexts.ts @@ -1,3 +1,5 @@ +import type { JsonLd } from 'jsonld/jsonld-spec.js'; + /* eslint:disable:quotemark indent */ const id_v1 = { '@context': { @@ -86,7 +88,7 @@ const id_v1 = { 'accessControl': { '@id': 'perm:accessControl', '@type': '@id' }, 'writePermission': { '@id': 'perm:writePermission', '@type': '@id' }, }, -}; +} satisfies JsonLd; const security_v1 = { '@context': { @@ -137,7 +139,7 @@ const security_v1 = { 'signatureAlgorithm': 'sec:signingAlgorithm', 'signatureValue': 'sec:signatureValue', }, -}; +} satisfies JsonLd; const activitystreams = { '@context': { @@ -517,9 +519,9 @@ const activitystreams = { '@type': '@id', }, }, -}; +} satisfies JsonLd; -export const CONTEXTS: Record = { +export const CONTEXTS: Record = { 'https://w3id.org/identity/v1': id_v1, 'https://w3id.org/security/v1': security_v1, 'https://www.w3.org/ns/activitystreams': activitystreams, diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index d3359ef90..46ed976a6 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -200,7 +200,7 @@ export class ApNoteService { | { status: 'ok'; res: Note } | { status: 'permerror' | 'temperror' } > => { - if (!uri.match(/^https?:/)) return { status: 'permerror' }; + if (!/^https?:/.test(uri)) return { status: 'permerror' }; try { const res = await this.resolveNote(uri); if (res == null) return { status: 'permerror' }; diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts index 58371c8fe..00890654e 100644 --- a/packages/backend/src/core/activitypub/type.ts +++ b/packages/backend/src/core/activitypub/type.ts @@ -218,7 +218,6 @@ export interface IApPropertyValue extends IObject { } export const isPropertyValue = (object: IObject): object is IApPropertyValue => - object && getApType(object) === 'PropertyValue' && typeof object.name === 'string' && 'value' in object && diff --git a/packages/backend/src/core/chart/core.ts b/packages/backend/src/core/chart/core.ts index d352adcc1..7a89233ed 100644 --- a/packages/backend/src/core/chart/core.ts +++ b/packages/backend/src/core/chart/core.ts @@ -254,7 +254,7 @@ export default abstract class Chart { private convertRawRecord(x: RawRecord): KVs { const kvs = {} as Record; for (const k of Object.keys(x).filter((k) => k.startsWith(COLUMN_PREFIX)) as (keyof Columns)[]) { - kvs[(k as string).substr(COLUMN_PREFIX.length).split(COLUMN_DELIMITER).join('.')] = x[k] as unknown as number; + kvs[(k as string).substring(COLUMN_PREFIX.length).split(COLUMN_DELIMITER).join('.')] = x[k] as unknown as number; } return kvs as KVs; } @@ -627,7 +627,7 @@ export default abstract class Chart { } // 要求された範囲の最も古い箇所に位置するログが存在しなかったら - } else if (!isTimeSame(new Date(logs[logs.length - 1].date * 1000), gt)) { + } else if (!isTimeSame(new Date(logs.at(-1)!.date * 1000), gt)) { // 要求された範囲の最も古い箇所時点での最も新しいログを持ってきて末尾に追加する // (隙間埋めできないため) const outdatedLog = await repository.findOne({ diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts index 15ffd4486..d24657260 100644 --- a/packages/backend/src/core/entities/ChannelEntityService.ts +++ b/packages/backend/src/core/entities/ChannelEntityService.ts @@ -47,17 +47,26 @@ export class ChannelEntityService { const banner = channel.bannerId ? await this.driveFilesRepository.findOneBy({ id: channel.bannerId }) : null; - const hasUnreadNote = meId ? (await this.noteUnreadsRepository.findOneBy({ noteChannelId: channel.id, userId: meId })) != null : undefined; + const hasUnreadNote = meId ? await this.noteUnreadsRepository.exist({ + where: { + noteChannelId: channel.id, + userId: meId + }, + }) : undefined; - const following = meId ? await this.channelFollowingsRepository.findOneBy({ - followerId: meId, - followeeId: channel.id, - }) : null; + const isFollowing = meId ? await this.channelFollowingsRepository.exist({ + where: { + followerId: meId, + followeeId: channel.id, + }, + }) : false; - const favorite = meId ? await this.channelFavoritesRepository.findOneBy({ - userId: meId, - channelId: channel.id, - }) : null; + const isFavorited = meId ? await this.channelFavoritesRepository.exist({ + where: { + userId: meId, + channelId: channel.id, + }, + }) : false; const pinnedNotes = channel.pinnedNoteIds.length > 0 ? await this.notesRepository.find({ where: { @@ -80,8 +89,8 @@ export class ChannelEntityService { notesCount: channel.notesCount, ...(me ? { - isFollowing: following != null, - isFavorited: favorite != null, + isFollowing, + isFavorited, hasUnreadNote, } : {}), diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts index 33d3c5380..f558cbc33 100644 --- a/packages/backend/src/core/entities/ClipEntityService.ts +++ b/packages/backend/src/core/entities/ClipEntityService.ts @@ -39,7 +39,7 @@ export class ClipEntityService { description: clip.description, isPublic: clip.isPublic, favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }), - isFavorited: meId ? await this.clipFavoritesRepository.findOneBy({ clipId: clip.id, userId: meId }).then(x => x != null) : undefined, + isFavorited: meId ? await this.clipFavoritesRepository.exist({ where: { clipId: clip.id, userId: meId } }) : undefined, }); } diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts index e52a59188..92345457c 100644 --- a/packages/backend/src/core/entities/FlashEntityService.ts +++ b/packages/backend/src/core/entities/FlashEntityService.ts @@ -40,7 +40,7 @@ export class FlashEntityService { summary: flash.summary, script: flash.script, likedCount: flash.likedCount, - isLiked: meId ? await this.flashLikesRepository.findOneBy({ flashId: flash.id, userId: meId }).then(x => x != null) : undefined, + isLiked: meId ? await this.flashLikesRepository.exist({ where: { flashId: flash.id, userId: meId } }) : undefined, }); } diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts index 632c75304..c44a5df11 100644 --- a/packages/backend/src/core/entities/GalleryPostEntityService.ts +++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts @@ -46,7 +46,7 @@ export class GalleryPostEntityService { tags: post.tags.length > 0 ? post.tags : undefined, isSensitive: post.isSensitive, likedCount: post.likedCount, - isLiked: meId ? await this.galleryLikesRepository.findOneBy({ postId: post.id, userId: meId }).then(x => x != null) : undefined, + isLiked: meId ? await this.galleryLikesRepository.exist({ where: { postId: post.id, userId: meId } }) : undefined, }); } diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index e730c0eb8..546e5f56d 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -106,16 +106,14 @@ export class NoteEntityService implements OnModuleInit { hide = false; } else { // フォロワーかどうか - const following = await this.followingsRepository.findOneBy({ - followeeId: packedNote.userId, - followerId: meId, + const isFollowing = await this.followingsRepository.exist({ + where: { + followeeId: packedNote.userId, + followerId: meId, + }, }); - if (following == null) { - hide = true; - } else { - hide = false; - } + hide = !isFollowing; } } diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts index d6da85663..94b26a501 100644 --- a/packages/backend/src/core/entities/PageEntityService.ts +++ b/packages/backend/src/core/entities/PageEntityService.ts @@ -97,7 +97,7 @@ export class PageEntityService { eyeCatchingImage: page.eyeCatchingImageId ? await this.driveFileEntityService.pack(page.eyeCatchingImageId) : null, attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter((x): x is DriveFile => x != null)), likedCount: page.likedCount, - isLiked: meId ? await this.pageLikesRepository.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined, + isLiked: meId ? await this.pageLikesRepository.exist({ where: { pageId: page.id, userId: meId } }) : undefined, }); } diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 6dacaa103..7d248f852 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -230,12 +230,14 @@ export class UserEntityService implements OnModuleInit { /* const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId); - const unread = myAntennas.length > 0 ? await this.antennaNotesRepository.findOneBy({ - antennaId: In(myAntennas.map(x => x.id)), - read: false, - }) : null; + const isUnread = (myAntennas.length > 0 ? await this.antennaNotesRepository.exist({ + where: { + antennaId: In(myAntennas.map(x => x.id)), + read: false, + }, + }) : false); - return unread != null; + return isUnread; */ return false; // TODO } diff --git a/packages/backend/src/misc/acct.ts b/packages/backend/src/misc/acct.ts index d1a6852a9..fb3b657cf 100644 --- a/packages/backend/src/misc/acct.ts +++ b/packages/backend/src/misc/acct.ts @@ -4,7 +4,7 @@ export type Acct = { }; export function parse(acct: string): Acct { - if (acct.startsWith('@')) acct = acct.substr(1); + if (acct.startsWith('@')) acct = acct.substring(1); const split = acct.split('@', 2); return { username: split[0], host: split[1] ?? null }; } diff --git a/packages/backend/src/misc/prelude/array.ts b/packages/backend/src/misc/prelude/array.ts index 0b2830cb7..2524eacfb 100644 --- a/packages/backend/src/misc/prelude/array.ts +++ b/packages/backend/src/misc/prelude/array.ts @@ -67,8 +67,9 @@ export function maximum(xs: number[]): number { export function groupBy(f: EndoRelation, xs: T[]): T[][] { const groups = [] as T[][]; for (const x of xs) { - if (groups.length !== 0 && f(groups[groups.length - 1][0], x)) { - groups[groups.length - 1].push(x); + const lastGroup = groups.at(-1); + if (lastGroup !== undefined && f(lastGroup[0], x)) { + lastGroup.push(x); } else { groups.push([x]); } diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts index c54bf59ae..6f887089e 100644 --- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan, Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { DriveFilesRepository } from '@/models/index.js'; +import type { DriveFile, DriveFilesRepository } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; @@ -31,7 +31,7 @@ export class CleanRemoteFilesProcessorService { this.logger.info('Deleting cached remote files...'); let deletedCount = 0; - let cursor: any = null; + let cursor: DriveFile['id'] | null = null; while (true) { const files = await this.driveFilesRepository.find({ @@ -51,7 +51,7 @@ export class CleanRemoteFilesProcessorService { break; } - cursor = files[files.length - 1].id; + cursor = files.at(-1)?.id ?? null; await Promise.all(files.map(file => this.driveService.deleteFileSync(file, true))); diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts index 65ded170b..b2886563f 100644 --- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts @@ -70,7 +70,7 @@ export class DeleteAccountProcessorService { break; } - cursor = notes[notes.length - 1].id; + cursor = notes.at(-1)?.id ?? null; await this.notesRepository.delete(notes.map(note => note.id)); @@ -101,7 +101,7 @@ export class DeleteAccountProcessorService { break; } - cursor = files[files.length - 1].id; + cursor = files.at(-1)?.id ?? null; for (const file of files) { await this.driveService.deleteFileSync(file); diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts index 6772c5dc7..07e376233 100644 --- a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts +++ b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts @@ -1,7 +1,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, DriveFilesRepository } from '@/models/index.js'; +import type { UsersRepository, DriveFilesRepository, DriveFile } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; @@ -40,7 +40,7 @@ export class DeleteDriveFilesProcessorService { } let deletedCount = 0; - let cursor: any = null; + let cursor: DriveFile['id'] | null = null; while (true) { const files = await this.driveFilesRepository.find({ @@ -59,7 +59,7 @@ export class DeleteDriveFilesProcessorService { break; } - cursor = files[files.length - 1].id; + cursor = files.at(-1)?.id ?? null; for (const file of files) { await this.driveService.deleteFileSync(file); diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts index eb758e162..d100c6d09 100644 --- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts @@ -3,7 +3,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { UsersRepository, BlockingsRepository, Blocking } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; @@ -53,7 +53,7 @@ export class ExportBlockingProcessorService { const stream = fs.createWriteStream(path, { flags: 'a' }); let exportedCount = 0; - let cursor: any = null; + let cursor: Blocking['id'] | null = null; while (true) { const blockings = await this.blockingsRepository.find({ @@ -72,7 +72,7 @@ export class ExportBlockingProcessorService { break; } - cursor = blockings[blockings.length - 1].id; + cursor = blockings.at(-1)?.id ?? null; for (const block of blockings) { const u = await this.usersRepository.findOneBy({ id: block.blockeeId }); diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts index 76c38a6b8..2be42b1a7 100644 --- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts @@ -94,7 +94,7 @@ export class ExportFavoritesProcessorService { break; } - cursor = favorites[favorites.length - 1].id; + cursor = favorites.at(-1)?.id ?? null; for (const favorite of favorites) { let poll: Poll | undefined; diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts index 8726cb140..d54e5e0b3 100644 --- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts @@ -79,7 +79,7 @@ export class ExportFollowingProcessorService { break; } - cursor = followings[followings.length - 1].id; + cursor = followings.at(-1)?.id ?? null; for (const following of followings) { const u = await this.usersRepository.findOneBy({ id: following.followeeId }); diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts index 0f11a9e84..030e38931 100644 --- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts @@ -3,7 +3,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { IsNull, MoreThan } from 'typeorm'; import { format as dateFormat } from 'date-fns'; import { DI } from '@/di-symbols.js'; -import type { MutingsRepository, UsersRepository, BlockingsRepository } from '@/models/index.js'; +import type { MutingsRepository, UsersRepository, BlockingsRepository, Muting } from '@/models/index.js'; import type { Config } from '@/config.js'; import type Logger from '@/logger.js'; import { DriveService } from '@/core/DriveService.js'; @@ -56,7 +56,7 @@ export class ExportMutingProcessorService { const stream = fs.createWriteStream(path, { flags: 'a' }); let exportedCount = 0; - let cursor: any = null; + let cursor: Muting['id'] | null = null; while (true) { const mutes = await this.mutingsRepository.find({ @@ -76,7 +76,7 @@ export class ExportMutingProcessorService { break; } - cursor = mutes[mutes.length - 1].id; + cursor = mutes.at(-1)?.id ?? null; for (const mute of mutes) { const u = await this.usersRepository.findOneBy({ id: mute.muteeId }); diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts index 24fb33188..75f32ffee 100644 --- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts @@ -90,7 +90,7 @@ export class ExportNotesProcessorService { break; } - cursor = notes[notes.length - 1].id; + cursor = notes.at(-1)?.id ?? null; for (const note of notes) { let poll: Poll | undefined; diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index f53351d99..634f5f0a4 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -181,7 +181,7 @@ export class ActivityPubServerService { undefined, inStock ? `${partOf}?${url.query({ page: 'true', - cursor: followings[followings.length - 1].id, + cursor: followings.at(-1)!.id, })}` : undefined, ); @@ -189,7 +189,11 @@ export class ActivityPubServerService { return (this.apRendererService.addContext(rendered)); } else { // index page - const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`); + const rendered = this.apRendererService.renderOrderedCollection( + partOf, + user.followersCount, + `${partOf}?page=true`, + ); reply.header('Cache-Control', 'public, max-age=180'); this.setResponseType(request, reply); return (this.apRendererService.addContext(rendered)); @@ -269,7 +273,7 @@ export class ActivityPubServerService { undefined, inStock ? `${partOf}?${url.query({ page: 'true', - cursor: followings[followings.length - 1].id, + cursor: followings.at(-1)!.id, })}` : undefined, ); @@ -277,7 +281,11 @@ export class ActivityPubServerService { return (this.apRendererService.addContext(rendered)); } else { // index page - const rendered = this.apRendererService.renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`); + const rendered = this.apRendererService.renderOrderedCollection( + partOf, + user.followingCount, + `${partOf}?page=true`, + ); reply.header('Cache-Control', 'public, max-age=180'); this.setResponseType(request, reply); return (this.apRendererService.addContext(rendered)); @@ -310,7 +318,10 @@ export class ActivityPubServerService { const rendered = this.apRendererService.renderOrderedCollection( `${this.config.url}/users/${userId}/collections/featured`, - renderedNotes.length, undefined, undefined, renderedNotes, + renderedNotes.length, + undefined, + undefined, + renderedNotes, ); reply.header('Cache-Control', 'public, max-age=180'); @@ -387,7 +398,7 @@ export class ActivityPubServerService { })}` : undefined, notes.length ? `${partOf}?${url.query({ page: 'true', - until_id: notes[notes.length - 1].id, + until_id: notes.at(-1)!.id, })}` : undefined, ); @@ -395,7 +406,9 @@ export class ActivityPubServerService { return (this.apRendererService.addContext(rendered)); } else { // index page - const rendered = this.apRendererService.renderOrderedCollection(partOf, user.notesCount, + const rendered = this.apRendererService.renderOrderedCollection( + partOf, + user.notesCount, `${partOf}?page=true`, `${partOf}?page=true&since_id=000000000000000000000000`, ); diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index fc5f3811e..5e18dcbe0 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -128,12 +128,12 @@ export class SignupApiService { } if (instance.emailRequiredForSignup) { - if (await this.usersRepository.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) { + if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) { throw new FastifyReplyError(400, 'DUPLICATED_USERNAME'); } // Check deleted username duplication - if (await this.usedUsernamesRepository.findOneBy({ username: username.toLowerCase() })) { + if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) { throw new FastifyReplyError(400, 'USED_USERNAME'); } diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts index bee1ffbae..8401cf51d 100644 --- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts @@ -50,9 +50,9 @@ export default class extends Endpoint { throw e; }); - const exist = await this.promoNotesRepository.findOneBy({ noteId: note.id }); + const exist = await this.promoNotesRepository.exist({ where: { noteId: note.id } }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyPromoted); } diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts index 467f157a6..1fedab454 100644 --- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts @@ -69,8 +69,8 @@ export default class extends Endpoint { private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps) => { - const role = await this.rolesRepository.findOneBy({ id: ps.roleId }); - if (role == null) { + const roleExist = await this.rolesRepository.exist({ where: { id: ps.roleId } }); + if (!roleExist) { throw new ApiError(meta.errors.noSuchRole); } diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts index e69f9c12e..aa199ab73 100644 --- a/packages/backend/src/server/api/endpoints/auth/accept.ts +++ b/packages/backend/src/server/api/endpoints/auth/accept.ts @@ -58,12 +58,14 @@ export default class extends Endpoint { const accessToken = secureRndstr(32); // Fetch exist access token - const exist = await this.accessTokensRepository.findOneBy({ - appId: session.appId, - userId: me.id, + const exist = await this.accessTokensRepository.exist({ + where: { + appId: session.appId, + userId: me.id, + }, }); - if (exist == null) { + if (!exist) { const app = await this.appsRepository.findOneByOrFail({ id: session.appId }); // Generate Hash diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index d9ba99f20..4ad40c8f1 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -84,12 +84,14 @@ export default class extends Endpoint { }); // Check if already blocking - const exist = await this.blockingsRepository.findOneBy({ - blockerId: blocker.id, - blockeeId: blockee.id, + const exist = await this.blockingsRepository.exist({ + where: { + blockerId: blocker.id, + blockeeId: blockee.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyBlocking); } diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index 46dd26a45..38913ae93 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -84,12 +84,14 @@ export default class extends Endpoint { }); // Check not blocking - const exist = await this.blockingsRepository.findOneBy({ - blockerId: blocker.id, - blockeeId: blockee.id, + const exist = await this.blockingsRepository.exist({ + where: { + blockerId: blocker.id, + blockeeId: blockee.id, + } }); - if (exist == null) { + if (!exist) { throw new ApiError(meta.errors.notBlocking); } diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts index c3561e2a7..2837f2cf8 100644 --- a/packages/backend/src/server/api/endpoints/clips/add-note.ts +++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts @@ -87,12 +87,14 @@ export default class extends Endpoint { throw e; }); - const exist = await this.clipNotesRepository.findOneBy({ - noteId: note.id, - clipId: clip.id, + const exist = await this.clipNotesRepository.exist({ + where: { + noteId: note.id, + clipId: clip.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyClipped); } diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts index f08caaf8d..ce0985553 100644 --- a/packages/backend/src/server/api/endpoints/clips/favorite.ts +++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts @@ -58,12 +58,14 @@ export default class extends Endpoint { throw new ApiError(meta.errors.noSuchClip); } - const exist = await this.clipFavoritesRepository.findOneBy({ - clipId: clip.id, - userId: me.id, + const exist = await this.clipFavoritesRepository.exist({ + where: { + clipId: clip.id, + userId: me.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyFavorited); } diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index 290cd4d2c..cdcdde7e8 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -34,12 +34,14 @@ export default class extends Endpoint { private driveFilesRepository: DriveFilesRepository, ) { super(meta, paramDef, async (ps, me) => { - const file = await this.driveFilesRepository.findOneBy({ - md5: ps.md5, - userId: me.id, + const exist = await this.driveFilesRepository.exist({ + where: { + md5: ps.md5, + userId: me.id, + }, }); - return file != null; + return exist; }); } } diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts index 23de2f397..57245f9f4 100644 --- a/packages/backend/src/server/api/endpoints/flash/like.ts +++ b/packages/backend/src/server/api/endpoints/flash/like.ts @@ -66,12 +66,14 @@ export default class extends Endpoint { } // if already liked - const exist = await this.flashLikesRepository.findOneBy({ - flashId: flash.id, - userId: me.id, + const exist = await this.flashLikesRepository.exist({ + where: { + flashId: flash.id, + userId: me.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyLiked); } diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index 4ad16de91..009fc96f6 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -99,12 +99,14 @@ export default class extends Endpoint { }); // Check if already following - const exist = await this.followingsRepository.findOneBy({ - followerId: follower.id, - followeeId: followee.id, + const exist = await this.followingsRepository.exist({ + where: { + followerId: follower.id, + followeeId: followee.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyFollowing); } diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 4f12db127..570c4eb81 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -84,12 +84,14 @@ export default class extends Endpoint { }); // Check not following - const exist = await this.followingsRepository.findOneBy({ - followerId: follower.id, - followeeId: followee.id, + const exist = await this.followingsRepository.exist({ + where: { + followerId: follower.id, + followeeId: followee.id, + }, }); - if (exist == null) { + if (!exist) { throw new ApiError(meta.errors.notFollowing); } diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts index 6ac5fa860..c0bb55f64 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts @@ -66,12 +66,14 @@ export default class extends Endpoint { } // if already liked - const exist = await this.galleryLikesRepository.findOneBy({ - postId: post.id, - userId: me.id, + const exist = await this.galleryLikesRepository.exist({ + where: { + postId: post.id, + userId: me.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyLiked); } diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts index 12ec5855d..8582e98f7 100644 --- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts +++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts @@ -66,8 +66,8 @@ export default class extends Endpoint { private downloadService: DownloadService, ) { super(meta, paramDef, async (ps, me) => { - const users = await this.usersRepository.findOneBy({ id: me.id }); - if (users === null) throw new ApiError(meta.errors.noSuchUser); + const userExist = await this.usersRepository.exist({ where: { id: me.id } }); + if (!userExist) throw new ApiError(meta.errors.noSuchUser); const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId }); if (file === null) throw new ApiError(meta.errors.noSuchFile); if (file.size === 0) throw new ApiError(meta.errors.emptyFile); diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts index b8922b91e..352fe54c5 100644 --- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -47,19 +47,21 @@ export default class extends Endpoint { ) { super(meta, paramDef, async (ps, me) => { // Check if announcement exists - const announcement = await this.announcementsRepository.findOneBy({ id: ps.announcementId }); + const announcementExist = await this.announcementsRepository.exist({ where: { id: ps.announcementId } }); - if (announcement == null) { + if (!announcementExist) { throw new ApiError(meta.errors.noSuchAnnouncement); } // Check if already read - const read = await this.announcementReadsRepository.findOneBy({ - announcementId: ps.announcementId, - userId: me.id, + const alreadyRead = await this.announcementReadsRepository.exist({ + where: { + announcementId: ps.announcementId, + userId: me.id, + }, }); - if (read != null) { + if (alreadyRead) { return; } diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts index 93daeb0cd..415a60147 100644 --- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -28,9 +28,9 @@ export default class extends Endpoint { private globalEventService: GlobalEventService, ) { super(meta, paramDef, async (ps, me) => { - const token = await this.accessTokensRepository.findOneBy({ id: ps.tokenId }); + const tokenExist = await this.accessTokensRepository.exist({ where: { id: ps.tokenId } }); - if (token) { + if (tokenExist) { await this.accessTokensRepository.delete({ id: ps.tokenId, userId: me.id, diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts index ee358d5c6..ef53f9ef4 100644 --- a/packages/backend/src/server/api/endpoints/mute/create.ts +++ b/packages/backend/src/server/api/endpoints/mute/create.ts @@ -79,12 +79,14 @@ export default class extends Endpoint { }); // Check if already muting - const exist = await this.mutingsRepository.findOneBy({ - muterId: muter.id, - muteeId: mutee.id, + const exist = await this.mutingsRepository.exist({ + where: { + muterId: muter.id, + muteeId: mutee.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyMuting); } diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 96be5ed84..739316997 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -217,11 +217,13 @@ export default class extends Endpoint { // Check blocking if (renote.userId !== me.id) { - const block = await this.blockingsRepository.findOneBy({ - blockerId: renote.userId, - blockeeId: me.id, + const blockExist = await this.blockingsRepository.exist({ + where: { + blockerId: renote.userId, + blockeeId: me.id, + }, }); - if (block) { + if (blockExist) { throw new ApiError(meta.errors.youHaveBeenBlocked); } } @@ -240,11 +242,13 @@ export default class extends Endpoint { // Check blocking if (reply.userId !== me.id) { - const block = await this.blockingsRepository.findOneBy({ - blockerId: reply.userId, - blockeeId: me.id, + const blockExist = await this.blockingsRepository.exist({ + where: { + blockerId: reply.userId, + blockeeId: me.id, + }, }); - if (block) { + if (blockExist) { throw new ApiError(meta.errors.youHaveBeenBlocked); } } diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index 611ea1956..9299d6603 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -63,12 +63,14 @@ export default class extends Endpoint { }); // if already favorited - const exist = await this.noteFavoritesRepository.findOneBy({ - noteId: note.id, - userId: me.id, + const exist = await this.noteFavoritesRepository.exist({ + where: { + noteId: note.id, + userId: me.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyFavorited); } diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index 543c126d9..bc6648810 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -66,12 +66,14 @@ export default class extends Endpoint { } // if already liked - const exist = await this.pageLikesRepository.findOneBy({ - pageId: page.id, - userId: me.id, + const exist = await this.pageLikesRepository.exist({ + where: { + pageId: page.id, + userId: me.id, + }, }); - if (exist != null) { + if (exist) { throw new ApiError(meta.errors.alreadyLiked); } diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index 90febdbce..9baa930f5 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -44,12 +44,14 @@ export default class extends Endpoint { throw err; }); - const exist = await this.promoReadsRepository.findOneBy({ - noteId: note.id, - userId: me.id, + const exist = await this.promoReadsRepository.exist({ + where: { + noteId: note.id, + userId: me.id, + }, }); - if (exist != null) { + if (exist) { return; } diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index c6f71474b..18d66500a 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -97,11 +97,13 @@ export default class extends Endpoint { if (me == null) { throw new ApiError(meta.errors.forbidden); } else if (me.id !== user.id) { - const following = await this.followingsRepository.findOneBy({ - followeeId: user.id, - followerId: me.id, + const isFollowing = await this.followingsRepository.exist({ + where: { + followeeId: user.id, + followerId: me.id, + }, }); - if (following == null) { + if (!isFollowing) { throw new ApiError(meta.errors.forbidden); } } diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index b072c9662..6ea7b923d 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -97,11 +97,13 @@ export default class extends Endpoint { if (me == null) { throw new ApiError(meta.errors.forbidden); } else if (me.id !== user.id) { - const following = await this.followingsRepository.findOneBy({ - followeeId: user.id, - followerId: me.id, + const isFollowing = await this.followingsRepository.exist({ + where: { + followeeId: user.id, + followerId: me.id, + }, }); - if (following == null) { + if (!isFollowing) { throw new ApiError(meta.errors.forbidden); } } diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts index cccffcdad..beb0ba85f 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts @@ -84,11 +84,13 @@ export default class extends Endpoint { private roleService: RoleService, ) { super(meta, paramDef, async (ps, me) => { - const list = await this.userListsRepository.findOneBy({ - id: ps.listId, - isPublic: true, + const listExist = await this.userListsRepository.exist({ + where: { + id: ps.listId, + isPublic: true, + }, }); - if (list === null) throw new ApiError(meta.errors.noSuchList); + if (!listExist) throw new ApiError(meta.errors.noSuchList); const currentCount = await this.userListsRepository.countBy({ userId: me.id, }); @@ -114,18 +116,22 @@ export default class extends Endpoint { }); if (currentUser.id !== me.id) { - const block = await this.blockingsRepository.findOneBy({ - blockerId: currentUser.id, - blockeeId: me.id, + const blockExist = await this.blockingsRepository.exist({ + where: { + blockerId: currentUser.id, + blockeeId: me.id, + }, }); - if (block) { + if (blockExist) { throw new ApiError(meta.errors.youHaveBeenBlocked); } } - const exist = await this.userListJoiningsRepository.findOneBy({ - userListId: userList.id, - userId: currentUser.id, + const exist = await this.userListJoiningsRepository.exist({ + where: { + userListId: userList.id, + userId: currentUser.id, + }, }); if (exist) { diff --git a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts index ea1a022be..2c09a47fe 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts @@ -41,21 +41,25 @@ export default class extends Endpoint { private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { - const userList = await this.userListsRepository.findOneBy({ - id: ps.listId, - isPublic: true, + const userListExist = await this.userListsRepository.exist({ + where: { + id: ps.listId, + isPublic: true, + }, }); - if (userList === null) { + if (!userListExist) { throw new ApiError(meta.errors.noSuchList); } - const exist = await this.userListFavoritesRepository.findOneBy({ - userId: me.id, - userListId: ps.listId, + const exist = await this.userListFavoritesRepository.exist({ + where: { + userId: me.id, + userListId: ps.listId, + }, }); - if (exist !== null) { + if (exist) { throw new ApiError(meta.errors.alreadyFavorited); } diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 925037e48..6e1f6b2c6 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -100,18 +100,22 @@ export default class extends Endpoint { // Check blocking if (user.id !== me.id) { - const block = await this.blockingsRepository.findOneBy({ - blockerId: user.id, - blockeeId: me.id, + const blockExist = await this.blockingsRepository.exist({ + where: { + blockerId: user.id, + blockeeId: me.id, + }, }); - if (block) { + if (blockExist) { throw new ApiError(meta.errors.youHaveBeenBlocked); } } - const exist = await this.userListJoiningsRepository.findOneBy({ - userListId: userList.id, - userId: user.id, + const exist = await this.userListJoiningsRepository.exist({ + where: { + userListId: userList.id, + userId: user.id, + }, }); if (exist) { diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 8077841c8..3fd418d04 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -69,10 +69,12 @@ export default class extends Endpoint { userListId: ps.listId, }); if (me !== null) { - additionalProperties.isLiked = (await this.userListFavoritesRepository.findOneBy({ - userId: me.id, - userListId: ps.listId, - }) !== null); + additionalProperties.isLiked = await this.userListFavoritesRepository.exist({ + where: { + userId: me.id, + userListId: ps.listId, + }, + }); } else { additionalProperties.isLiked = false; } diff --git a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts index be8e31781..a7c3b5894 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts @@ -39,12 +39,14 @@ export default class extends Endpoint { private userListFavoritesRepository: UserListFavoritesRepository, ) { super(meta, paramDef, async (ps, me) => { - const userList = await this.userListsRepository.findOneBy({ - id: ps.listId, - isPublic: true, + const userListExist = await this.userListsRepository.exist({ + where: { + id: ps.listId, + isPublic: true, + }, }); - if (userList === null) { + if (!userListExist) { throw new ApiError(meta.errors.noSuchList); } diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index d97d2b8ba..ea4cff0bc 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -34,11 +34,13 @@ class UserListChannel extends Channel { this.listId = params.listId as string; // Check existence and owner - const list = await this.userListsRepository.findOneBy({ - id: this.listId, - userId: this.user!.id, + const listExist = await this.userListsRepository.exist({ + where: { + id: this.listId, + userId: this.user!.id, + }, }); - if (!list) return; + if (!listExist) return; // Subscribe stream this.subscriber.on(`userListStream:${this.listId}`, this.send); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 48947072e..31ea3e5ab 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -447,12 +447,12 @@ export async function testPaginationConsistency id + ':' + createdAt), @@ -480,7 +480,7 @@ export async function testPaginationConsistency id + ':' + createdAt), diff --git a/packages/frontend/.storybook/preload-locale.ts b/packages/frontend/.storybook/preload-locale.ts index 636931967..2b7362b88 100644 --- a/packages/frontend/.storybook/preload-locale.ts +++ b/packages/frontend/.storybook/preload-locale.ts @@ -1,5 +1,5 @@ import { writeFile } from 'node:fs/promises'; -import * as locales from '../../../locales/index.js'; +import locales from '../../../locales/index.js'; await writeFile( new URL('locale.ts', import.meta.url), diff --git a/packages/frontend/src/cache.ts b/packages/frontend/src/cache.ts index c95da64bb..cb315c8ff 100644 --- a/packages/frontend/src/cache.ts +++ b/packages/frontend/src/cache.ts @@ -4,3 +4,4 @@ import { Cache } from '@/scripts/cache'; export const clipsCache = new Cache(Infinity); export const rolesCache = new Cache(Infinity); export const userListsCache = new Cache(Infinity); +export const antennasCache = new Cache(Infinity); diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue index 8bfcfa6aa..7aa8f94c3 100644 --- a/packages/frontend/src/components/MkAsUi.vue +++ b/packages/frontend/src/components/MkAsUi.vue @@ -39,7 +39,7 @@ -
+
@@ -102,10 +102,6 @@ function openPostForm() { gap: 12px; } -.containerCenter { - text-align: center; -} - .fontSerif { font-family: serif; } diff --git a/packages/frontend/src/components/MkAvatars.vue b/packages/frontend/src/components/MkAvatars.vue index 630620fc0..437dce0a1 100644 --- a/packages/frontend/src/components/MkAvatars.vue +++ b/packages/frontend/src/components/MkAvatars.vue @@ -1,24 +1,29 @@ diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index bb4361ef6..599819b3f 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -25,8 +25,8 @@

- {{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }} - {{ file.name.substr(file.name.lastIndexOf('.')) }} + {{ file.name.lastIndexOf('.') != -1 ? file.name.substring(0, file.name.lastIndexOf('.')) : file.name }} + {{ file.name.substring(file.name.lastIndexOf('.')) }}

diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index 201a6ccdc..aff227da4 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -568,7 +568,7 @@ function fetchMoreFolders() { os.api('drive/folders', { folderId: folder.value ? folder.value.id : null, type: props.type, - untilId: folders.value[folders.value.length - 1].id, + untilId: folders.value.at(-1)?.id, limit: max + 1, }).then(folders => { if (folders.length === max + 1) { @@ -591,7 +591,7 @@ function fetchMoreFiles() { os.api('drive/files', { folderId: folder.value ? folder.value.id : null, type: props.type, - untilId: files.value[files.value.length - 1].id, + untilId: files.value.at(-1)?.id, limit: max + 1, }).then(files => { if (files.length === max + 1) { diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue index 2e4f93e84..8e61c7048 100644 --- a/packages/frontend/src/components/MkLink.vue +++ b/packages/frontend/src/components/MkLink.vue @@ -1,6 +1,6 @@ diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index fcad5b806..5c37f70bd 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -32,7 +32,7 @@
- +
diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue index 937e0f079..3b6e348e0 100644 --- a/packages/frontend/src/components/MkUserPopup.vue +++ b/packages/frontend/src/components/MkUserPopup.vue @@ -88,7 +88,7 @@ onMounted(() => { user = props.q; } else { const query = props.q.startsWith('@') ? - Acct.parse(props.q.substr(1)) : + Acct.parse(props.q.substring(1)) : { userId: props.q }; os.api('users/show', query).then(res => { diff --git a/packages/frontend/src/components/global/MkA.stories.impl.ts b/packages/frontend/src/components/global/MkA.stories.impl.ts index 639ed19af..6e3ff573c 100644 --- a/packages/frontend/src/components/global/MkA.stories.impl.ts +++ b/packages/frontend/src/components/global/MkA.stories.impl.ts @@ -29,11 +29,11 @@ export const Default = { const canvas = within(canvasElement); const a = canvas.getByRole('link'); await expect(a.href).toMatch(/^https?:\/\/.*#test$/); - await userEvent.click(a, { button: 2 }); + await userEvent.pointer({ keys: '[MouseRight]', target: a }); await tick(); const menu = canvas.getByRole('menu'); await expect(menu).toBeInTheDocument(); - await userEvent.click(a, { button: 0 }); + await userEvent.click(a); a.blur(); await tick(); await expect(menu).not.toBeInTheDocument(); diff --git a/packages/frontend/src/components/global/MkAd.stories.impl.ts b/packages/frontend/src/components/global/MkAd.stories.impl.ts index 7d8a42a03..58b6cc14c 100644 --- a/packages/frontend/src/components/global/MkAd.stories.impl.ts +++ b/packages/frontend/src/components/global/MkAd.stories.impl.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/explicit-function-return-type */ import { expect } from '@storybook/jest'; -import { userEvent, within } from '@storybook/testing-library'; +import { userEvent, waitFor, within } from '@storybook/testing-library'; import { StoryObj } from '@storybook/vue3'; -import { i18n } from '@/i18n'; import MkAd from './MkAd.vue'; +import { i18n } from '@/i18n'; const common = { render(args) { return { @@ -36,6 +36,7 @@ const common = { const i = buttons[0]; await expect(i).toBeInTheDocument(); await userEvent.click(i); + await waitFor(() => expect(canvasElement).toHaveTextContent(i18n.ts._ad.back)); await expect(a).not.toBeInTheDocument(); await expect(i).not.toBeInTheDocument(); buttons = canvas.getAllByRole('button'); @@ -49,6 +50,7 @@ const common = { await expect(back).toBeInTheDocument(); await expect(back).toHaveTextContent(i18n.ts._ad.back); await userEvent.click(back); + await waitFor(() => expect(canvas.queryByRole('img')).toBeTruthy()); if (reduce) { await expect(reduce).not.toBeInTheDocument(); } diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index e8a7f17cc..e7af47268 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -18,7 +18,7 @@ const props = defineProps<{ useOriginalSize?: boolean; }>(); -const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substr(1, props.name.length - 2) : props.name).replace('@.', '')); +const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substring(1, props.name.length - 1) : props.name).replace('@.', '')); const isLocal = computed(() => !props.host && (customEmojiName.value.endsWith('@.') || !customEmojiName.value.includes('@'))); const rawUrl = computed(() => { diff --git a/packages/frontend/src/components/global/i18n.ts b/packages/frontend/src/components/global/i18n.ts index 2708b759a..6706d08f2 100644 --- a/packages/frontend/src/components/global/i18n.ts +++ b/packages/frontend/src/components/global/i18n.ts @@ -11,13 +11,13 @@ export default function(props: { src: string; tag?: string; textTag?: string; }, parsed.push(str); break; } else { - if (nextBracketOpen > 0) parsed.push(str.substr(0, nextBracketOpen)); + if (nextBracketOpen > 0) parsed.push(str.substring(0, nextBracketOpen)); parsed.push({ arg: str.substring(nextBracketOpen + 1, nextBracketClose), }); } - str = str.substr(nextBracketClose + 1); + str = str.substring(nextBracketClose + 1); } return h(props.tag ?? 'span', parsed.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]())); diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue index b67cf2647..196a1b76e 100644 --- a/packages/frontend/src/pages/about-misskey.vue +++ b/packages/frontend/src/pages/about-misskey.vue @@ -88,10 +88,13 @@
- Mask Network + Mask Network
- DC Advirth + Skeb +
+
+ DC Advirth
@@ -161,6 +164,9 @@ const patronsWithIcon = [{ }, { name: '清遊あみ', icon: 'https://misskey-hub.net/patrons/de25195b88e940a388388bea2e7637d8.jpg', +}, { + name: 'Nagi8410', + icon: 'https://misskey-hub.net/patrons/31b102ab4fc540ed806b0461575d38be.jpg', }]; const patrons = [ @@ -256,6 +262,7 @@ const patrons = [ 'binvinyl', '渡志郎', 'ぷーざ', + '越貝鯛丸', ]; let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure')); diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue index b08757aeb..7d8d46851 100644 --- a/packages/frontend/src/pages/admin/overview.queue.vue +++ b/packages/frontend/src/pages/admin/overview.queue.vue @@ -85,7 +85,7 @@ onMounted(() => { connection.on('stats', onStats); connection.on('statsLog', onStatsLog); connection.send('requestLog', { - id: Math.random().toString().substr(2, 8), + id: Math.random().toString().substring(2, 10), length: 100, }); }); diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue index 838c197f0..41a6d4f5b 100644 --- a/packages/frontend/src/pages/admin/overview.vue +++ b/packages/frontend/src/pages/admin/overview.vue @@ -156,7 +156,7 @@ onMounted(async () => { nextTick(() => { queueStatsConnection.send('requestLog', { - id: Math.random().toString().substr(2, 8), + id: Math.random().toString().substring(2, 10), length: 100, }); }); diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue index 8e6856fdd..83ca9639e 100644 --- a/packages/frontend/src/pages/admin/queue.chart.vue +++ b/packages/frontend/src/pages/admin/queue.chart.vue @@ -106,7 +106,7 @@ onMounted(() => { connection.on('stats', onStats); connection.on('statsLog', onStatsLog); connection.send('requestLog', { - id: Math.random().toString().substr(2, 8), + id: Math.random().toString().substring(2, 10), length: 200, }); }); diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index bcc0fc686..2a056f21d 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -87,7 +87,7 @@ const props = defineProps<{ channelId: string; }>(); -let tab = $ref('timeline'); +let tab = $ref('overview'); let channel = $ref(null); let favorited = $ref(false); let searchQuery = $ref(''); @@ -107,6 +107,9 @@ watch(() => props.channelId, async () => { channelId: props.channelId, }); favorited = channel.isFavorited; + if (favorited || channel.isFollowing) { + tab = 'timeline'; + } }, { immediate: true }); function edit() { diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue index dde4d67f1..359bbeadc 100644 --- a/packages/frontend/src/pages/custom-emojis-manager.vue +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -18,7 +18,7 @@ Set tag Add tag Remove tag - Set Lisence + Set License Delete
@@ -221,7 +221,7 @@ const setCategoryBulk = async () => { emojisPaginationComponent.value.reload(); }; -const setLisenceBulk = async () => { +const setLicenseBulk = async () => { const { canceled, result } = await os.inputText({ title: 'License', }); diff --git a/packages/frontend/src/pages/my-antennas/create.vue b/packages/frontend/src/pages/my-antennas/create.vue index 355d18fdb..632c36bbf 100644 --- a/packages/frontend/src/pages/my-antennas/create.vue +++ b/packages/frontend/src/pages/my-antennas/create.vue @@ -9,6 +9,7 @@ import XAntenna from './editor.vue'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; import { useRouter } from '@/router'; +import { antennasCache } from '@/cache'; const router = useRouter(); @@ -26,13 +27,10 @@ let draft = $ref({ }); function onAntennaCreated() { + antennasCache.delete(); router.push('/my/antennas'); } -const headerActions = $computed(() => []); - -const headerTabs = $computed(() => []); - definePageMetadata({ title: i18n.ts.manageAntennas, icon: 'ti ti-antenna', diff --git a/packages/frontend/src/pages/my-antennas/edit.vue b/packages/frontend/src/pages/my-antennas/edit.vue index da9b2de48..3fb9690ac 100644 --- a/packages/frontend/src/pages/my-antennas/edit.vue +++ b/packages/frontend/src/pages/my-antennas/edit.vue @@ -10,6 +10,7 @@ import * as os from '@/os'; import { i18n } from '@/i18n'; import { useRouter } from '@/router'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { antennasCache } from '@/cache'; const router = useRouter(); @@ -20,6 +21,7 @@ const props = defineProps<{ }>(); function onAntennaUpdated() { + antennasCache.delete(); router.push('/my/antennas'); } @@ -27,10 +29,6 @@ os.api('antennas/show', { antennaId: props.antennaId }).then((antennaResponse) = antenna = antennaResponse; }); -const headerActions = $computed(() => []); - -const headerTabs = $computed(() => []); - definePageMetadata({ title: i18n.ts.manageAntennas, icon: 'ti ti-antenna', diff --git a/packages/frontend/src/pages/my-antennas/index.vue b/packages/frontend/src/pages/my-antennas/index.vue index 2ca026b9a..1e9136f1f 100644 --- a/packages/frontend/src/pages/my-antennas/index.vue +++ b/packages/frontend/src/pages/my-antennas/index.vue @@ -2,15 +2,20 @@ -
- {{ i18n.ts.add }} +
+
+
+ +
{{ i18n.ts.nothing }}
+
+
-
- - -
{{ antenna.name }}
-
-
+ {{ i18n.ts.add }} + +
+ +
{{ antenna.name }}
+
@@ -18,19 +23,31 @@ - diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue index cee241c48..0f59ca0b3 100644 --- a/packages/frontend/src/pages/my-lists/index.vue +++ b/packages/frontend/src/pages/my-lists/index.vue @@ -3,38 +3,43 @@
+
+
+ +
{{ i18n.ts.nothing }}
+
+
+ {{ i18n.ts.createList }} - -
- -
{{ list.name }}
- -
-
-
+
+ +
{{ list.name }}
+ +
+